Skip to content
Open
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
1,123 changes: 1,123 additions & 0 deletions dashboard/app.js

Large diffs are not rendered by default.

510 changes: 510 additions & 0 deletions dashboard/index.html

Large diffs are not rendered by default.

518 changes: 518 additions & 0 deletions dashboard/member.html

Large diffs are not rendered by default.

476 changes: 476 additions & 0 deletions dashboard/member.js

Large diffs are not rendered by default.

943 changes: 943 additions & 0 deletions dashboard/styles.css

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions electron/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
dist/
24 changes: 24 additions & 0 deletions electron/config-dashboard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"appId": "com.boardflow.dashboard",
"productName": "BoardFlow Dashboard",
"copyright": "BoardFlow Team",
"extraMetadata": {
"main": "main-dashboard.js"
},
"files": [
"main-dashboard.js",
"preload.js",
{ "from": "../dashboard", "to": "dashboard", "filter": ["**/*"] }
],
"win": {
"target": [
{ "target": "portable", "arch": ["x64"] }
]
},
"portable": {
"artifactName": "BoardFlow-Dashboard.exe"
},
"directories": {
"output": "dist/dashboard"
}
}
24 changes: 24 additions & 0 deletions electron/config-member.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"appId": "com.boardflow.member",
"productName": "BoardFlow Member",
"copyright": "BoardFlow Team",
"extraMetadata": {
"main": "main-member.js"
},
"files": [
"main-member.js",
"preload.js",
{ "from": "../dashboard", "to": "dashboard", "filter": ["**/*"] }
],
"win": {
"target": [
{ "target": "portable", "arch": ["x64"] }
]
},
"portable": {
"artifactName": "BoardFlow-Member.exe"
},
"directories": {
"output": "dist/member"
}
}
89 changes: 89 additions & 0 deletions electron/main-dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* main-dashboard.js — Electron main process for BoardFlow Dashboard
* Opens dashboard/index.html in a native window.
*
* Data file: %APPDATA%\BoardFlow\tasks.json (Windows)
* ~/Library/Application Support/BoardFlow/tasks.json (macOS)
* ~/.config/BoardFlow/tasks.json (Linux)
*/
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');

// ─── DATA FILE ────────────────────────────────────
// app.getPath('userData') resolves to the OS-appropriate AppData folder
const dataDir = path.join(app.getPath('userData'), 'BoardFlow');
const dataFile = path.join(dataDir, 'tasks.json');

function ensureDataDir() {
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
}

// ─── IPC: LOAD (synchronous) ──────────────────────
ipcMain.on('load-data', event => {
try {
ensureDataDir();
event.returnValue = fs.existsSync(dataFile)
? JSON.parse(fs.readFileSync(dataFile, 'utf8'))
: null;
} catch(e) {
console.error('[load-data]', e.message);
event.returnValue = null;
}
});

// ─── IPC: SAVE (async, fire-and-forget) ───────────
ipcMain.on('save-data', (event, data) => {
try {
ensureDataDir();
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2), 'utf8');
} catch(e) {
console.error('[save-data]', e.message);
}
});

// ─── IPC: DATA FILE PATH ──────────────────────────
ipcMain.on('get-data-path', event => {
event.returnValue = dataFile;
});

// ─── WINDOW ───────────────────────────────────────
function createWindow() {
const win = new BrowserWindow({
width: 1300,
height: 840,
minWidth: 960,
minHeight: 640,
title: 'BoardFlow — Dashboard',
backgroundColor: '#0d0f14',
show: false, // show only after content loads (avoids white flash)
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // required for contextBridge
nodeIntegration: false, // keep renderer sandboxed
},
});

// Dev: load from source tree; packaged: load from bundled resources
const htmlFile = app.isPackaged
? path.join(__dirname, 'dashboard', 'index.html')
: path.join(__dirname, '..', 'dashboard', 'index.html');

win.loadFile(htmlFile);
win.once('ready-to-show', () => win.show());

// Remove the default menu bar (File / Edit / View…)
win.removeMenu();
}

app.whenReady().then(createWindow);

// Quit when all windows are closed (except on macOS)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});

// macOS: re-open window when clicking dock icon
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
86 changes: 86 additions & 0 deletions electron/main-member.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* main-member.js — Electron main process for BoardFlow Member View
* Opens dashboard/member.html in a native window.
*
* Reads and writes the SAME data file as main-dashboard.js so both
* executables share one central task store.
*
* Data file: %APPDATA%\BoardFlow\tasks.json (Windows)
* ~/Library/Application Support/BoardFlow/tasks.json (macOS)
* ~/.config/BoardFlow/tasks.json (Linux)
*/
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');

// ─── DATA FILE (identical path to main-dashboard.js) ─
const dataDir = path.join(app.getPath('userData'), 'BoardFlow');
const dataFile = path.join(dataDir, 'tasks.json');

function ensureDataDir() {
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
}

// ─── IPC: LOAD ────────────────────────────────────
ipcMain.on('load-data', event => {
try {
ensureDataDir();
event.returnValue = fs.existsSync(dataFile)
? JSON.parse(fs.readFileSync(dataFile, 'utf8'))
: null;
} catch(e) {
console.error('[load-data]', e.message);
event.returnValue = null;
}
});

// ─── IPC: SAVE ────────────────────────────────────
ipcMain.on('save-data', (event, data) => {
try {
ensureDataDir();
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2), 'utf8');
} catch(e) {
console.error('[save-data]', e.message);
}
});

// ─── IPC: DATA FILE PATH ──────────────────────────
ipcMain.on('get-data-path', event => {
event.returnValue = dataFile;
});

// ─── WINDOW ───────────────────────────────────────
function createWindow() {
const win = new BrowserWindow({
width: 960,
height: 780,
minWidth: 700,
minHeight: 560,
title: 'BoardFlow — My Tasks',
backgroundColor: '#0d0f14',
show: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});

const htmlFile = app.isPackaged
? path.join(__dirname, 'dashboard', 'member.html')
: path.join(__dirname, '..', 'dashboard', 'member.html');

win.loadFile(htmlFile);
win.once('ready-to-show', () => win.show());
win.removeMenu();
}

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
17 changes: 17 additions & 0 deletions electron/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "boardflow-electron",
"version": "1.0.0",
"description": "BoardFlow — Board Design Team Task Manager (Desktop)",
"private": true,
"scripts": {
"start:dashboard": "electron main-dashboard.js",
"start:member": "electron main-member.js",
"build:dashboard": "electron-builder --config config-dashboard.json",
"build:member": "electron-builder --config config-member.json",
"build": "npm run build:dashboard && npm run build:member"
},
"devDependencies": {
"electron": "^29.4.6",
"electron-builder": "^24.13.3"
}
}
32 changes: 32 additions & 0 deletions electron/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* preload.js — Electron context bridge
*
* Exposes a safe, minimal API to the renderer (dashboard/app.js and
* dashboard/member.js) so they can read/write the shared tasks.json file
* without exposing the full Node.js API.
*
* contextIsolation is ON (nodeIntegration is OFF), so this is the only
* channel between the renderer and the main process.
*/
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
/**
* Load all data from tasks.json.
* Returns { tasks, team, sync } or null if file doesn't exist yet.
* Called synchronously — app waits for data before first render.
*/
loadData: () => ipcRenderer.sendSync('load-data'),

/**
* Persist data to tasks.json.
* Fire-and-forget — renderer does not wait for the write to complete.
* @param {{ tasks: Array, team: Array, sync: string }} data
*/
saveData: (data) => ipcRenderer.send('save-data', data),

/**
* Returns the absolute path to tasks.json (shown in the sync bar).
*/
getDataPath: () => ipcRenderer.sendSync('get-data-path'),
});