diff --git a/.gitignore b/.gitignore
index 408a58b..a83910e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,5 @@ frontend/wailsjs
*/package.json.md5
*.exe
*.o
-*.rc
\ No newline at end of file
+*.rc
+frontend/public
\ No newline at end of file
diff --git a/app.go b/app.go
index e99339e..eb4cec3 100644
--- a/app.go
+++ b/app.go
@@ -10,6 +10,7 @@ import (
"path/filepath"
"strings"
"sync"
+ "time"
"github.com/wailsapp/wails/v2/pkg/runtime"
@@ -27,6 +28,8 @@ type App struct {
ctx context.Context
hardwareManager *hardware.HardwareManager // For managing all the hardware
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
// FOR TESTING PURPOSE ONLY
ftdi *hardware.FTDIPeripheral
}
@@ -39,6 +42,7 @@ func NewApp() *App {
hardwareManager.RegisterFinder(hardware.NewFTDIFinder())
return &App{
hardwareManager: hardwareManager,
+ projectSave: "",
}
}
@@ -53,14 +57,27 @@ func (a *App) startup(ctx context.Context) {
}
}
+// CreateProject creates a new blank project
+func (a *App) CreateProject() ShowInfo {
+ date := time.Now()
+ a.projectSave = ""
+ a.projectInfo.ShowInfo = 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",
+ }
+ return a.projectInfo.ShowInfo
+}
+
// GetPeripherals gets all the peripherals connected
-func (a *App) GetPeripherals() {
- a.hardwareManager.Scan(a.ctx)
+func (a *App) GetPeripherals() error {
+ return a.hardwareManager.Scan(a.ctx)
}
// GetProjects gets all the projects in the projects directory
-func (a *App) GetProjects() ([]ProjectInfo, error) {
- projects := []ProjectInfo{}
+func (a *App) GetProjects() ([]ProjectMetaData, error) {
+ projects := []ProjectMetaData{}
f, err := os.Open(projectsDirectory)
if err != nil {
@@ -82,8 +99,10 @@ func (a *App) GetProjects() ([]ProjectInfo, error) {
err = yaml.Unmarshal(fileData, &projectObject)
if err == nil {
// Add the SaveFile property
- projectObject.ShowInfo.SaveFile = fileInfo.Name()
- projects = append(projects, projectObject)
+ projects = append(projects, ProjectMetaData{
+ Name: projectObject.ShowInfo.Name,
+ Save: fileInfo.Name(),
+ })
}
}
}
@@ -91,23 +110,23 @@ func (a *App) GetProjects() ([]ProjectInfo, error) {
}
// GetProjectInfo returns the information of the saved project
-func (a *App) GetProjectInfo(projectFile string) (ShowInfo, error) {
+func (a *App) GetProjectInfo(projectFile string) (ProjectInfo, error) {
+ // Open the project file
projectPath := filepath.Join(projectsDirectory, projectFile)
content, err := os.ReadFile(projectPath)
if err != nil {
log.Fatalf("Unable to read the project file: %v", err)
- return ShowInfo{}, err
+ return ProjectInfo{}, err
}
-
- projectInfo := ProjectInfo{}
-
- err = yaml.Unmarshal(content, &projectInfo)
+ err = yaml.Unmarshal(content, &a.projectInfo)
if err != nil {
log.Fatalf("Unable to get the project information: %v", err)
- return ShowInfo{}, err
+ return ProjectInfo{}, err
}
- projectInfo.ShowInfo.SaveFile = projectFile
- return projectInfo.ShowInfo, nil
+ // Load it into the app
+ a.projectSave = projectFile
+ // Return the show information
+ return a.projectInfo, nil
}
// ChooseAvatarPath opens a filedialog to choose the show avatar
@@ -134,40 +153,85 @@ func (a *App) ChooseAvatarPath() (string, error) {
return filepath.Base(filePath), nil
}
+// UpdateShowInfo updates the show information
+func (a *App) UpdateShowInfo(showInfo ShowInfo) {
+ fmt.Printf("%s\n", showInfo)
+ a.projectInfo.ShowInfo = showInfo
+}
+
+// AddPeripheral adds a peripheral to the project
+func (a *App) AddPeripheral(protocolName string, peripheralID string) error {
+ // Get the device from its finder
+ p, found := a.hardwareManager.GetPeripheral(protocolName, peripheralID)
+ if !found {
+ return fmt.Errorf("Unable to localize the peripheral %s", peripheralID)
+ }
+ // Add the peripheral ID to the project
+ a.projectInfo.PeripheralsInfo = append(a.projectInfo.PeripheralsInfo, p.GetInfo())
+ // TODO: Connect the peripheral
+ return nil
+}
+
+// RemovePeripheral adds a peripheral to the project
+func (a *App) RemovePeripheral(protocolName string, peripheralID string) error {
+ // TODO: Disconnect the peripheral
+ // Remove the peripheral ID from the project
+ a.projectInfo.PeripheralsInfo = removePeripheralFromList(a.projectInfo.PeripheralsInfo, peripheralID)
+ return nil
+}
+
// SaveProject saves the project
-func (a *App) SaveProject(showInfo ShowInfo) (string, error) {
- log.Printf("Saving the project %s to %s", showInfo.Name, showInfo.SaveFile)
+func (a *App) SaveProject() (string, error) {
+ log.Printf("Saving the project %s to %s", a.projectInfo.ShowInfo.Name, a.projectSave)
// If there is no save file, create a new one with the show name
- if showInfo.SaveFile == "" {
- showInfo.SaveFile = fmt.Sprintf("%s%s", formatString(showInfo.Name), projectExtension)
+ 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)
}
- project := ProjectInfo{}
- log.Printf("The number of universes: %d", showInfo.UniversesNumber)
- project.ShowInfo = showInfo
- data, err := yaml.Marshal(project)
+ data, err := yaml.Marshal(a.projectInfo)
if err != nil {
return "", err
}
- err = os.WriteFile(filepath.Join(projectsDirectory, showInfo.SaveFile), data, os.ModePerm)
+ // Create the project directory if not exists
+ err = os.MkdirAll(projectsDirectory, os.ModePerm)
if err != nil {
return "", err
}
- return showInfo.SaveFile, nil
+ err = os.WriteFile(filepath.Join(projectsDirectory, a.projectSave), data, os.ModePerm)
+ if err != nil {
+ return "", err
+ }
+ return a.projectSave, nil
}
// ShowInfo defines the information of the show
type ShowInfo struct {
- Name string `yaml:"name"`
- Date string `yaml:"date"`
- UniversesNumber int `yaml:"universesNumber"`
- Avatar string `yaml:"avatar"`
- Comments string `yaml:"comments"`
- SaveFile string `yaml:"-"`
+ 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
+ ShowInfo ShowInfo `yaml:"show"` // Show information
+ PeripheralsInfo []hardware.PeripheralInfo `yaml:"peripherals"` // Peripherals information
+}
+
+func removePeripheralFromList(slice []hardware.PeripheralInfo, idToRemove string) []hardware.PeripheralInfo {
+ result := []hardware.PeripheralInfo{}
+ for _, peripheral := range slice {
+ if peripheral.SerialNumber != idToRemove {
+ result = append(result, peripheral)
+ }
+ }
+ return result
}
func formatString(input string) string {
@@ -208,7 +272,7 @@ func copy(src, dst string) (int64, error) {
func (a *App) ConnectFTDI() error {
// Create a new FTDI object
var err error
- a.ftdi, err = hardware.NewFTDIPeripheral("FTDI TEST INTERFACE", "A50825I", 0)
+ a.ftdi, err = hardware.NewFTDIPeripheral("FTDI TEST INTERFACE", "A50825I", 0, "Virtual FTDI finder")
if err != nil {
return err
}
diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte
index a554ef0..c6a5d5e 100644
--- a/frontend/src/App.svelte
+++ b/frontend/src/App.svelte
@@ -9,29 +9,30 @@
import Show from './components/Show/Show.svelte';
import GeneralConsole from './components/Console/GeneralConsole.svelte';
import RoundIconButton from './components/General/RoundIconButton.svelte';
- import { currentProject, needProjectSave, peripherals } from './stores';
+ import { showInformation, needProjectSave, availablePeripherals, savedPeripherals } from './stores';
import { SaveProject } from '../wailsjs/go/main/App.js';
import { construct_svelte_component } from 'svelte/internal';
import { EventsOn } from '../wailsjs/runtime'
- import { GetPeripherals } from "../wailsjs/go/main/App";
+ import { CreateProject, GetPeripherals } from "../wailsjs/go/main/App";
+ import { WindowSetTitle } from "../wailsjs/runtime/runtime"
- // When the list of hardware changed, update the store
+ // Handle the event when a new peripheral is detected
EventsOn('PERIPHERAL_ARRIVAL', function(peripheralInfo){
console.log("Hardware has been added to the system");
- console.log(peripheralInfo)
- $peripherals = [...$peripherals, peripheralInfo];
+ $availablePeripherals = [...$availablePeripherals, peripheralInfo];
})
+ // 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");
- console.log(peripheralInfo)
- $peripherals = $peripherals.filter(item => JSON.stringify(item) !== JSON.stringify(peripheralInfo))
- console.log($peripherals)
-
- // $peripherals = devicesList
- // console.log(devicesList)
+ $availablePeripherals = $availablePeripherals.filter(item => JSON.stringify(item) !== JSON.stringify(peripheralInfo))
})
+ // Set the window title
+ $: {
+ WindowSetTitle("DMXConnect - " + $showInformation.Name + ($needProjectSave ? " (unsaved)" : ""))
+ }
+
let selectedMenu = "settings"
// When the navigation menu changed, update the selected menu
function onNavigationChanged(event){
@@ -40,44 +41,41 @@
// Save the project
function saveProject(){
- SaveProject($currentProject).then((saveFile) => {
- console.log($currentProject)
- $currentProject.SaveFile = saveFile
+ SaveProject().then((filePath) => {
needProjectSave.set(false)
- console.log("Project has been saved")
+ console.log("Project has been saved to " + filePath)
}).catch((error) => {
console.error(`Unable to save the project: ${error}`)
})
}
- function formatDate(date) {
- const pad = (number) => number.toString().padStart(2, '0');
- const year = date.getFullYear();
- const month = pad(date.getMonth() + 1); // Les mois commencent à 0
- const day = pad(date.getDate());
- const hours = pad(date.getHours());
- const minutes = pad(date.getMinutes());
- return `${year}-${month}-${day}T${hours}:${minutes}`;
- }
-
- currentProject.set({
- Name: "My new show",
- Date: formatDate(new Date()),
- Avatar: "appicon.png",
- UniversesNumber: 1,
- Comments: "Write your comments here",
- SaveFile: "",
- });
+ // Instanciate a new project
+ CreateProject().then((showInfo) => {
+ showInformation.set(showInfo)
+ $needProjectSave = true
+ })
// Request the list of peripherals
+ // TODO: Handle the error here
GetPeripherals()
+ // 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()
+ }
+ });
+
{#if $needProjectSave}
-
+
{/if}
diff --git a/frontend/src/components/General/Input.svelte b/frontend/src/components/General/Input.svelte
index d695018..11119e7 100644
--- a/frontend/src/components/General/Input.svelte
+++ b/frontend/src/components/General/Input.svelte
@@ -15,21 +15,27 @@
const dispatch = createEventDispatcher();
- function handleInput(event){
- value = event.target.value
- dispatch('input', value)
+ function handleInput(){
+ dispatch('input')
}
+ function handleBlur(event){
+ dispatch('blur', event)
+ }
+
+ function handleDblClick(){
+ dispatch('dblclick')
+ }
{label}
{#if type === 'large'}
-
@@ -48,14 +54,14 @@
}
input::selection {
- background: #0F4C75; /* Couleur de fond de la sélection */
- color: #FFFFFF; /* Couleur du texte de la sélection */
+ background: var(--first-color); /* Couleur de fond de la sélection */
+ color: var(--white-color); /* Couleur du texte de la sélection */
}
/* Pour Firefox */
input::-moz-selection {
- background: #0F4C75; /* Couleur de fond de la sélection */
- color: #FFFFFF; /* Couleur du texte de la sélection */
+ background: var(--first-color); /* Couleur de fond de la sélection */
+ color: var(--white-color); /* Couleur du texte de la sélection */
}
input:focus {
outline: 1px solid #BBE1FA;
@@ -69,14 +75,14 @@
}
textarea::selection {
- background: #0F4C75; /* Couleur de fond de la sélection */
- color: #FFFFFF; /* Couleur du texte de la sélection */
+ background: var(--first-color); /* Couleur de fond de la sélection */
+ color: var(--white-color); /* Couleur du texte de la sélection */
}
/* Pour Firefox */
textarea::-moz-selection {
- background: #0F4C75; /* Couleur de fond de la sélection */
- color: #FFFFFF; /* Couleur du texte de la sélection */
+ background: var(--first-color); /* Couleur de fond de la sélection */
+ color: var(--white-color); /* Couleur du texte de la sélection */
}
textarea:focus {
outline: 1px solid #BBE1FA;
diff --git a/frontend/src/components/General/RoundIconButton.svelte b/frontend/src/components/General/RoundIconButton.svelte
index 60be4a9..16d6d36 100644
--- a/frontend/src/components/General/RoundIconButton.svelte
+++ b/frontend/src/components/General/RoundIconButton.svelte
@@ -48,8 +48,11 @@
// Emit a click event when the button is clicked
const dispatch = createEventDispatcher();
- function emitClick() {
- dispatch('click');
+ function emitMouseDown() {
+ dispatch('mousedown');
+ }
+ function emitMouseUp() {
+ dispatch('mouseup');
}
let tooltipPosition = {top: 0, left: 0}
@@ -71,7 +74,8 @@