Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7,209 changes: 3,469 additions & 3,740 deletions package-lock.json

Large diffs are not rendered by default.

38 changes: 23 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "go-dash",
"version": "0.3.1",
"version": "0.4.0",
"main": "./out/main/index.js",
"description": "A dashboard for your Grand Orgue organs",
"author": "devmlb",
Expand All @@ -26,32 +26,40 @@
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0",
"@seald-io/nedb": "^4.1.2",
"@typescript-eslint/parser": "^8.56.0",
"autoprefixer": "^10.4.24",
"@typescript-eslint/parser": "^8.59.2",
"autoprefixer": "^10.5.0",
"electron-updater": "^6.8.3",
"lucide-react": "^0.575.0",
"lucide-react": "^1.14.0",
"postcss": "^8.5.6",
"postcss-nesting": "^14.0.0"
},
"devDependencies": {
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
"@electron-toolkit/eslint-config-ts": "^3.1.0",
"@electron-toolkit/tsconfig": "^2.0.0",
"@types/node": "^25.3.0",
"@types/node": "^25.6.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4",
"electron": "^40.6.0",
"@vitejs/plugin-react": "^6.0.1",
"electron": "^42.0.1",
"electron-builder": "^26.8.1",
"electron-vite": "^5.0.0",
"eslint": "^9.39.3",
"eslint": "^10.3.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.0",
"prettier": "^3.8.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"typescript": "^5.9.3",
"vite": "^7.3.1"
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"prettier": "^3.8.3",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"typescript": "^6.0.3",
"vite": "^8.0.11"
},
"overrides": {
"electron-vite": {
"vite": "$vite"
},
"eslint-plugin-react": {
"eslint": "$eslint"
}
}
}
14 changes: 13 additions & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { electronApp, optimizer, is } from "@electron-toolkit/utils";
// import icon from "../../resources/icon.png?asset";

import { organService } from "./services/organ.service";
import { settingsService, SettingValue } from "./services/settings.service";
import { updaterService } from "./services/updater.service";
import type { Organ } from "./services/organ.service";

Expand Down Expand Up @@ -87,10 +88,21 @@ app.whenReady().then(() => {
);
ipcMain.handle("chooseOrganImage", () => organService.chooseImage(window));
ipcMain.handle("chooseOrganFile", () => organService.chooseGOFile(window));
ipcMain.handle("getAppVersion", () => app.getVersion());
ipcMain.handle("exportAllOrgans", () => organService.exportAll(window));
ipcMain.handle("importOrgans", () => organService.import(window));

ipcMain.handle("getAllSettings", () => settingsService.getAll());
ipcMain.handle("getSettingValueByName", (_event, name: string) =>
settingsService.getValueByName(name),
);
ipcMain.handle(
"setSettingValueByName",
(_event, name: string, value: SettingValue) =>
settingsService.setValueByName(name, value),
);

ipcMain.handle("getAppVersion", () => app.getVersion());

app.on("activate", function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
Expand Down
63 changes: 50 additions & 13 deletions src/main/services/organ.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface MinimalOrgan {
builder: string | null;
url: string | null;
features: string | null;
stops: number | null;
keyboards: number | null;
}

interface Organ extends MinimalOrgan {
Expand All @@ -28,13 +30,20 @@ interface Organ extends MinimalOrgan {
}

class OrganService {
db: Datastore<Organ>;
db!: Datastore<Organ>;

constructor() {
this.db = new Datastore<Organ>({
filename: join(app.getPath("userData"), "organs"),
autoload: true,
});
(async () => {
try {
await this.db.autoloadPromise;
} catch {
throw new Error("Cannot load the Organ DB");
}
})();
}

async getAll(): Promise<MinimalOrgan[]> {
Expand All @@ -44,19 +53,33 @@ class OrganService {
_id: doc._id,
name: doc.name,
country: doc.country,
year: doc.year,
builder: doc.builder,
url: doc.url,
features: doc.features,
year: doc.year ?? null,
builder: doc.builder ?? null,
url: doc.url ?? null,
features: doc.features ?? null,
stops: doc.stops ?? null,
keyboards: doc.keyboards ?? null,
}));
}

async getById(id: string): Promise<Organ> {
await this.db.autoloadPromise;
const organDoc = await this.db.findOneAsync({ _id: id });

if (organDoc) {
return organDoc;
return {
_id: organDoc._id,
name: organDoc.name,
country: organDoc.country,
year: organDoc.year ?? null,
builder: organDoc.builder ?? null,
url: organDoc.url ?? null,
features: organDoc.features ?? null,
stops: organDoc.stops ?? null,
keyboards: organDoc.keyboards ?? null,
path: organDoc.path,
coverPath: organDoc.coverPath,
previewPath: organDoc.previewPath,
};
} else {
throw new Error("Unknown organ");
}
Expand Down Expand Up @@ -126,12 +149,16 @@ class OrganService {
2,
);

const exportPath = openSaveFileDialog(window, "Exporter les orgues", [
{
name: "Fichier JSON",
extensions: ["json"],
},
]);
const exportPath = openSaveFileDialog(
window,
"Exporter tous les orgues",
[
{
name: "Fichier JSON",
extensions: ["json"],
},
],
);
if (exportPath) saveToFile(organsExport, exportPath);
}

Expand Down Expand Up @@ -196,6 +223,16 @@ class OrganService {
typeof parsedOrgan.coverPath === "string"
? parsedOrgan.coverPath
: null,
stops:
"stops" in parsedOrgan &&
typeof parsedOrgan.stops === "number"
? parsedOrgan.stops
: null,
keyboards:
"keyboards" in parsedOrgan &&
typeof parsedOrgan.keyboards === "number"
? parsedOrgan.keyboards
: null,
});
});
}
Expand Down
81 changes: 81 additions & 0 deletions src/main/services/settings.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { app } from "electron";
import { join } from "node:path";
import Datastore from "@seald-io/nedb";

type SettingValue = string | boolean;

interface Setting {
_id?: string;
name: string;
value: SettingValue;
}

const DEFAULT_SETTINGS: Setting[] = [
{ name: "sort-field", value: "name" },
{ name: "ascendant-sort", value: true },
];

class SettingsService {
db!: Datastore<Setting>;
initialized: boolean;

constructor() {
this.initialized = false;
this.db = new Datastore<Setting>({
filename: join(app.getPath("userData"), "settings"),
autoload: true,
});
(async () => {
try {
await this.db.autoloadPromise;

for (const setting of DEFAULT_SETTINGS) {
if (!(await this.db.findOneAsync({ name: setting.name }))) {
await this.db.insertAsync(setting);
}
}
} catch {
throw new Error("Cannot load the Settings DB");
}
})();
}

async getAll(): Promise<Omit<Setting, "_id">[]> {
const settingDocs = await this.db.findAsync({});

return settingDocs.map((doc) => ({
name: doc.name,
value: doc.value,
}));
}

async getValueByName(name: string): Promise<SettingValue> {
const settingDoc = await this.db.findOneAsync({ name });

if (settingDoc) {
return settingDoc.value;
} else {
throw new Error("Unknown setting");
}
}

async setValueByName(name: string, value: SettingValue): Promise<void> {
const settingDoc = await this.db.findOneAsync({ name });

if (settingDoc) {
if (typeof value === typeof settingDoc.value) {
await this.db.updateAsync({ name }, { name, value });
} else {
throw new Error(
"Cannot set a new setting value with a different type",
);
}
} else {
throw new Error("Unknown setting");
}
}
}

const settingsService = new SettingsService();

export { settingsService, type Setting, type SettingValue };
14 changes: 11 additions & 3 deletions src/main/utils/fs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { execSync } from "node:child_process";
import { copyFileSync, readFileSync, writeFileSync } from "node:fs";
import { copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
import { app, dialog, FileFilter } from "electron";
import { join } from "node:path";

Expand All @@ -22,8 +22,16 @@ function getFileContent(filePath: string): string {
return readFileSync(filePath, "utf-8");
}

function getFileContentB64(filePath: string): string {
return readFileSync(filePath).toString("base64");
function getFileContentB64(filePath: string): string | null {
if (!existsSync(filePath)) {
return null;
}

try {
return readFileSync(filePath).toString("base64");
} catch {
return null;
}
}

function openChooseFileDialog(
Expand Down
10 changes: 9 additions & 1 deletion src/preload/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ipcRenderer } from "electron";

import { Organ } from "../main/services/organ.service";
import { SettingValue } from "../main/services/settings.service";

// Custom APIs for renderer
const api = {
Expand All @@ -16,9 +17,16 @@ const api = {
getOrganPreview: (id: string) => ipcRenderer.invoke("getOrganPreview", id),
chooseOrganImage: () => ipcRenderer.invoke("chooseOrganImage"),
chooseOrganFile: () => ipcRenderer.invoke("chooseOrganFile"),
getAppVersion: () => ipcRenderer.invoke("getAppVersion"),
exportAllOrgans: () => ipcRenderer.invoke("exportAllOrgans"),
importOrgans: () => ipcRenderer.invoke("importOrgans"),

getAllSettings: () => ipcRenderer.invoke("getAllSettings"),
getSettingValueByName: (name: string) =>
ipcRenderer.invoke("getSettingValueByName", name),
setSettingValueByName: (name: string, value: SettingValue) =>
ipcRenderer.invoke("setSettingValueByName", name, value),

getAppVersion: () => ipcRenderer.invoke("getAppVersion"),
};

export { api };
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MinimalOrgan, Organ } from "./types/api.type";
import type { MinimalOrgan, Organ } from "../utils/types/api.type";

async function getOrgansList(): Promise<MinimalOrgan[]> {
return await window.api.getAllOrgans();
Expand Down Expand Up @@ -42,10 +42,6 @@ async function chooseOrganImage(): Promise<string | null> {
return await window.api.chooseOrganImage();
}

async function getAppVersion(): Promise<string> {
return await window.api.getAppVersion();
}

async function exportAllOrgans(): Promise<string> {
return await window.api.exportAllOrgans();
}
Expand All @@ -69,7 +65,6 @@ export {
removeOrgan,
chooseOrganImage,
chooseOrganFile,
getAppVersion,
exportAllOrgans,
importOrgans,
removeAllOrgans,
Expand Down
24 changes: 24 additions & 0 deletions src/renderer/src/api/settings.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Setting, SettingValue } from "../utils/types/api.type";

async function getAllSettings(): Promise<Setting[]> {
return await window.api.getAllSettings();
}

async function getSettingValue<T extends SettingValue>(
name: string,
): Promise<T> {
return await window.api.getSettingValueByName(name);
}

async function setSettingValue(
name: string,
value: SettingValue,
): Promise<void> {
return await window.api.setSettingValueByName(name, value);
}

async function getAppVersion(): Promise<string> {
return await window.api.getAppVersion();
}

export { getAllSettings, getSettingValue, setSettingValue, getAppVersion };
Loading
Loading