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
+}