- Add Backspace-design-prototype.html: finalized "Aether Drift" design language with warm matte surfaces and subtle frosted glass accents - Update CLAUDE.md with DESIGN SYSTEM section and remove Discord clone references - Rename all Opencord references to Backspace across the full codebase - Archive outdated design experiments and Discord-specific assets - Add science-backed accessibility fallback (prefers-reduced-transparency)
195 lines
4.0 KiB
TypeScript
195 lines
4.0 KiB
TypeScript
import {
|
|
app,
|
|
BrowserWindow,
|
|
Tray,
|
|
Menu,
|
|
Notification,
|
|
nativeImage,
|
|
ipcMain,
|
|
shell,
|
|
} from 'electron';
|
|
import path from 'path';
|
|
|
|
let mainWindow: BrowserWindow | null = null;
|
|
let tray: Tray | null = null;
|
|
let isQuitting = false;
|
|
|
|
const SERVER_URL = process.env.BACKSPACE_URL || 'http://localhost:3000';
|
|
|
|
function createWindow(): void {
|
|
mainWindow = new BrowserWindow({
|
|
width: 1280,
|
|
height: 800,
|
|
minWidth: 940,
|
|
minHeight: 500,
|
|
title: 'Backspace',
|
|
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
|
|
backgroundColor: '#313338',
|
|
show: false,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
sandbox: true,
|
|
},
|
|
});
|
|
|
|
mainWindow.loadURL(SERVER_URL);
|
|
|
|
mainWindow.once('ready-to-show', () => {
|
|
mainWindow?.show();
|
|
});
|
|
|
|
mainWindow.on('close', (event) => {
|
|
if (!isQuitting) {
|
|
event.preventDefault();
|
|
mainWindow?.hide();
|
|
}
|
|
});
|
|
|
|
mainWindow.on('closed', () => {
|
|
mainWindow = null;
|
|
});
|
|
|
|
// Open external links in default browser
|
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
shell.openExternal(url);
|
|
}
|
|
return { action: 'deny' };
|
|
});
|
|
}
|
|
|
|
function createTray(): void {
|
|
// Create a 16x16 tray icon (blue circle for Backspace)
|
|
const size = 16;
|
|
const canvas = Buffer.alloc(size * size * 4);
|
|
const cx = size / 2;
|
|
const cy = size / 2;
|
|
const r = size / 2 - 1;
|
|
for (let y = 0; y < size; y++) {
|
|
for (let x = 0; x < size; x++) {
|
|
const idx = (y * size + x) * 4;
|
|
const dist = Math.sqrt((x - cx) ** 2 + (y - cy) ** 2);
|
|
if (dist <= r) {
|
|
canvas[idx] = 0x58; // R (blurple)
|
|
canvas[idx + 1] = 0x65; // G
|
|
canvas[idx + 2] = 0xf2; // B
|
|
canvas[idx + 3] = 0xff; // A
|
|
} else {
|
|
canvas[idx] = 0;
|
|
canvas[idx + 1] = 0;
|
|
canvas[idx + 2] = 0;
|
|
canvas[idx + 3] = 0;
|
|
}
|
|
}
|
|
}
|
|
const icon = nativeImage.createFromBuffer(canvas, { width: size, height: size });
|
|
tray = new Tray(icon);
|
|
|
|
const contextMenu = Menu.buildFromTemplate([
|
|
{
|
|
label: 'Show Backspace',
|
|
click: () => {
|
|
mainWindow?.show();
|
|
mainWindow?.focus();
|
|
},
|
|
},
|
|
{
|
|
label: 'Hide',
|
|
click: () => {
|
|
mainWindow?.hide();
|
|
},
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: 'Quit',
|
|
click: () => {
|
|
isQuitting = true;
|
|
app.quit();
|
|
},
|
|
},
|
|
]);
|
|
|
|
tray.setToolTip('Backspace');
|
|
tray.setContextMenu(contextMenu);
|
|
|
|
tray.on('click', () => {
|
|
if (mainWindow?.isVisible()) {
|
|
mainWindow.hide();
|
|
} else {
|
|
mainWindow?.show();
|
|
mainWindow?.focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
function showNotification(title: string, body: string): void {
|
|
if (Notification.isSupported()) {
|
|
const notification = new Notification({
|
|
title,
|
|
body,
|
|
silent: false,
|
|
});
|
|
|
|
notification.on('click', () => {
|
|
mainWindow?.show();
|
|
mainWindow?.focus();
|
|
});
|
|
|
|
notification.show();
|
|
}
|
|
}
|
|
|
|
function registerIpcHandlers(): void {
|
|
ipcMain.on('show-notification', (_event, data: { title: string; body: string }) => {
|
|
showNotification(data.title, data.body);
|
|
});
|
|
|
|
ipcMain.on('set-badge-count', (_event, count: number) => {
|
|
if (app.setBadgeCount) {
|
|
app.setBadgeCount(count);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('minimize-window', () => {
|
|
mainWindow?.minimize();
|
|
});
|
|
|
|
ipcMain.on('maximize-window', () => {
|
|
if (mainWindow?.isMaximized()) {
|
|
mainWindow.unmaximize();
|
|
} else {
|
|
mainWindow?.maximize();
|
|
}
|
|
});
|
|
|
|
ipcMain.on('close-window', () => {
|
|
mainWindow?.close();
|
|
});
|
|
}
|
|
|
|
app.on('ready', () => {
|
|
registerIpcHandlers();
|
|
createWindow();
|
|
createTray();
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (process.platform !== 'darwin') {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
app.on('activate', () => {
|
|
if (mainWindow === null) {
|
|
createWindow();
|
|
} else {
|
|
mainWindow.show();
|
|
}
|
|
});
|
|
|
|
app.on('before-quit', () => {
|
|
isQuitting = true;
|
|
});
|