corrected events

This commit is contained in:
2025-11-10 14:18:22 +01:00
parent 9e8cbed73f
commit f8b39d90de
10 changed files with 620 additions and 164 deletions

2
.gitignore vendored
View File

@@ -9,4 +9,6 @@ frontend/wailsjs
*.exe
*.o
*.rc
*.dll
*.dll.a
frontend/public

1
app.go
View File

@@ -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

View File

@@ -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") + ' <b>' + peripheralInfo.Name + '</b>')
})
// 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") + ' <b>' + peripheralInfo.Name + '</b>')
}
// 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"))
})
}
</script>
<header>
<!-- Project buttons -->
<RoundIconButton on:mouseup={initializeNewProject} icon='bxs-plus-square' width="2.5em" tooltip={$_("newProjectTooltip")}/>
<RoundDropdownList on:click={loadProjectsList} on:selected={openSelectedProject} choices={choices} icon='bxs-folder-open' width="2.5em" tooltip={$_("openProjectTooltip")}/>
<NavigationBar on:navigationChanged="{onNavigationChanged}"/>
{#if $needProjectSave}
<RoundIconButton on:mouseup={saveProject} icon="bx-save" width="2.5em" tooltip={$_("saveButtonTooltip")}></RoundIconButton>
<RoundIconButton on:mouseup={saveProject} icon="bx-save" width="2.5em" tooltip={$_("saveButtonTooltip")}/>
{/if}
<Clock/>
</header>

View File

@@ -0,0 +1,153 @@
<!-- Create a round icon button -->
<script>
import { createEventDispatcher, onDestroy } from 'svelte';
import {colors} from '../../stores.js';
import Tooltip from './Tooltip.svelte';
import { _ } from 'svelte-i18n'
export let icon = "bxs-heart" // The icon wanted
export let width = "10em" // The button width
export let active = false // If the button is active or not
export let tooltip = "Default tooltip" // The description shown in the tooltip
export let operationalStatus = undefined// The optional button status
export let okStatusLabel = "" // The label shown when the button is OK
export let nokStatusLabel = "" // The label shown when the button is NOK
export let choices = new Map()
export let style = '';
let tooltipMessage = tooltip
// Default values for background and foreground
$: background = $colors.first
$: foreground = $colors.first
// Change the background when the selected prop changed
$: {
if (active === true) {
background = $colors.third
foreground = $colors.fourth
} else {
background = $colors.fourth
foreground = $colors.second
}
}
// Show the operational status if specified
// undefined => no status displayed
// operationalStatus = true => OK color displayed
// operationalStatus = false => NOK color displayed
$: statusColor = $colors.nok
$: {
if (operationalStatus === true){
statusColor = $colors.ok
tooltipMessage = tooltip + " " + okStatusLabel
} else {
statusColor = $colors.nok
tooltipMessage = tooltip + " " + nokStatusLabel
}
}
// Emit a click event when the button is clicked
const dispatch = createEventDispatcher();
function handleclick(key){
// Deactivate the list visibility
hideList()
dispatch('selected', key)
}
// Show the option list
let listShowing = false
function toggleList(){
if (!listShowing) {
dispatch('click')
}
listShowing = !listShowing
}
function hideList(){
listShowing = false
}
let tooltipPosition = {top: 0, left: 0}
// Show a tooltip on mouse hover
let tooltipShowing = false
let buttonRef
function toggleTooltip(active){
const rect = buttonRef.getBoundingClientRect();
tooltipPosition = {
top: rect.bottom + 5, // Ajouter une marge en bas
left: rect.left, // Centrer horizontalement
};
tooltipShowing = active
}
</script>
<div class="container">
<button bind:this={buttonRef}
style="width:{width}; height:{width}; border-radius:{width}; background-color:{background}; color:{foreground};"
on:mouseenter={() => { toggleTooltip(true) }}
on:mouseleave={() => { toggleTooltip(false) }}
on:click={toggleList}>
<i class='bx {icon}' style="font-size:100%;"></i>
</button>
<!-- Showing the badge status if the button has an operational status -->
{#if (operationalStatus !== undefined)}
<div class="badge"
style="width: calc({width} / 3); height: calc({width} / 3); border-radius: calc({width}); background-color:{statusColor}; display:block;">
</div>
{/if}
<Tooltip message={tooltipMessage} show={tooltipShowing} position={tooltipPosition}></Tooltip>
<div class="list" style="color: {$colors.white}; display: {listShowing ? "block" : "none"};"
on:mouseleave={hideList}>
{#each Array.from(choices) as [key, value]}
<div class="item" on:click={() => handleclick({key})}>{value}</div>
{/each}
</div>
</div>
<style>
.item{
border-radius: 0.3em;
padding: 0.3em;
}
.item:hover {
background-color: var(--second-color);
color: var(--white-color);
}
.container {
position: relative;
display: inline-block;
}
.list {
z-index: 200;
padding: 0.2em;
backdrop-filter: blur(20px);
margin-top: 0.2em;
position: absolute;
width: auto;
cursor: pointer;
border-radius: 0.5em;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 30em;
max-height: 40vh;
overflow-y: scroll;
scrollbar-width: none;
}
button{
display: inline-block;
margin: 0;
border:none;
cursor: pointer;
}
button:hover{
box-shadow: 1px 1px 3px 0px rgba(0, 0, 0, 0.25) inset;
}
</style>

View File

@@ -1,109 +1,16 @@
<script lang=ts>
import { generateToast, projectsList, showInformation, needProjectSave, peripherals } from '../../stores.js';
import RoundedButton from "../General/RoundedButton.svelte";
import ProjectPropertiesContent from "./ProjectPropertiesContent.svelte";
import DropdownList from "../General/DropdownList.svelte";
import InputsOutputsContent from "./InputsOutputsContent.svelte";
import Tab from "../General/Tab.svelte";
import { CreateProject, GetProjects, OpenProjectFromDisk } from "../../../wailsjs/go/main/App";
import { _ } from 'svelte-i18n'
import {colors} from '../../stores.js';
import { get } from "svelte/store"
import { EventsOn } from '../../../wailsjs/runtime'
const tabs = [
{ title: $_("projectPropertiesTab"), icon: 'bxs-info-circle', tooltip: $_("projectPropertiesTooltip"), component: ProjectPropertiesContent },
{ title: $_("projectInputOutputTab"), icon: 'bxs-plug', tooltip: $_("projectInputOutputTooltip"), component: InputsOutputsContent },
];
// 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"))
})
}
// Unsave peripherals from the store and remove the disconnected peripherals
function unsavePeripherals(){
peripherals.update((storedPeripherals) => {
// Set all the isSaved keys to false and delete the disconnected peripherals
for (let peripheralID in storedPeripherals) {
storedPeripherals[peripheralID].isSaved = false
if (!storedPeripherals[peripheralID].isDetected) {
delete storedPeripherals[peripheralID]
}
}
return {...storedPeripherals}
})
}
// Load the saved peripherals into the store
function loadPeripherals(peripheralsInfo){
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
for (let peripheralID in peripheralsInfo){
// Add the peripheral to the list of peripherals, with the last isDetected key and the isSaved key to true
let lastDetectedKey = storedPeripherals[peripheralID]?.isDetected
storedPeripherals[peripheralID] = peripheralsInfo[peripheralID]
storedPeripherals[peripheralID].isDetected = (lastDetectedKey === true) ? true : false
storedPeripherals[peripheralID].isSaved = true
}
return {...storedPeripherals}
})
}
// Handle the event when a new project need to be loaded
EventsOn('LOAD_PROJECT', function(projectInfo){
// Store project information
showInformation.set(projectInfo.ShowInfo)
// Remove the saved peripherals of the current project
unsavePeripherals()
// Load new project peripherals
loadPeripherals(projectInfo.PeripheralsInfo)
console.log("Project has been opened");
generateToast('info', 'bx-folder-open', $_("projectOpenedToast") + ' <b>' + projectInfo.ShowInfo.Name + '</b>')
})
// 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"))
})
}
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"))
})
}
// Instantiate a new project
initializeNewProject()
</script>
<!-- Project buttons -->
<RoundedButton on:click={initializeNewProject} text={$_("newProjectString")} icon='bxs-plus-square' tooltip={$_("newProjectTooltip")}/>
<DropdownList icon='bxs-folder-open' text={$_("openProjectString")} choices={choices} tooltip={$_("openProjectTooltip")} on:click={loadProjectsList} on:selected={openSelectedProject}/>
<!-- Project tabcontrol -->
<Tab { tabs } maxHeight='73vh'/>

View File

@@ -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';

View File

@@ -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") + ' <b>' + peripheralInfo.Name + '</b>')
}
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") + ' <b>' + peripheralInfo.Name + '</b>')
}
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") + ' <b>' + showInfo.Name + '</b>')
}
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')
}

View File

@@ -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",

View File

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

248
project.go Normal file
View File

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