From f8b39d90de94817a9b718569733b01b63611d173 Mon Sep 17 00:00:00 2001 From: Valentin Boulanger Date: Mon, 10 Nov 2025 14:18:22 +0100 Subject: [PATCH] corrected events --- .gitignore | 2 + app.go | 1 - frontend/src/App.svelte | 128 ++++----- .../General/RoundDropdownList.svelte | 153 +++++++++++ .../src/components/Settings/Settings.svelte | 93 ------- frontend/src/main.js | 3 +- frontend/src/runtime-events.js | 137 ++++++++++ frontend/src/stores.js | 9 +- hardware/FTDIFinder.go | 10 +- project.go | 248 ++++++++++++++++++ 10 files changed, 620 insertions(+), 164 deletions(-) create mode 100644 frontend/src/components/General/RoundDropdownList.svelte create mode 100644 frontend/src/runtime-events.js create mode 100644 project.go diff --git a/.gitignore b/.gitignore index a83910e..8195664 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ frontend/wailsjs *.exe *.o *.rc +*.dll +*.dll.a frontend/public \ No newline at end of file diff --git a/app.go b/app.go index 8708e62..67b9289 100644 --- a/app.go +++ b/app.go @@ -24,7 +24,6 @@ type App struct { wmiMutex sync.Mutex // Avoid some WMI operations at the same time projectInfo ProjectInfo // The project information structure projectSave string // The file name of the project - projectCancel context.CancelFunc // The project cancel function } // NewApp creates a new App application struct diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index b7d7ba7..c17324c 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -7,66 +7,50 @@ import Settings from './components/Settings/Settings.svelte'; import Devices from './components/Devices/Devices.svelte'; import Show from './components/Show/Show.svelte'; + import DropdownList from "./components/General/DropdownList.svelte"; + import RoundDropdownList from "./components/General/RoundDropdownList.svelte"; import GeneralConsole from './components/Console/GeneralConsole.svelte'; import RoundIconButton from './components/General/RoundIconButton.svelte'; - import { generateToast, showInformation, needProjectSave, peripherals } from './stores'; - import { SaveProject } from '../wailsjs/go/main/App.js'; - import { construct_svelte_component } from 'svelte/internal'; - import { EventsOn } from '../wailsjs/runtime' - import { CreateProject } from "../wailsjs/go/main/App"; + import { generateToast, showInformation, needProjectSave, projectsList } from './stores.js'; + import { GetProjects, CreateProject, OpenProjectFromDisk, SaveProject } from '../wailsjs/go/main/App.js'; import { WindowSetTitle } from "../wailsjs/runtime/runtime" - import { get } from "svelte/store" import ToastNotification from './components/General/ToastNotification.svelte'; + import { onMount, onDestroy } from 'svelte' + import { destroyRuntimeEvents, initRuntimeEvents } from './runtime-events.js' - // Handle the event when a new peripheral is detected - EventsOn('PERIPHERAL_ARRIVAL', function(peripheralInfo){ - // When a new peripheral is detected, add it to the map and: - // - Pass the isDetected key to true - // - Set the isSaved key to the last value - let peripheralsList = get(peripherals) - let lastSavedProperty = peripheralsList[peripheralInfo.SerialNumber]?.isSaved - peripheralInfo.isDetected = true - peripheralInfo.isSaved = (lastSavedProperty === true) ? true : false - peripherals.update((peripherals) => { - peripherals[peripheralInfo.SerialNumber] = peripheralInfo - return {...peripherals} - }) - console.log("Hardware has been added to the system"); - generateToast('info', 'bxs-hdd', $_("peripheralArrivalToast") + ' ' + peripheralInfo.Name + '') - }) - - // Handle the event when a peripheral is removed from the system - EventsOn('PERIPHERAL_REMOVAL', function(peripheralInfo){ - console.log("Hardware has been removed from the system"); - // When a peripheral is disconnected, pass its isDetected key to false - // If the isSaved key is set to false, we can completely remove the peripheral from the list - let peripheralsList = get(peripherals) - let lastSavedProperty = peripheralsList[peripheralInfo.SerialNumber]?.isSaved - let needToDelete = (lastSavedProperty !== true) ? true : false - peripherals.update((storedPeripherals) => { - if (needToDelete){ - delete storedPeripherals[peripheralInfo.SerialNumber]; - return { ...storedPeripherals }; - } - storedPeripherals[peripheralInfo.SerialNumber].isDetected = false - return {...storedPeripherals} + function initializeNewProject(){ + // Instanciate a new project + CreateProject().then(() => { + // Project created, we set the needSave flag to true (not already saved) + needProjectSave.set(true) + }).catch((error) => { + console.error(`Unable to create the project: ${error}`) + generateToast('danger', 'bx-error', $_("projectCreateErrorToast")) }) - generateToast('warning', 'bxs-hdd', $_("peripheralRemovalToast") + ' ' + peripheralInfo.Name + '') + } + + // Initialize runtime events at startup + onMount(() => { + initRuntimeEvents() + + // Handle window shortcuts + document.addEventListener('keydown', function(event) { + // Check the CTRL+S keys + if ((event.ctrlKey || event.metaKey) && event.key === 's') { + // Avoid the natural behaviour + event.preventDefault(); + // Save the current project + saveProject() + } + }); + + // Initialize a new project + initializeNewProject() }) - // Handle the event when a peripheral status is updated - EventsOn('PERIPHERAL_STATUS', function(peripheral, status){ - console.log("Hardware status has been updated to " + status); - // When a peripheral status is updated, update it in the store - peripherals.update((storedPeripherals) => { - return { - ...storedPeripherals, - [peripheral.SerialNumber]: { - ...storedPeripherals[peripheral.SerialNumber], - isSaved: true, - Status: status, - }, - }}) + // Destroy runtime events at shutdown + onDestroy(() => { + destroyRuntimeEvents() }) // Set the window title @@ -92,23 +76,41 @@ }) } - // Handle window shortcuts - document.addEventListener('keydown', function(event) { - // Check the CTRL+S keys - if ((event.ctrlKey || event.metaKey) && event.key === 's') { - // Avoid the natural behaviour - event.preventDefault(); - // Save the current project - saveProject() - } - }); + // Open the selected project + function openSelectedProject(event){ + let selectedOption = event.detail.key + // Open the selected project + OpenProjectFromDisk(selectedOption).then(() => { + // Project opened, we set the needSave flag to false (already saved) + needProjectSave.set(false) + }).catch((error) => { + console.error(`Unable to open the project: ${error}`) + generateToast('danger', 'bx-error', $_("projectOpenErrorToast")) + }) + } + + // Refresh the projects list + let choices = new Map() + function loadProjectsList(){ + GetProjects().then((projects) => { + choices = new Map(projects.map(item => [item.Save, item.Name])); + $projectsList = projects + }).catch((error) => { + console.error(`Unable to get the projects list: ${error}`) + generateToast('danger', 'bx-error', $_("projectsLoadErrorToast")) + }) + }
+ + + + {#if $needProjectSave} - + {/if}
diff --git a/frontend/src/components/General/RoundDropdownList.svelte b/frontend/src/components/General/RoundDropdownList.svelte new file mode 100644 index 0000000..e538148 --- /dev/null +++ b/frontend/src/components/General/RoundDropdownList.svelte @@ -0,0 +1,153 @@ + + + + +
+ + + {#if (operationalStatus !== undefined)} +
+
+ {/if} + +
+ {#each Array.from(choices) as [key, value]} +
handleclick({key})}>{value}
+ {/each} +
+
+ + \ No newline at end of file diff --git a/frontend/src/components/Settings/Settings.svelte b/frontend/src/components/Settings/Settings.svelte index a2e7b48..9625ecb 100644 --- a/frontend/src/components/Settings/Settings.svelte +++ b/frontend/src/components/Settings/Settings.svelte @@ -1,109 +1,16 @@ - - - diff --git a/frontend/src/main.js b/frontend/src/main.js index 034c3f3..70e0647 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,8 +1,9 @@ import App from './App.svelte'; import { WindowSetTitle } from "../wailsjs/runtime/runtime" +import { _ } from 'svelte-i18n' -import {showInformation, needProjectSave} from './stores.js'; +import {messages, showInformation, needProjectSave} from './stores.js'; // Load dictionaries import { addMessages, init } from 'svelte-i18n'; diff --git a/frontend/src/runtime-events.js b/frontend/src/runtime-events.js new file mode 100644 index 0000000..961acb9 --- /dev/null +++ b/frontend/src/runtime-events.js @@ -0,0 +1,137 @@ +import { EventsOn, EventsOff } from "../wailsjs/runtime/runtime.js" +import { peripherals, generateToast, needProjectSave, showInformation } from './stores' +import { get } from "svelte/store" +import { _ } from 'svelte-i18n' + +function addPeripheral (peripheralInfo){ + // When a new peripheral is detected, add it to the map and: + // - Pass the isDetected key to true + // - Set the isSaved key to the last value + let peripheralsList = get(peripherals) + let lastSavedProperty = peripheralsList[peripheralInfo.SerialNumber]?.isSaved + peripheralInfo.isDetected = true + peripheralInfo.isSaved = (lastSavedProperty === true) ? true : false + peripherals.update((peripherals) => { + peripherals[peripheralInfo.SerialNumber] = peripheralInfo + return {...peripherals} + }) + console.log("Hardware has been added to the system"); + generateToast('info', 'bxs-hdd', get(_)("peripheralArrivalToast") + ' ' + peripheralInfo.Name + '') +} + +function removePeripheral (peripheralInfo){ + console.log("Hardware has been removed from the system"); + // When a peripheral is disconnected, pass its isDetected key to false + // If the isSaved key is set to false, we can completely remove the peripheral from the list + let peripheralsList = get(peripherals) + let lastSavedProperty = peripheralsList[peripheralInfo.SerialNumber]?.isSaved + let needToDelete = (lastSavedProperty !== true) ? true : false + peripherals.update((storedPeripherals) => { + if (needToDelete){ + delete storedPeripherals[peripheralInfo.SerialNumber]; + return { ...storedPeripherals }; + } + storedPeripherals[peripheralInfo.SerialNumber].isDetected = false + return {...storedPeripherals} + }) + generateToast('warning', 'bxs-hdd', get(_)("peripheralRemovalToast") + ' ' + peripheralInfo.Name + '') +} + +function updatePeripheral(peripheral, status){ + console.log("Hardware status has been updated to " + status); + // When a peripheral status is updated, update it in the store + peripherals.update((storedPeripherals) => { + return { + ...storedPeripherals, + [peripheral.SerialNumber]: { + ...storedPeripherals[peripheral.SerialNumber], + isSaved: true, + Status: status, + }, + }}) +} + +function loadPeripheral (peripheralInfo) { + peripherals.update((storedPeripherals) => { + // Add the saved peripherals of the project + // If already exists pass the isSaved key to true, if not create the peripheral and set it to disconnected + // Add the peripheral to the list of peripherals, with the last isDetected key and the isSaved key to true + let lastDetectedKey = storedPeripherals[peripheralInfo.SerialNumber]?.isDetected + storedPeripherals[peripheralInfo.SerialNumber] = peripheralInfo + storedPeripherals[peripheralInfo.SerialNumber].isDetected = (lastDetectedKey === true) ? true : false + storedPeripherals[peripheralInfo.SerialNumber].isSaved = true + return {...storedPeripherals} + }) + //TODO: Lors d'un chargement/déchargement natif au démarrage, il ne doit pas y avoir de nécessité de sauvegarder + needProjectSave.set(true) +} + +function loadProject (showInfo){ + // Store project information + showInformation.set(showInfo) + + console.log("Project has been opened"); + generateToast('info', 'bx-folder-open', get(_)("projectOpenedToast") + ' ' + showInfo.Name + '') +} + +function unloadPeripheral (peripheralInfo) { + peripherals.update((storedPeripherals) => { + // Set all the isSaved keys to false and delete the disconnected peripherals + storedPeripherals[peripheralInfo.SerialNumber].isSaved = false + if (!storedPeripherals[peripheralInfo.SerialNumber].isDetected) { + delete storedPeripherals[peripheralInfo.SerialNumber] + } + return {...storedPeripherals} + }) + + //TODO: Lors d'un chargement/déchargement natif au démarrage, il ne doit pas y avoir de nécessité de sauvegarder + needProjectSave.set(true) + } + +let initialized = false + +export function initRuntimeEvents(){ + if (initialized) return + initialized = true + + // Handle the event when a new peripheral is detected + EventsOn('PERIPHERAL_ARRIVAL', addPeripheral) + + // Handle the event when a peripheral is removed from the system + EventsOn('PERIPHERAL_REMOVAL', removePeripheral) + + // Handle the event when a peripheral status is updated + EventsOn('PERIPHERAL_STATUS', updatePeripheral) + + // Handle the event when a new project need to be loaded + EventsOn('LOAD_PROJECT', loadProject) + + // Handle a peripheral loaded in the project + EventsOn('LOAD_PERIPHERAL', loadPeripheral) + + // Handle a peripheral unloaded from the project + EventsOn('UNLOAD_PERIPHERAL', unloadPeripheral) +} + +export function destroyRuntimeEvents(){ + if (!initialized) return + initialized = false + + // Handle the event when a new peripheral is detected + EventsOff('PERIPHERAL_ARRIVAL') + + // Handle the event when a peripheral is removed from the system + EventsOff('PERIPHERAL_REMOVAL') + + // Handle the event when a peripheral status is updated + EventsOff('PERIPHERAL_STATUS') + + // Handle the event when a new project need to be loaded + EventsOff('LOAD_PROJECT') + + // Handle a peripheral loaded in the project + EventsOff('LOAD_PERIPHERAL') + + // Handle a peripheral unloaded from the project + EventsOff('UNLOAD_PERIPHERAL') +} \ No newline at end of file diff --git a/frontend/src/stores.js b/frontend/src/stores.js index a060938..16d093e 100644 --- a/frontend/src/stores.js +++ b/frontend/src/stores.js @@ -10,13 +10,12 @@ export let showInformation = writable({}) // Toasts notifications export let messages = writable([]) export function generateToast(type, icon, text){ - messages.update((value) => { - value.push( { id: Date.now(), type: type, icon: icon, text: text } ) - return value.slice(-5) - }) + messages.update((value) => { + value.push( { id: Date.now(), type: type, icon: icon, text: text } ) + return value.slice(-5) + }) } - // Application colors export const colors = writable({ first: "#1B262C", diff --git a/hardware/FTDIFinder.go b/hardware/FTDIFinder.go index 3a748e2..f24812f 100644 --- a/hardware/FTDIFinder.go +++ b/hardware/FTDIFinder.go @@ -11,6 +11,7 @@ import ( "unsafe" "github.com/rs/zerolog/log" + "github.com/wailsapp/wails/v2/pkg/runtime" ) /* @@ -52,6 +53,9 @@ func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData Peri f.registeredPeripherals[peripheralData.SerialNumber] = ftdiPeripheral log.Trace().Any("periph", &ftdiPeripheral).Str("file", "FTDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created") + // Emit the event to the front + runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData) + // Peripheral created, connect it err = ftdiPeripheral.Connect(ctx) if err != nil { @@ -80,8 +84,12 @@ func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralID stri if err != nil { return err } + + delete(f.registeredPeripherals, peripheralID) + + // Emit the event to the front + runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheral.info) } - delete(f.registeredPeripherals, peripheralID) return nil } diff --git a/project.go b/project.go new file mode 100644 index 0000000..0a249ab --- /dev/null +++ b/project.go @@ -0,0 +1,248 @@ +package main + +import ( + "dmxconnect/hardware" + "fmt" + + "github.com/rs/zerolog/log" + + "os" + "path/filepath" + "time" + + "github.com/wailsapp/wails/v2/pkg/runtime" + "gopkg.in/yaml.v2" +) + +const ( + projectsDirectory = "projects" // The directory were are stored all the projects + avatarsDirectory = "frontend/public" // The directory were are stored all the avatars + projectExtension = ".dmxproj" // The extension of a DMX Connect project +) + +// GetProjects gets all the projects in the projects directory +func (a *App) GetProjects() ([]ProjectMetaData, error) { + projects := []ProjectMetaData{} + + f, err := os.Open(projectsDirectory) + if err != nil { + log.Err(err).Str("file", "project").Msg("unable to open the projects directory") + return nil, fmt.Errorf("unable to open the projects directory: %v", err) + } + log.Trace().Str("file", "project").Str("projectsDirectory", projectsDirectory).Msg("projects directory opened") + + files, err := f.Readdir(0) + if err != nil { + log.Err(err).Str("file", "project").Msg("unable to read the projects directory") + return nil, fmt.Errorf("unable to read the projects directory: %v", err) + } + log.Trace().Str("file", "project").Any("projectsFiles", files).Msg("project files got") + + for _, fileInfo := range files { + // Open the file and get the show name + fileData, err := os.ReadFile(filepath.Join(projectsDirectory, fileInfo.Name())) + if err != nil { + log.Warn().Str("file", "project").Str("projectFile", fileInfo.Name()).Msg("unable to open the project file") + continue + } + log.Trace().Str("file", "project").Str("projectFile", fileInfo.Name()).Any("fileData", fileData).Msg("project file read") + + projectObject := ProjectInfo{} + err = yaml.Unmarshal(fileData, &projectObject) + if err != nil { + log.Warn().Str("file", "project").Str("projectFile", fileInfo.Name()).Msg("project has invalid format") + continue + } + log.Trace().Str("file", "project").Str("projectFile", fileInfo.Name()).Msg("project file unmarshalled") + + // Add the SaveFile property + projects = append(projects, ProjectMetaData{ + Name: projectObject.ShowInfo.Name, + Save: fileInfo.Name(), + }) + } + log.Info().Str("file", "project").Any("projectsList", projects).Msg("got the projects list") + return projects, nil +} + +// CreateProject creates a new blank project +func (a *App) CreateProject() error { + + // Create new project information + date := time.Now() + projectInfo := ProjectInfo{ + ShowInfo{ + Name: "My new show", + Date: fmt.Sprintf("%04d-%02d-%02dT%02d:%02d", date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute()), + Avatar: "appicon.png", + Comments: "Write your comments here", + }, + make(map[string]hardware.PeripheralInfo), + } + + // The project isn't saved for now + a.projectSave = "" + + return a.OpenProject(projectInfo) +} + +// OpenProjectFromDisk opens a project based on its filename +func (a *App) OpenProjectFromDisk(projectFile string) error { + // Open the project file + projectPath := filepath.Join(projectsDirectory, projectFile) + log.Trace().Str("file", "project").Str("projectPath", projectPath).Msg("project path is created") + content, err := os.ReadFile(projectPath) + if err != nil { + log.Err(err).Str("file", "project").Str("projectFile", projectFile).Msg("Unable to read the project file") + return fmt.Errorf("unable to read the project file: %v", err) + } + log.Trace().Str("file", "project").Str("projectPath", projectPath).Msg("project file read") + + // Import project data structure + projectInfo := ProjectInfo{} + err = yaml.Unmarshal(content, &projectInfo) + if err != nil { + log.Err(err).Str("file", "project").Str("projectFile", projectFile).Msg("Unable to get the project information") + return fmt.Errorf("unable to get the project information: %v", err) + } + log.Trace().Str("file", "project").Str("projectPath", projectPath).Msg("project information got") + + // The project is saved + a.projectSave = projectFile + + return a.OpenProject(projectInfo) +} + +// OpenProject opens a project based on its information +func (a *App) OpenProject(projectInfo ProjectInfo) error { + // Close the current project + err := a.CloseCurrentProject() + if err != nil { + return fmt.Errorf("unable to close project: %w", err) + } + + // Open the project + a.projectInfo = projectInfo + + // Send an event with the project data + runtime.EventsEmit(a.ctx, "LOAD_PROJECT", projectInfo.ShowInfo) + + // Load all peripherals of the project + projectPeripherals := a.projectInfo.PeripheralsInfo + for key, value := range projectPeripherals { + hostFinder, err := a.hardwareManager.GetFinder(value.ProtocolName) + if err != nil { + return fmt.Errorf("unable to find the finder '%s': %w", value.ProtocolName, err) + } + _, err = hostFinder.RegisterPeripheral(a.ctx, value) + if err != nil { + return fmt.Errorf("unable to register the peripheral S/N '%s'", key) + } + } + return nil +} + +// CloseCurrentProject closes the current project +func (a *App) CloseCurrentProject() error { + // Unregistrer all peripherals of the project + projectPeripherals := a.projectInfo.PeripheralsInfo + for key, value := range projectPeripherals { + hostFinder, err := a.hardwareManager.GetFinder(value.ProtocolName) + if err != nil { + return fmt.Errorf("unable to find the finder '%s': %w", value.ProtocolName, err) + } + err = hostFinder.UnregisterPeripheral(a.ctx, key) + if err != nil { + return fmt.Errorf("unable to unregister the peripheral S/N '%s': %w", key, err) + } + } + + // Unload project info in the front + return nil +} + +// ChooseAvatarPath opens a filedialog to choose the show avatar +func (a *App) ChooseAvatarPath() (string, error) { + // Open the file dialog box + filePath, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "Choose your show avatar", + Filters: []runtime.FileFilter{ + { + DisplayName: "Images", + Pattern: "*.png;*.jpg;*.jpeg", + }, + }, + }) + if err != nil { + log.Err(err).Str("file", "project").Msg("unable to open the avatar dialog") + return "", err + } + log.Debug().Str("file", "project").Msg("avatar dialog is opened") + // Copy the avatar to the application avatars path + avatarPath := filepath.Join(avatarsDirectory, filepath.Base(filePath)) + log.Trace().Str("file", "project").Str("avatarPath", avatarPath).Msg("avatar path is created") + _, err = copy(filePath, avatarPath) + if err != nil { + log.Err(err).Str("file", "project").Str("avatarsDirectory", avatarsDirectory).Str("fileBase", filepath.Base(filePath)).Msg("unable to copy the avatar file") + return "", err + } + log.Info().Str("file", "project").Str("avatarFileName", filepath.Base(filePath)).Msg("got the new avatar file") + return filepath.Base(filePath), nil +} + +// UpdateShowInfo updates the show information +func (a *App) UpdateShowInfo(showInfo ShowInfo) { + a.projectInfo.ShowInfo = showInfo + log.Info().Str("file", "project").Any("showInfo", showInfo).Msg("show information was updated") +} + +// SaveProject saves the project +func (a *App) SaveProject() (string, error) { + // If there is no save file, create a new one with the show name + if a.projectSave == "" { + date := time.Now() + a.projectSave = fmt.Sprintf("%04d%02d%02d%02d%02d%02d%s", date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), projectExtension) + log.Debug().Str("file", "project").Str("newProjectSave", a.projectSave).Msg("projectSave is null, getting a new one") + } + data, err := yaml.Marshal(a.projectInfo) + if err != nil { + log.Err(err).Str("file", "project").Any("projectInfo", a.projectInfo).Msg("unable to format the project information") + return "", err + } + log.Trace().Str("file", "project").Any("projectInfo", a.projectInfo).Msg("projectInfo has been marshalled") + // Create the project directory if not exists + err = os.MkdirAll(projectsDirectory, os.ModePerm) + if err != nil { + log.Err(err).Str("file", "project").Str("projectsDirectory", projectsDirectory).Msg("unable to create the projects directory") + return "", err + } + log.Trace().Str("file", "project").Str("projectsDirectory", projectsDirectory).Msg("projects directory has been created") + + err = os.WriteFile(filepath.Join(projectsDirectory, a.projectSave), data, os.ModePerm) + if err != nil { + log.Err(err).Str("file", "project").Str("projectsDirectory", projectsDirectory).Str("projectSave", a.projectSave).Msg("unable to save the project") + return "", err + } + log.Info().Str("file", "project").Str("projectFileName", a.projectSave).Msg("project has been saved") + return a.projectSave, nil +} + +// ShowInfo defines the information of the show +type ShowInfo struct { + Name string `yaml:"name"` + Date string `yaml:"date"` + Avatar string `yaml:"avatar"` + Comments string `yaml:"comments"` +} + +// ProjectMetaData defines all the minimum information for a lighting project +type ProjectMetaData struct { + Name string // Show name + Save string // The save file of the project +} + +// ProjectInfo defines all the information for a lighting project +type ProjectInfo struct { + ShowInfo ShowInfo `yaml:"show"` // Show information + PeripheralsInfo map[string]hardware.PeripheralInfo `yaml:"peripherals"` // Peripherals information +}