diff --git a/.gitignore b/.gitignore index 03c85d1..59ad4c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/bin +projects node_modules frontend/.vscode frontend/dist diff --git a/app.go b/app.go index af53038..64211c3 100644 --- a/app.go +++ b/app.go @@ -3,6 +3,21 @@ package main import ( "context" "fmt" + "io" + "log" + "os" + "path/filepath" + "strings" + + "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 ) // App struct @@ -21,7 +36,147 @@ func (a *App) startup(ctx context.Context) { a.ctx = ctx } -// Greet returns a greeting for the given name -func (a *App) Greet(name string) string { - return fmt.Sprintf("Hello %s, It's show time!", name) +// GetProjects gets all the projects in the projects directory +func (a *App) GetProjects() ([]ProjectInfo, error) { + projects := []ProjectInfo{} + + f, err := os.Open(projectsDirectory) + if err != nil { + log.Fatalf("Unable to open the projects directory: %v", err) + return nil, err + } + + files, err := f.Readdir(0) + if err != nil { + log.Fatalf("Unable to read the projects directory: %v", err) + return nil, err + } + + for _, fileInfo := range files { + // Open the file and get the show name + fileData, err := os.ReadFile(filepath.Join(projectsDirectory, fileInfo.Name())) + if err == nil { + projectObject := ProjectInfo{} + err = yaml.Unmarshal(fileData, &projectObject) + if err == nil { + // Add the SaveFile property + projectObject.ShowInfo.SaveFile = fileInfo.Name() + projects = append(projects, projectObject) + } + } + } + return projects, nil +} + +// GetProjectInfo returns the information of the saved project +func (a *App) GetProjectInfo(projectFile string) (ShowInfo, error) { + 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 + } + + projectInfo := ProjectInfo{} + + err = yaml.Unmarshal(content, &projectInfo) + if err != nil { + log.Fatalf("Unable to get the project information: %v", err) + return ShowInfo{}, err + } + projectInfo.ShowInfo.SaveFile = projectFile + return projectInfo.ShowInfo, 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 { + return "", err + } + // Copy the avatar to the application avatars path + avatarPath := filepath.Join(avatarsDirectory, filepath.Base(filePath)) + _, err = copy(filePath, avatarPath) + if err != nil { + return "", err + } + return filepath.Base(filePath), 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) + // 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) + } + project := ProjectInfo{} + log.Printf("The number of universes: %d", showInfo.UniversesNumber) + project.ShowInfo = showInfo + data, err := yaml.Marshal(project) + if err != nil { + return "", err + } + err = os.WriteFile(filepath.Join(projectsDirectory, showInfo.SaveFile), data, os.ModePerm) + if err != nil { + return "", err + } + return showInfo.SaveFile, 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:"-"` +} + +// ProjectInfo defines all the information for a lighting project +type ProjectInfo struct { + ShowInfo ShowInfo `yaml:"show"` // Show information +} + +func formatString(input string) string { + // Convertir en minuscules + lowerCaseString := strings.ToLower(input) + // Remplacer les espaces par des underscores + formattedString := strings.ReplaceAll(lowerCaseString, " ", "_") + return formattedString +} + +func copy(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + defer destination.Close() + nBytes, err := io.Copy(destination, source) + return nBytes, err } diff --git a/build/appicon.png b/build/appicon.png index 1257908..63617fe 100644 Binary files a/build/appicon.png and b/build/appicon.png differ diff --git a/build/windows/icon.ico b/build/windows/icon.ico index 9cb213c..bfa0690 100644 Binary files a/build/windows/icon.ico and b/build/windows/icon.ico differ diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 970dbfb..23b0f57 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -8,16 +8,55 @@ import Devices from './components/Devices/Devices.svelte'; import Show from './components/Show/Show.svelte'; import GeneralConsole from './components/Console/GeneralConsole.svelte'; + import RoundIconButton from './components/General/RoundIconButton.svelte'; + import { currentProject, needProjectSave } from './stores'; + import { SaveProject } from '../wailsjs/go/main/App.js'; + import { construct_svelte_component } from 'svelte/internal'; let selectedMenu = "settings" // When the navigation menu changed, update the selected menu function onNavigationChanged(event){ selectedMenu = event.detail.menu; } + + // Save the project + function saveProject(){ + SaveProject($currentProject).then((saveFile) => { + console.log($currentProject) + $currentProject.SaveFile = saveFile + needProjectSave.set(false) + console.log("Project has been saved") + }).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: "", + }); +
+ {#if $needProjectSave} + + {/if}
diff --git a/frontend/src/components/General/Clock.svelte b/frontend/src/components/General/Clock.svelte index 2e588d1..72701f4 100644 --- a/frontend/src/components/General/Clock.svelte +++ b/frontend/src/components/General/Clock.svelte @@ -1,16 +1,7 @@ -
+
{hours}:{minutes}{seconds}
diff --git a/frontend/src/components/General/DropdownList.svelte b/frontend/src/components/General/DropdownList.svelte new file mode 100644 index 0000000..3df0136 --- /dev/null +++ b/frontend/src/components/General/DropdownList.svelte @@ -0,0 +1,95 @@ + + + +
+ + +
+ {#each Array.from(choices) as [key, value]} +
handleclick({key})}>{value}
+ {/each} +
+
+ + \ No newline at end of file diff --git a/frontend/src/components/General/Input.svelte b/frontend/src/components/General/Input.svelte index ff1248c..bf9572f 100644 --- a/frontend/src/components/General/Input.svelte +++ b/frontend/src/components/General/Input.svelte @@ -1,6 +1,6 @@
-

{label}

+

{label}

{#if type === 'large'} -