generated from thinkode/modelRepository
24-project-life (#28)
Fix peripherals states and implements plug and reload feature. Graphics improvements. Stability improvements. Reviewed-on: #28
This commit was merged in pull request #28.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,4 +9,6 @@ frontend/wailsjs
|
||||
*.exe
|
||||
*.o
|
||||
*.rc
|
||||
*.dll
|
||||
*.dll.a
|
||||
frontend/public
|
||||
1
app.go
1
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
|
||||
|
||||
@@ -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>
|
||||
|
||||
152
frontend/src/components/General/RoundDropdownList.svelte
Normal file
152
frontend/src/components/General/RoundDropdownList.svelte
Normal file
@@ -0,0 +1,152 @@
|
||||
<!-- 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"}; border: 2px solid {$colors.second}; background-color: {$colors.first};"
|
||||
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;
|
||||
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>
|
||||
@@ -15,13 +15,14 @@
|
||||
export let signalizable = false;
|
||||
export let signalized = false;
|
||||
export let selected = false;
|
||||
export let status = "disconnected";
|
||||
export let status = "PERIPHERAL_DISCONNECTED";
|
||||
|
||||
// Emit a delete event when the device is being removed
|
||||
const dispatch = createEventDispatcher();
|
||||
function remove(event){
|
||||
dispatch('delete')
|
||||
}
|
||||
|
||||
function add(event){
|
||||
dispatch('add')
|
||||
}
|
||||
@@ -37,11 +38,11 @@
|
||||
</script>
|
||||
|
||||
<div class="card" on:dblclick={dblclick}>
|
||||
<div class="{selected ? "selected" : "unselected"} {status == "connecting" ? "waiting" : ""}" on:mousedown={click} style="color: {(status == "disconnected") ? $colors.first : $colors.white};">
|
||||
<div class="{selected ? "selected" : "unselected"} {status == "PERIPHERAL_CONNECTING" ? "waiting" : ""}" on:mousedown={click} style="color: {(status == "PERIPHERAL_DISCONNECTED") ? $colors.first : $colors.white};">
|
||||
<div style="z-index: 1;">
|
||||
<p>{#if status == "disconnected" }<i class='bx bx-no-signal' style="font-size:100%; color: var(--nok-color);"></i> {/if}{title}</p>
|
||||
<p>{#if status == "PERIPHERAL_DISCONNECTED" }<i class='bx bx-no-signal' style="font-size:100%; color: var(--nok-color);"></i> {/if}{title}</p>
|
||||
<h6 class="subtitle">{type} {location != '' ? "- " : ""}<i>{location}</i></h6>
|
||||
{#if status == "disconnected"}
|
||||
{#if status == "PERIPHERAL_DISCONNECTED"}
|
||||
<h6><b>Disconnected</b></h6>
|
||||
{:else}
|
||||
<h6>{line1}</h6>
|
||||
@@ -50,9 +51,9 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<InfoButton on:click={add} color="{(status == "disconnected") ? $colors.first : $colors.white}" style="margin: 0.2em; display: { addable ? 'flex' : 'none' }" icon='bxs-message-square-add' interactive message={$_("projectHardwareAddTooltip")}/>
|
||||
<InfoButton on:click={remove} color="{(status == "disconnected") ? $colors.first : $colors.white}" style="margin: 0.2em; display: { removable ? 'flex' : 'none' }" icon='bx-trash' interactive message={$_("projectHardwareDeleteTooltip")}/>
|
||||
<InfoButton style="margin: 0.2em; display: { (status == "activated" || status == "deactivated") ? 'flex' : 'none' }" background={ (status == "activated") ? $colors.ok : (status == "deactivated") ? $colors.nok : null} icon='bx-pulse' hide={!signalizable}/>
|
||||
<InfoButton on:click={add} color="{(status == "PERIPHERAL_DISCONNECTED") ? $colors.first : $colors.white}" style="margin: 0.2em; display: { addable ? 'flex' : 'none' }" icon='bxs-message-square-add' interactive message={$_("projectHardwareAddTooltip")}/>
|
||||
<InfoButton on:click={remove} color="{(status == "PERIPHERAL_DISCONNECTED") ? $colors.first : $colors.white}" style="margin: 0.2em; display: { removable ? 'flex' : 'none' }" icon='bx-trash' interactive message={$_("projectHardwareDeleteTooltip")}/>
|
||||
<InfoButton style="margin: 0.2em; display: { (status == "PERIPHERAL_ACTIVATED" || status == "PERIPHERAL_DEACTIVATED") ? 'flex' : 'none' }" background={ (status == "PERIPHERAL_ACTIVATED") ? $colors.ok : (status == "PERIPHERAL_DEACTIVATED") ? $colors.nok : null} icon='bx-pulse' hide={!signalizable}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,20 +11,8 @@
|
||||
// Add the peripheral to the project
|
||||
function addPeripheral(peripheral){
|
||||
// Add the peripheral to the project (backend)
|
||||
AddPeripheral(peripheral).then((serialNumber) => {
|
||||
peripherals.update((storedPeripherals) => {
|
||||
return {
|
||||
...storedPeripherals,
|
||||
[serialNumber]: {
|
||||
...storedPeripherals[serialNumber],
|
||||
Name: peripheral.Name,
|
||||
ProtocolName: peripheral.ProtocolName,
|
||||
SerialNumber: serialNumber,
|
||||
isSaved: true,
|
||||
},
|
||||
}})
|
||||
$needProjectSave = true
|
||||
}).catch((error) => {
|
||||
AddPeripheral(peripheral)
|
||||
.catch((error) => {
|
||||
console.log("Unable to add the peripheral to the project: " + error)
|
||||
generateToast('danger', 'bx-error', $_("addPeripheralErrorToast"))
|
||||
})
|
||||
@@ -33,27 +21,8 @@
|
||||
// Remove the peripheral from the project
|
||||
function removePeripheral(peripheral) {
|
||||
// Delete the peripheral from the project (backend)
|
||||
RemovePeripheral(peripheral.ProtocolName, peripheral.SerialNumber).then(() => {
|
||||
// If the peripheral is not detected, we can delete it form the store
|
||||
// If not, we only pass the isSaved key to false
|
||||
let peripheralsList = get(peripherals)
|
||||
let lastDetectedProperty = peripheralsList[peripheral.SerialNumber]?.isDetected
|
||||
let needToDelete = (lastDetectedProperty !== true) ? true : false
|
||||
peripherals.update((storedPeripherals) => {
|
||||
if (needToDelete){
|
||||
delete storedPeripherals[peripheral.SerialNumber];
|
||||
return { ...storedPeripherals };
|
||||
}
|
||||
storedPeripherals[peripheral.SerialNumber].isSaved = false
|
||||
return { ...storedPeripherals };
|
||||
})
|
||||
$needProjectSave = true
|
||||
// If the peripheral is currently selected, unselect it
|
||||
if (selectedPeripheralSN == peripheral.SerialNumber) {
|
||||
selectedPeripheralSN = null
|
||||
selectedPeripheralSettings = {}
|
||||
}
|
||||
}).catch((error) => {
|
||||
RemovePeripheral(peripheral)
|
||||
.catch((error) => {
|
||||
console.log("Unable to remove the peripheral from the project: " + error)
|
||||
generateToast('danger', 'bx-error', $_("removePeripheralErrorToast"))
|
||||
})
|
||||
@@ -76,7 +45,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Unselect the peripheral if it is disconnect
|
||||
// Unselect the peripheral if it is disconnected
|
||||
$: {
|
||||
Object.entries($peripherals).filter(([serialNumber, peripheral]) => {
|
||||
if (!peripheral.isDetected && peripheral.isSaved && selectedPeripheralSN == serialNumber) {
|
||||
@@ -120,7 +89,7 @@
|
||||
if(!peripheral.isSaved)
|
||||
addPeripheral(peripheral)
|
||||
}}
|
||||
status="connected" title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/>
|
||||
status="PERIPHERAL_CONNECTED" title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={"S/N: " + peripheral.SerialNumber} addable={!peripheral.isSaved}/>
|
||||
{/if}
|
||||
{/each}
|
||||
<p style="color: var(--first-color);"><i class='bx bxs-network-chart' ></i> {$_("projectHardwareOthersLabel")}</p>
|
||||
@@ -134,7 +103,7 @@
|
||||
{#if savedPeripheralNumber > 0}
|
||||
{#each Object.entries($peripherals) as [serialNumber, peripheral]}
|
||||
{#if peripheral.isSaved}
|
||||
<DeviceCard status="{peripheral.Status}" on:delete={() => removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)} on:click={() => selectPeripheral(peripheral)}
|
||||
<DeviceCard status="{peripheral.status}" on:delete={() => removePeripheral(peripheral)} on:dblclick={() => removePeripheral(peripheral)} on:click={() => selectPeripheral(peripheral)}
|
||||
title={peripheral.Name} type={peripheral.ProtocolName} location={peripheral.Location ? peripheral.Location : ""} line1={peripheral.SerialNumber ? "S/N: " + peripheral.SerialNumber : ""} selected={serialNumber == selectedPeripheralSN} removable signalizable/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -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'/>
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
169
frontend/src/runtime-events.js
Normal file
169
frontend/src/runtime-events.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import { EventsOn, EventsOff } from "../wailsjs/runtime/runtime.js"
|
||||
import { peripherals, generateToast, needProjectSave, showInformation } from './stores'
|
||||
import { get } from "svelte/store"
|
||||
import { _ } from 'svelte-i18n'
|
||||
|
||||
// New peripheral has been added to the system
|
||||
function peripheralArrival (peripheralInfo){
|
||||
// If not exists, add it to the map
|
||||
// isDetected key to true
|
||||
|
||||
peripherals.update((storedPeripherals) => {
|
||||
return {
|
||||
...storedPeripherals,
|
||||
[peripheralInfo.SerialNumber]: {
|
||||
...storedPeripherals[peripheralInfo.SerialNumber],
|
||||
Name: peripheralInfo.Name,
|
||||
ProtocolName: peripheralInfo.ProtocolName,
|
||||
SerialNumber: peripheralInfo.SerialNumber,
|
||||
Settings: peripheralInfo.Settings,
|
||||
isDetected: true,
|
||||
},
|
||||
}})
|
||||
console.log("Hardware has been added to the system");
|
||||
generateToast('info', 'bxs-hdd', get(_)("peripheralArrivalToast") + ' <b>' + peripheralInfo.Name + '</b>')
|
||||
}
|
||||
|
||||
// Peripheral is removed from the system
|
||||
function peripheralRemoval (peripheralInfo){
|
||||
// If not exists, add it to the map
|
||||
// isDetected key to false
|
||||
|
||||
peripherals.update((storedPeripherals) => {
|
||||
return {
|
||||
...storedPeripherals,
|
||||
[peripheralInfo.SerialNumber]: {
|
||||
...storedPeripherals[peripheralInfo.SerialNumber],
|
||||
Name: peripheralInfo.Name,
|
||||
ProtocolName: peripheralInfo.ProtocolName,
|
||||
SerialNumber: peripheralInfo.SerialNumber,
|
||||
Settings: peripheralInfo.Settings,
|
||||
isDetected: false,
|
||||
status: "PERIPHERAL_DISCONNECTED",
|
||||
},
|
||||
}})
|
||||
console.log("Hardware has been removed from the system");
|
||||
generateToast('warning', 'bxs-hdd', get(_)("peripheralRemovalToast") + ' <b>' + peripheralInfo.Name + '</b>')
|
||||
}
|
||||
|
||||
// Update peripheral status
|
||||
function peripheralUpdateStatus(peripheralInfo, status){
|
||||
// If not exists, add it to the map
|
||||
// change status key
|
||||
|
||||
peripherals.update((storedPeripherals) => {
|
||||
console.log(status)
|
||||
return {
|
||||
...storedPeripherals,
|
||||
[peripheralInfo.SerialNumber]: {
|
||||
...storedPeripherals[peripheralInfo.SerialNumber],
|
||||
Name: peripheralInfo.Name,
|
||||
ProtocolName: peripheralInfo.ProtocolName,
|
||||
SerialNumber: peripheralInfo.SerialNumber,
|
||||
Settings: peripheralInfo.Settings,
|
||||
status: status,
|
||||
},
|
||||
}})
|
||||
|
||||
console.log("Hardware status has been updated to " + status);
|
||||
}
|
||||
|
||||
// Load the peripheral in the project
|
||||
function loadPeripheral (peripheralInfo) {
|
||||
// If not exists, add it to the map
|
||||
// isSaved key to true
|
||||
|
||||
peripherals.update((storedPeripherals) => {
|
||||
return {
|
||||
...storedPeripherals,
|
||||
[peripheralInfo.SerialNumber]: {
|
||||
...storedPeripherals[peripheralInfo.SerialNumber],
|
||||
Name: peripheralInfo.Name,
|
||||
ProtocolName: peripheralInfo.ProtocolName,
|
||||
SerialNumber: peripheralInfo.SerialNumber,
|
||||
Settings: peripheralInfo.Settings,
|
||||
isSaved: true,
|
||||
},
|
||||
}})
|
||||
console.log("Hardware has been added to the project");
|
||||
//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>')
|
||||
}
|
||||
|
||||
// Unload the hardware from the project
|
||||
function unloadPeripheral (peripheralInfo) {
|
||||
// If not exists, add it to the map
|
||||
// isSaved key to false
|
||||
|
||||
peripherals.update((storedPeripherals) => {
|
||||
return {
|
||||
...storedPeripherals,
|
||||
[peripheralInfo.SerialNumber]: {
|
||||
...storedPeripherals[peripheralInfo.SerialNumber],
|
||||
Name: peripheralInfo.Name,
|
||||
ProtocolName: peripheralInfo.ProtocolName,
|
||||
SerialNumber: peripheralInfo.SerialNumber,
|
||||
Settings: peripheralInfo.Settings,
|
||||
isSaved: false,
|
||||
},
|
||||
}})
|
||||
console.log("Hardware has been removed from the project");
|
||||
//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', peripheralArrival)
|
||||
|
||||
// Handle the event when a peripheral is removed from the system
|
||||
EventsOn('PERIPHERAL_REMOVAL', peripheralRemoval)
|
||||
|
||||
// Handle the event when a peripheral status is updated
|
||||
EventsOn('PERIPHERAL_STATUS', peripheralUpdateStatus)
|
||||
|
||||
// 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')
|
||||
}
|
||||
@@ -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",
|
||||
@@ -35,4 +34,15 @@ export const secondSize = writable("14px")
|
||||
export const thirdSize = writable("20px")
|
||||
|
||||
// List of current hardware
|
||||
export let peripherals = writable({})
|
||||
export let peripherals = writable({})
|
||||
|
||||
// Peripheral structure :
|
||||
|
||||
// Name string `yaml:"name"` // Name of the peripheral
|
||||
// SerialNumber string `yaml:"sn"` // S/N of the peripheral
|
||||
// ProtocolName string `yaml:"protocol"` // Protocol name of the peripheral
|
||||
// Settings map[string]interface{} `yaml:"settings"` // Peripheral settings
|
||||
|
||||
// isSaved // if the peripheral is saved in the project
|
||||
// isDetected // if the peripheral is detected by the system
|
||||
// status // the status of connection
|
||||
@@ -2,8 +2,6 @@ package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
goRuntime "runtime"
|
||||
"sync"
|
||||
@@ -11,6 +9,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -23,65 +22,93 @@ import "C"
|
||||
// FTDIFinder manages all the FTDI peripherals
|
||||
type FTDIFinder struct {
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
|
||||
findTicker *time.Ticker // Peripherals find ticker
|
||||
foundPeripherals map[string]PeripheralInfo // The list of peripherals handled by this finder
|
||||
registeredPeripherals map[string]*FTDIPeripheral // The list of found peripherals
|
||||
scanChannel chan struct{} // The channel to trigger a scan event
|
||||
saved map[string]PeripheralInfo // Peripherals saved in the project
|
||||
detected map[string]*FTDIPeripheral // Detected peripherals
|
||||
|
||||
scanEvery time.Duration // Scans peripherals periodically
|
||||
|
||||
onArrival func(p PeripheralInfo) // When a peripheral arrives
|
||||
onRemoval func(p PeripheralInfo) // When a peripheral goes away
|
||||
}
|
||||
|
||||
// NewFTDIFinder creates a new FTDI finder
|
||||
func NewFTDIFinder(findPeriod time.Duration) *FTDIFinder {
|
||||
func NewFTDIFinder(scanEvery time.Duration) *FTDIFinder {
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder created")
|
||||
return &FTDIFinder{
|
||||
findTicker: time.NewTicker(findPeriod),
|
||||
foundPeripherals: make(map[string]PeripheralInfo),
|
||||
registeredPeripherals: make(map[string]*FTDIPeripheral),
|
||||
scanChannel: make(chan struct{}),
|
||||
scanEvery: scanEvery,
|
||||
saved: make(map[string]PeripheralInfo),
|
||||
detected: make(map[string]*FTDIPeripheral),
|
||||
}
|
||||
}
|
||||
|
||||
// OnArrival is the callback function when a new peripheral arrives
|
||||
func (f *FTDIFinder) OnArrival(cb func(p PeripheralInfo)) {
|
||||
f.onArrival = cb
|
||||
}
|
||||
|
||||
// OnRemoval i the callback when a peripheral goes away
|
||||
func (f *FTDIFinder) OnRemoval(cb func(p PeripheralInfo)) {
|
||||
f.onRemoval = cb
|
||||
}
|
||||
|
||||
// RegisterPeripheral registers a new peripheral
|
||||
func (f *FTDIFinder) RegisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) (string, error) {
|
||||
// Create a new FTDI peripheral
|
||||
ftdiPeripheral, err := NewFTDIPeripheral(peripheralData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create the FTDI peripheral: %v", err)
|
||||
}
|
||||
// Register it in the finder
|
||||
f.registeredPeripherals[peripheralData.SerialNumber] = ftdiPeripheral
|
||||
log.Trace().Any("periph", &ftdiPeripheral).Str("file", "FTDIFinder").Str("peripheralName", peripheralData.Name).Msg("FTDI peripheral has been created")
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
// Peripheral created, connect it
|
||||
err = ftdiPeripheral.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral")
|
||||
f.saved[peripheralData.SerialNumber] = peripheralData
|
||||
|
||||
// If already detected, connect it
|
||||
if peripheral, ok := f.detected[peripheralData.SerialNumber]; ok {
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusConnecting)
|
||||
err := peripheral.Connect(ctx)
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
|
||||
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to connect the peripheral")
|
||||
return peripheralData.SerialNumber, nil
|
||||
}
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated)
|
||||
// Peripheral connected, activate it
|
||||
err = peripheral.Activate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the FTDI peripheral")
|
||||
return peripheralData.SerialNumber, nil
|
||||
}
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusActivated)
|
||||
}
|
||||
// Peripheral connected, activate it
|
||||
err = ftdiPeripheral.Activate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIFinder").Str("peripheralSN", peripheralData.SerialNumber).Msg("unable to activate the peripheral")
|
||||
}
|
||||
// Peripheral activated
|
||||
|
||||
// Emits the event in the hardware
|
||||
runtime.EventsEmit(ctx, "LOAD_PERIPHERAL", peripheralData)
|
||||
|
||||
return peripheralData.SerialNumber, nil
|
||||
}
|
||||
|
||||
// UnregisterPeripheral unregisters an existing peripheral
|
||||
func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralID string) error {
|
||||
peripheral, registered := f.registeredPeripherals[peripheralID]
|
||||
if registered {
|
||||
func (f *FTDIFinder) UnregisterPeripheral(ctx context.Context, peripheralData PeripheralInfo) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
if peripheral, detected := f.detected[peripheralData.SerialNumber]; detected {
|
||||
// Deactivating peripheral
|
||||
err := peripheral.Deactivate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to deactivate the peripheral")
|
||||
return nil
|
||||
}
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated)
|
||||
// Disconnecting peripheral
|
||||
err = peripheral.Disconnect()
|
||||
if err != nil {
|
||||
return err
|
||||
log.Err(err).Str("sn", peripheralData.SerialNumber).Msg("unable to disconnect the peripheral")
|
||||
return nil
|
||||
}
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
|
||||
}
|
||||
delete(f.registeredPeripherals, peripheralID)
|
||||
delete(f.saved, peripheralData.SerialNumber)
|
||||
runtime.EventsEmit(ctx, "UNLOAD_PERIPHERAL", peripheralData)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,18 +127,15 @@ func (f *FTDIFinder) Initialize() error {
|
||||
func (f *FTDIFinder) Start(ctx context.Context) error {
|
||||
f.wg.Add(1)
|
||||
go func() {
|
||||
ticker := time.NewTicker(f.scanEvery)
|
||||
defer ticker.Stop()
|
||||
defer f.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-f.findTicker.C:
|
||||
// Scan the peripherals
|
||||
err := f.scanPeripherals(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "FTDIFinder").Msg("unable to scan FTDI peripherals")
|
||||
}
|
||||
case <-f.scanChannel:
|
||||
case <-ticker.C:
|
||||
// Scan the peripherals
|
||||
err := f.scanPeripherals(ctx)
|
||||
if err != nil {
|
||||
@@ -123,13 +147,13 @@ func (f *FTDIFinder) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceScan explicily asks for scanning peripherals
|
||||
// ForceScan explicitly asks for scanning peripherals
|
||||
func (f *FTDIFinder) ForceScan() {
|
||||
select {
|
||||
case f.scanChannel <- struct{}{}:
|
||||
default:
|
||||
// Ignore if the channel is full or if it is closed
|
||||
}
|
||||
// select {
|
||||
// case f.scanChannel <- struct{}{}:
|
||||
// default:
|
||||
// // Ignore if the channel is full or if it is closed
|
||||
// }
|
||||
}
|
||||
|
||||
// GetName returns the name of the driver
|
||||
@@ -140,25 +164,27 @@ func (f *FTDIFinder) GetName() string {
|
||||
// GetPeripheralSettings gets the peripheral settings
|
||||
func (f *FTDIFinder) GetPeripheralSettings(peripheralID string) (map[string]interface{}, error) {
|
||||
// Return the specified peripheral
|
||||
peripheral, found := f.registeredPeripherals[peripheralID]
|
||||
if !found {
|
||||
log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
|
||||
return nil, fmt.Errorf("unable to found the peripheral")
|
||||
}
|
||||
log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
|
||||
return peripheral.GetSettings(), nil
|
||||
// peripheral, found := f.registeredPeripherals[peripheralID]
|
||||
// if !found {
|
||||
// log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
|
||||
// return nil, fmt.Errorf("unable to found the peripheral")
|
||||
// }
|
||||
// log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
|
||||
// return peripheral.GetSettings(), nil
|
||||
return make(map[string]interface{}), nil
|
||||
}
|
||||
|
||||
// SetPeripheralSettings sets the peripheral settings
|
||||
func (f *FTDIFinder) SetPeripheralSettings(peripheralID string, settings map[string]interface{}) error {
|
||||
// Return the specified peripheral
|
||||
peripheral, found := f.registeredPeripherals[peripheralID]
|
||||
if !found {
|
||||
log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
|
||||
return fmt.Errorf("unable to found the peripheral")
|
||||
}
|
||||
log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
|
||||
return peripheral.SetSettings(settings)
|
||||
// peripheral, found := f.registeredPeripherals[peripheralID]
|
||||
// if !found {
|
||||
// log.Error().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("unable to get this peripheral from the FTDI finder")
|
||||
// return fmt.Errorf("unable to found the peripheral")
|
||||
// }
|
||||
// log.Debug().Str("file", "FTDIFinder").Str("peripheralID", peripheralID).Msg("peripheral found by the FTDI finder")
|
||||
// return peripheral.SetSettings(settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
// scanPeripherals scans the FTDI peripherals
|
||||
@@ -178,19 +204,19 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
|
||||
|
||||
C.get_ftdi_devices((*C.FTDIPeripheralC)(devicesPtr), C.int(count))
|
||||
|
||||
temporaryPeripherals := make(map[string]PeripheralInfo)
|
||||
currentMap := make(map[string]PeripheralInfo)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
d := devices[i]
|
||||
|
||||
sn := C.GoString(d.serialNumber)
|
||||
desc := C.GoString(d.description)
|
||||
isOpen := d.isOpen != 0
|
||||
// isOpen := d.isOpen != 0
|
||||
|
||||
temporaryPeripherals[sn] = PeripheralInfo{
|
||||
currentMap[sn] = PeripheralInfo{
|
||||
SerialNumber: sn,
|
||||
Name: desc,
|
||||
IsOpen: isOpen,
|
||||
// IsOpen: isOpen,
|
||||
ProtocolName: "FTDI",
|
||||
}
|
||||
|
||||
@@ -198,12 +224,63 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
|
||||
C.free_ftdi_device(&d)
|
||||
}
|
||||
|
||||
log.Info().Any("peripherals", temporaryPeripherals).Msg("available FTDI peripherals")
|
||||
log.Info().Any("peripherals", currentMap).Msg("available FTDI peripherals")
|
||||
|
||||
// Emit the peripherals changes to the front
|
||||
emitPeripheralsChanges(ctx, f.foundPeripherals, temporaryPeripherals)
|
||||
// Store the new peripherals list
|
||||
f.foundPeripherals = temporaryPeripherals
|
||||
// Detect arrivals
|
||||
for sn, peripheralData := range currentMap {
|
||||
if _, known := f.detected[sn]; !known {
|
||||
peripheral := NewFTDIPeripheral(peripheralData)
|
||||
f.detected[sn] = peripheral
|
||||
|
||||
if f.onArrival != nil {
|
||||
go f.onArrival(peripheralData)
|
||||
}
|
||||
log.Info().Str("sn", sn).Str("name", peripheralData.Name).Msg("[FTDI] New peripheral detected")
|
||||
|
||||
// Disconnected by default
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
|
||||
|
||||
// If the peripheral is saved in the project => connect
|
||||
if _, saved := f.saved[sn]; saved {
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusConnecting)
|
||||
err := peripheral.Connect(ctx)
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDisconnected)
|
||||
log.Err(err).Str("sn", sn).Msg("unable to connect the FTDI peripheral")
|
||||
return nil
|
||||
}
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusDeactivated)
|
||||
err = peripheral.Activate(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", sn).Msg("unable to activate the FTDI peripheral")
|
||||
return nil
|
||||
}
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), peripheralData, PeripheralStatusActivated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect removals
|
||||
for sn, oldPeripheral := range f.detected {
|
||||
if _, still := currentMap[sn]; !still {
|
||||
|
||||
// Properly clean the DMX device
|
||||
err := oldPeripheral.Disconnect()
|
||||
if err != nil {
|
||||
log.Err(err).Str("sn", sn).Msg("unable to clean the FTDI peripheral after disconnection")
|
||||
}
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), oldPeripheral.GetInfo(), PeripheralStatusDisconnected)
|
||||
|
||||
// Delete it from the detected list
|
||||
delete(f.detected, sn)
|
||||
log.Info().Str("sn", sn).Str("name", oldPeripheral.GetInfo().Name).Msg("[FTDI] peripheral removed")
|
||||
|
||||
// Execute the removal callback
|
||||
if f.onRemoval != nil {
|
||||
go f.onRemoval(oldPeripheral.GetInfo())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -211,29 +288,26 @@ func (f *FTDIFinder) scanPeripherals(ctx context.Context) error {
|
||||
func (f *FTDIFinder) WaitStop() error {
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("stopping the FTDI finder...")
|
||||
|
||||
// Stop the ticker
|
||||
f.findTicker.Stop()
|
||||
|
||||
// Close the channel
|
||||
close(f.scanChannel)
|
||||
// close(f.scanChannel)
|
||||
|
||||
// Wait for all the peripherals to close
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("closing all FTDI peripherals")
|
||||
var errs []error
|
||||
for registeredPeripheralSN, registeredPeripheral := range f.registeredPeripherals {
|
||||
err := registeredPeripheral.WaitStop()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
|
||||
}
|
||||
}
|
||||
// log.Trace().Str("file", "FTDIFinder").Msg("closing all FTDI peripherals")
|
||||
// var errs []error
|
||||
// for registeredPeripheralSN, registeredPeripheral := range f.registeredPeripherals {
|
||||
// err := registeredPeripheral.WaitStop()
|
||||
// if err != nil {
|
||||
// errs = append(errs, fmt.Errorf("%s: %w", registeredPeripheralSN, err))
|
||||
// }
|
||||
// }
|
||||
|
||||
// Wait for goroutines to stop
|
||||
f.wg.Wait()
|
||||
|
||||
// Returning errors
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
// if len(errs) > 0 {
|
||||
// return errors.Join(errs...)
|
||||
// }
|
||||
log.Trace().Str("file", "FTDIFinder").Msg("FTDI finder stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package hardware
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"sync"
|
||||
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -28,12 +26,12 @@ type FTDIPeripheral struct {
|
||||
}
|
||||
|
||||
// NewFTDIPeripheral creates a new FTDI peripheral
|
||||
func NewFTDIPeripheral(info PeripheralInfo) (*FTDIPeripheral, error) {
|
||||
func NewFTDIPeripheral(info PeripheralInfo) *FTDIPeripheral {
|
||||
log.Info().Str("file", "FTDIPeripheral").Str("name", info.Name).Str("s/n", info.SerialNumber).Msg("FTDI peripheral created")
|
||||
return &FTDIPeripheral{
|
||||
info: info,
|
||||
dmxSender: nil,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Connect connects the FTDI peripheral
|
||||
@@ -47,12 +45,11 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error {
|
||||
p.dmxSender = C.dmx_create()
|
||||
|
||||
// Connect the FTDI
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "connecting")
|
||||
serialNumber := C.CString(p.info.SerialNumber)
|
||||
defer C.free(unsafe.Pointer(serialNumber))
|
||||
if C.dmx_connect(p.dmxSender, serialNumber) != C.DMX_OK {
|
||||
log.Error().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("unable to connect the DMX device")
|
||||
return errors.Errorf("unable to connect '%s'")
|
||||
return errors.Errorf("unable to connect '%s'", p.info.SerialNumber)
|
||||
}
|
||||
|
||||
p.wg.Add(1)
|
||||
@@ -62,10 +59,7 @@ func (p *FTDIPeripheral) Connect(ctx context.Context) error {
|
||||
_ = p.Disconnect()
|
||||
}()
|
||||
|
||||
// Send deactivated state (connected but not activated for now)
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "deactivated")
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device connected successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -78,6 +72,9 @@ func (p *FTDIPeripheral) Disconnect() error {
|
||||
|
||||
// Destroy the dmx sender
|
||||
C.dmx_destroy(p.dmxSender)
|
||||
|
||||
// Reset the pointer to the peripheral
|
||||
p.dmxSender = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -95,13 +92,11 @@ func (p *FTDIPeripheral) Activate(ctx context.Context) error {
|
||||
return errors.Errorf("unable to activate the DMX sender!")
|
||||
}
|
||||
|
||||
// Test only
|
||||
C.dmx_setValue(p.dmxSender, C.uint16_t(1), C.uint8_t(255))
|
||||
C.dmx_setValue(p.dmxSender, C.uint16_t(5), C.uint8_t(255))
|
||||
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "activated")
|
||||
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device activated successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -120,7 +115,6 @@ func (p *FTDIPeripheral) Deactivate(ctx context.Context) error {
|
||||
}
|
||||
|
||||
log.Trace().Str("file", "FTDIPeripheral").Str("s/n", p.info.SerialNumber).Msg("DMX device deactivated successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ func NewOS2LPeripheral(peripheralData PeripheralInfo) (*OS2LPeripheral, error) {
|
||||
func (p *OS2LPeripheral) Connect(ctx context.Context) error {
|
||||
log.Info().Str("file", "OS2LPeripheral").Msg("OS2L peripheral connected")
|
||||
go func() {
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "connecting")
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.info, "connecting")
|
||||
time.Sleep(5 * time.Second)
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatus), p.info, "disconnected")
|
||||
runtime.EventsEmit(ctx, string(PeripheralStatusUpdated), p.info, "disconnected")
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,21 +7,30 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// PeripheralEvent is trigger by the finders when the scan is complete
|
||||
type PeripheralEvent string
|
||||
|
||||
// PeripheralStatus is the peripheral status (DISCONNECTED => CONNECTING => DEACTIVATED => ACTIVATED)
|
||||
type PeripheralStatus string
|
||||
|
||||
const (
|
||||
// PeripheralArrival is triggerd when a peripheral has been connected to the system
|
||||
PeripheralArrival PeripheralEvent = "PERIPHERAL_ARRIVAL"
|
||||
// PeripheralRemoval is triggered when a peripheral has been disconnected from the system
|
||||
PeripheralRemoval PeripheralEvent = "PERIPHERAL_REMOVAL"
|
||||
// PeripheralStatus is triggered when a peripheral status has been updated (disconnected - connecting - connected)
|
||||
PeripheralStatus PeripheralEvent = "PERIPHERAL_STATUS"
|
||||
// debounceDuration = 500 * time.Millisecond
|
||||
// PeripheralStatusUpdated is triggered when a peripheral status has been updated (disconnected - connecting - connected)
|
||||
PeripheralStatusUpdated PeripheralEvent = "PERIPHERAL_STATUS"
|
||||
// PeripheralStatusDisconnected : peripheral is now disconnected
|
||||
PeripheralStatusDisconnected PeripheralStatus = "PERIPHERAL_DISCONNECTED"
|
||||
// PeripheralStatusConnecting : peripheral is now connecting
|
||||
PeripheralStatusConnecting PeripheralStatus = "PERIPHERAL_CONNECTING"
|
||||
// PeripheralStatusDeactivated : peripheral is now deactivated
|
||||
PeripheralStatusDeactivated PeripheralStatus = "PERIPHERAL_DEACTIVATED"
|
||||
// PeripheralStatusActivated : peripheral is now activated
|
||||
PeripheralStatusActivated PeripheralStatus = "PERIPHERAL_ACTIVATED"
|
||||
)
|
||||
|
||||
// HardwareManager is the class who manages the hardware
|
||||
@@ -45,13 +54,19 @@ func NewHardwareManager() *HardwareManager {
|
||||
|
||||
// Start starts to find new peripheral events
|
||||
func (h *HardwareManager) Start(ctx context.Context) error {
|
||||
// Initialize all the finders
|
||||
// Initialize all the finders and their callback functions
|
||||
for finderName, finder := range h.finders {
|
||||
err := finder.Initialize()
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to initialize finder")
|
||||
return err
|
||||
}
|
||||
finder.OnArrival(func(p PeripheralInfo) {
|
||||
runtime.EventsEmit(ctx, string(PeripheralArrival), p)
|
||||
})
|
||||
finder.OnRemoval(func(p PeripheralInfo) {
|
||||
runtime.EventsEmit(ctx, string(PeripheralRemoval), p)
|
||||
})
|
||||
err = finder.Start(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "hardware").Str("finderName", finderName).Msg("unable to start finder")
|
||||
@@ -105,27 +120,6 @@ func (h *HardwareManager) Scan() error {
|
||||
}
|
||||
}
|
||||
|
||||
// emitPeripheralsChanges compares the old and new peripherals to determine which ones have been added or removed.
|
||||
func emitPeripheralsChanges(ctx context.Context, oldPeripherals map[string]PeripheralInfo, newPeripherals map[string]PeripheralInfo) {
|
||||
log.Trace().Any("oldList", oldPeripherals).Any("newList", newPeripherals).Msg("emitting peripherals changes to the front")
|
||||
|
||||
// Identify removed peripherals: present in the old list but not in the new list
|
||||
for oldPeriphName := range oldPeripherals {
|
||||
if _, exists := newPeripherals[oldPeriphName]; !exists {
|
||||
runtime.EventsEmit(ctx, string(PeripheralRemoval), oldPeripherals[oldPeriphName])
|
||||
log.Trace().Str("file", "hardware").Str("event", string(PeripheralRemoval)).Msg("emit peripheral removal event")
|
||||
}
|
||||
}
|
||||
|
||||
// Identify added peripherals: present in the new list but not in the old list
|
||||
for newPeriphName := range newPeripherals {
|
||||
if _, exists := oldPeripherals[newPeriphName]; !exists {
|
||||
runtime.EventsEmit(ctx, string(PeripheralArrival), newPeripherals[newPeriphName])
|
||||
log.Trace().Str("file", "hardware").Str("event", string(PeripheralArrival)).Msg("emit peripheral arrival event")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WaitStop stops the hardware manager
|
||||
func (h *HardwareManager) WaitStop() error {
|
||||
log.Trace().Str("file", "hardware").Msg("closing the hardware manager")
|
||||
|
||||
@@ -6,7 +6,7 @@ import "context"
|
||||
type Peripheral interface {
|
||||
Connect(context.Context) error // Connect the peripheral
|
||||
IsConnected() bool // Return if the peripheral is connected or not
|
||||
Disconnect() error // Disconnect the peripheral
|
||||
Disconnect() error // Disconnect the peripheral
|
||||
Activate(context.Context) error // Activate the peripheral
|
||||
Deactivate(context.Context) error // Deactivate the peripheral
|
||||
SetSettings(map[string]interface{}) error // Set a peripheral setting
|
||||
@@ -18,21 +18,25 @@ type Peripheral interface {
|
||||
|
||||
// PeripheralInfo represents a peripheral information
|
||||
type PeripheralInfo struct {
|
||||
Name string `yaml:"name"` // Name of the peripheral
|
||||
SerialNumber string `yaml:"sn"` // S/N of the peripheral
|
||||
ProtocolName string `yaml:"protocol"` // Protocol name of the peripheral
|
||||
IsOpen bool // Open flag for peripheral connection
|
||||
Settings map[string]interface{} `yaml:"settings"` // Peripheral settings
|
||||
Name string `yaml:"name"` // Name of the peripheral
|
||||
SerialNumber string `yaml:"sn"` // S/N of the peripheral
|
||||
ProtocolName string `yaml:"protocol"` // Protocol name of the peripheral
|
||||
// IsConnected bool // If the peripheral is connected to the system
|
||||
// IsActivated bool // If the peripheral is activated in the project
|
||||
// IsDetected bool // If the peripheral is detected by the system
|
||||
Settings map[string]interface{} `yaml:"settings"` // Peripheral settings
|
||||
}
|
||||
|
||||
// PeripheralFinder represents how compatible peripheral drivers are implemented
|
||||
type PeripheralFinder interface {
|
||||
Initialize() error // Initializes the protocol
|
||||
OnArrival(cb func(p PeripheralInfo)) // Callback function when a peripheral arrives
|
||||
OnRemoval(cb func(p PeripheralInfo)) // Callback function when a peripheral goes away
|
||||
Start(context.Context) error // Start the detection
|
||||
WaitStop() error // Waiting for finder to close
|
||||
ForceScan() // Explicitly scans for peripherals
|
||||
RegisterPeripheral(context.Context, PeripheralInfo) (string, error) // Registers a new peripheral data
|
||||
UnregisterPeripheral(context.Context, string) error // Unregisters an existing peripheral
|
||||
UnregisterPeripheral(context.Context, PeripheralInfo) error // Unregisters an existing peripheral
|
||||
GetPeripheralSettings(string) (map[string]interface{}, error) // Gets the peripheral settings
|
||||
SetPeripheralSettings(string, map[string]interface{}) error // Sets the peripheral settings
|
||||
GetName() string // Get the name of the finder
|
||||
|
||||
@@ -65,21 +65,21 @@ func (a *App) UpdatePeripheralSettings(protocolName, peripheralID string, settin
|
||||
}
|
||||
|
||||
// RemovePeripheral removes a peripheral from the project
|
||||
func (a *App) RemovePeripheral(protocolName string, peripheralID string) error {
|
||||
func (a *App) RemovePeripheral(peripheralData hardware.PeripheralInfo) error {
|
||||
// Unregister the peripheral from the finder
|
||||
f, err := a.hardwareManager.GetFinder(protocolName)
|
||||
f, err := a.hardwareManager.GetFinder(peripheralData.ProtocolName)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "peripherals").Str("protocolName", protocolName).Msg("unable to find the finder")
|
||||
log.Err(err).Str("file", "peripherals").Str("protocolName", peripheralData.ProtocolName).Msg("unable to find the finder")
|
||||
return fmt.Errorf("unable to find the finder")
|
||||
}
|
||||
err = f.UnregisterPeripheral(a.ctx, peripheralID)
|
||||
err = f.UnregisterPeripheral(a.ctx, peripheralData)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", "peripherals").Str("peripheralID", peripheralID).Msg("unable to unregister this peripheral")
|
||||
log.Err(err).Str("file", "peripherals").Str("peripheralID", peripheralData.SerialNumber).Msg("unable to unregister this peripheral")
|
||||
return fmt.Errorf("unable to unregister this peripheral")
|
||||
}
|
||||
// Remove the peripheral ID from the project
|
||||
delete(a.projectInfo.PeripheralsInfo, peripheralID)
|
||||
log.Info().Str("file", "peripheral").Str("protocolName", protocolName).Str("periphID", peripheralID).Msg("peripheral removed from project")
|
||||
delete(a.projectInfo.PeripheralsInfo, peripheralData.SerialNumber)
|
||||
log.Info().Str("file", "peripheral").Str("protocolName", peripheralData.ProtocolName).Str("periphID", peripheralData.SerialNumber).Msg("peripheral removed from project")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
248
project.go
Normal file
248
project.go
Normal 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, value)
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user