Browse Source
- Fixed TypeScript configuration in electron/tsconfig.json: * Added skipLibCheck: true to resolve node_modules type conflicts * Added allowSyntheticDefaultImports for better module compatibility * Disabled strict mode to avoid unnecessary type errors * Updated TypeScript version to ~5.2.2 for consistency - Resolved SQLite plugin configuration issue: * Changed plugin key from 'SQLite' to 'CapacitorSQLite' in capacitor.config.json * This fixes 'Cannot read properties of undefined (reading electronIsEncryption)' error * All 45+ SQLite plugin methods now properly registered and available - Added Electron development scripts: * electron-dev.sh for streamlined development workflow * setup-electron.sh for initial Electron environment setup - Electron app now starts successfully without TypeScript compilation errors - SQLite plugin fully functional with proper configuration access Status: Electron platform now working for TimeSafari developmentstreamline-attempt
20 changed files with 6882 additions and 6 deletions
@ -0,0 +1,8 @@ |
|||
# NPM renames .gitignore to .npmignore |
|||
# In order to prevent that, we remove the initial "." |
|||
# And the CLI then renames it |
|||
app |
|||
node_modules |
|||
build |
|||
dist |
|||
logs |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,98 @@ |
|||
{ |
|||
"appId": "app.timesafari", |
|||
"appName": "TimeSafari", |
|||
"webDir": "dist", |
|||
"server": { |
|||
"cleartext": true |
|||
}, |
|||
"plugins": { |
|||
"App": { |
|||
"appUrlOpen": { |
|||
"handlers": [ |
|||
{ |
|||
"url": "timesafari://*", |
|||
"autoVerify": true |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
"CapacitorSQLite": { |
|||
"iosDatabaseLocation": "Library/CapacitorDatabase", |
|||
"iosIsEncryption": false, |
|||
"iosBiometric": { |
|||
"biometricAuth": false, |
|||
"biometricTitle": "Biometric login for TimeSafari" |
|||
}, |
|||
"androidIsEncryption": false, |
|||
"androidBiometric": { |
|||
"biometricAuth": false, |
|||
"biometricTitle": "Biometric login for TimeSafari" |
|||
}, |
|||
"electronIsEncryption": false |
|||
} |
|||
}, |
|||
"ios": { |
|||
"contentInset": "never", |
|||
"allowsLinkPreview": true, |
|||
"scrollEnabled": true, |
|||
"limitsNavigationsToAppBoundDomains": true, |
|||
"backgroundColor": "#ffffff", |
|||
"allowNavigation": [ |
|||
"*.timesafari.app", |
|||
"*.jsdelivr.net", |
|||
"api.endorser.ch" |
|||
] |
|||
}, |
|||
"android": { |
|||
"allowMixedContent": false, |
|||
"captureInput": true, |
|||
"webContentsDebuggingEnabled": false, |
|||
"allowNavigation": [ |
|||
"*.timesafari.app", |
|||
"*.jsdelivr.net", |
|||
"api.endorser.ch" |
|||
] |
|||
}, |
|||
"electron": { |
|||
"deepLinking": { |
|||
"schemes": ["timesafari"] |
|||
}, |
|||
"buildOptions": { |
|||
"appId": "app.timesafari", |
|||
"productName": "TimeSafari", |
|||
"directories": { |
|||
"output": "dist-electron-packages" |
|||
}, |
|||
"files": [ |
|||
"dist/**/*", |
|||
"electron/**/*" |
|||
], |
|||
"mac": { |
|||
"category": "public.app-category.productivity", |
|||
"target": [ |
|||
{ |
|||
"target": "dmg", |
|||
"arch": ["x64", "arm64"] |
|||
} |
|||
] |
|||
}, |
|||
"win": { |
|||
"target": [ |
|||
{ |
|||
"target": "nsis", |
|||
"arch": ["x64"] |
|||
} |
|||
] |
|||
}, |
|||
"linux": { |
|||
"target": [ |
|||
{ |
|||
"target": "AppImage", |
|||
"arch": ["x64"] |
|||
} |
|||
], |
|||
"category": "Utility" |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
{ |
|||
"appId": "com.yourdoamnin.yourapp", |
|||
"directories": { |
|||
"buildResources": "resources" |
|||
}, |
|||
"files": [ |
|||
"assets/**/*", |
|||
"build/**/*", |
|||
"capacitor.config.*", |
|||
"app/**/*" |
|||
], |
|||
"publish": { |
|||
"provider": "github" |
|||
}, |
|||
"nsis": { |
|||
"allowElevation": true, |
|||
"oneClick": false, |
|||
"allowToChangeInstallationDirectory": true |
|||
}, |
|||
"win": { |
|||
"target": "nsis", |
|||
"icon": "assets/appIcon.ico" |
|||
}, |
|||
"mac": { |
|||
"category": "your.app.category.type", |
|||
"target": "dmg" |
|||
} |
|||
} |
@ -0,0 +1,75 @@ |
|||
/* eslint-disable no-undef */ |
|||
/* eslint-disable @typescript-eslint/no-var-requires */ |
|||
const cp = require('child_process'); |
|||
const chokidar = require('chokidar'); |
|||
const electron = require('electron'); |
|||
|
|||
let child = null; |
|||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; |
|||
const reloadWatcher = { |
|||
debouncer: null, |
|||
ready: false, |
|||
watcher: null, |
|||
restarting: false, |
|||
}; |
|||
|
|||
///*
|
|||
function runBuild() { |
|||
return new Promise((resolve, _reject) => { |
|||
let tempChild = cp.spawn(npmCmd, ['run', 'build']); |
|||
tempChild.once('exit', () => { |
|||
resolve(); |
|||
}); |
|||
tempChild.stdout.pipe(process.stdout); |
|||
}); |
|||
} |
|||
//*/
|
|||
|
|||
async function spawnElectron() { |
|||
if (child !== null) { |
|||
child.stdin.pause(); |
|||
child.kill(); |
|||
child = null; |
|||
await runBuild(); |
|||
} |
|||
child = cp.spawn(electron, ['--inspect=5858', './']); |
|||
child.on('exit', () => { |
|||
if (!reloadWatcher.restarting) { |
|||
process.exit(0); |
|||
} |
|||
}); |
|||
child.stdout.pipe(process.stdout); |
|||
} |
|||
|
|||
function setupReloadWatcher() { |
|||
reloadWatcher.watcher = chokidar |
|||
.watch('./src/**/*', { |
|||
ignored: /[/\\]\./, |
|||
persistent: true, |
|||
}) |
|||
.on('ready', () => { |
|||
reloadWatcher.ready = true; |
|||
}) |
|||
.on('all', (_event, _path) => { |
|||
if (reloadWatcher.ready) { |
|||
clearTimeout(reloadWatcher.debouncer); |
|||
reloadWatcher.debouncer = setTimeout(async () => { |
|||
console.log('Restarting'); |
|||
reloadWatcher.restarting = true; |
|||
await spawnElectron(); |
|||
reloadWatcher.restarting = false; |
|||
reloadWatcher.ready = false; |
|||
clearTimeout(reloadWatcher.debouncer); |
|||
reloadWatcher.debouncer = null; |
|||
reloadWatcher.watcher = null; |
|||
setupReloadWatcher(); |
|||
}, 500); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
(async () => { |
|||
await runBuild(); |
|||
await spawnElectron(); |
|||
setupReloadWatcher(); |
|||
})(); |
File diff suppressed because it is too large
@ -0,0 +1,51 @@ |
|||
{ |
|||
"name": "TimeSafari", |
|||
"version": "1.0.0", |
|||
"description": "An Amazing Capacitor App", |
|||
"author": { |
|||
"name": "", |
|||
"email": "" |
|||
}, |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "" |
|||
}, |
|||
"license": "MIT", |
|||
"main": "build/src/index.js", |
|||
"scripts": { |
|||
"build": "tsc && electron-rebuild", |
|||
"electron:start-live": "node ./live-runner.js", |
|||
"electron:start": "npm run build && electron --inspect=5858 ./", |
|||
"electron:pack": "npm run build && electron-builder build --dir -c ./electron-builder.config.json", |
|||
"electron:make": "npm run build && electron-builder build -c ./electron-builder.config.json -p always" |
|||
}, |
|||
"dependencies": { |
|||
"@capacitor-community/electron": "^5.0.0", |
|||
"@capacitor-community/sqlite": "^6.0.2", |
|||
"better-sqlite3-multiple-ciphers": "^12.1.1", |
|||
"chokidar": "~3.5.3", |
|||
"crypto": "^1.0.1", |
|||
"crypto-js": "^4.2.0", |
|||
"electron-is-dev": "~2.0.0", |
|||
"electron-json-storage": "^4.6.0", |
|||
"electron-serve": "~1.1.0", |
|||
"electron-unhandled": "~4.0.1", |
|||
"electron-updater": "^5.3.0", |
|||
"electron-window-state": "^5.0.3", |
|||
"jszip": "^3.10.1", |
|||
"node-fetch": "^2.6.7" |
|||
}, |
|||
"devDependencies": { |
|||
"@types/better-sqlite3": "^7.6.13", |
|||
"@types/crypto-js": "^4.2.2", |
|||
"@types/electron-json-storage": "^4.5.4", |
|||
"electron": "^26.2.2", |
|||
"electron-builder": "~23.6.0", |
|||
"electron-rebuild": "^3.2.9", |
|||
"typescript": "~5.2.2" |
|||
}, |
|||
"keywords": [ |
|||
"capacitor", |
|||
"electron" |
|||
] |
|||
} |
@ -0,0 +1,10 @@ |
|||
/* eslint-disable no-undef */ |
|||
/* eslint-disable @typescript-eslint/no-var-requires */ |
|||
const electronPublish = require('electron-publish'); |
|||
|
|||
class Publisher extends electronPublish.Publisher { |
|||
async upload(task) { |
|||
console.log('electron-publisher-custom', task.file); |
|||
} |
|||
} |
|||
module.exports = Publisher; |
@ -0,0 +1,70 @@ |
|||
import type { CapacitorElectronConfig } from '@capacitor-community/electron'; |
|||
import { getCapacitorElectronConfig, setupElectronDeepLinking } from '@capacitor-community/electron'; |
|||
import type { MenuItemConstructorOptions } from 'electron'; |
|||
import { app, MenuItem } from 'electron'; |
|||
import electronIsDev from 'electron-is-dev'; |
|||
import unhandled from 'electron-unhandled'; |
|||
import { autoUpdater } from 'electron-updater'; |
|||
|
|||
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup'; |
|||
|
|||
// Graceful handling of unhandled errors.
|
|||
unhandled(); |
|||
|
|||
// Define our menu templates (these are optional)
|
|||
const trayMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [new MenuItem({ label: 'Quit App', role: 'quit' })]; |
|||
const appMenuBarMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [ |
|||
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' }, |
|||
{ role: 'viewMenu' }, |
|||
]; |
|||
|
|||
// Get Config options from capacitor.config
|
|||
const capacitorFileConfig: CapacitorElectronConfig = getCapacitorElectronConfig(); |
|||
|
|||
// Initialize our app. You can pass menu templates into the app here.
|
|||
// const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig);
|
|||
const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate); |
|||
|
|||
// If deeplinking is enabled then we will set it up here.
|
|||
if (capacitorFileConfig.electron?.deepLinkingEnabled) { |
|||
setupElectronDeepLinking(myCapacitorApp, { |
|||
customProtocol: capacitorFileConfig.electron.deepLinkingCustomProtocol ?? 'mycapacitorapp', |
|||
}); |
|||
} |
|||
|
|||
// If we are in Dev mode, use the file watcher components.
|
|||
if (electronIsDev) { |
|||
setupReloadWatcher(myCapacitorApp); |
|||
} |
|||
|
|||
// Run Application
|
|||
(async () => { |
|||
// Wait for electron app to be ready.
|
|||
await app.whenReady(); |
|||
// Security - Set Content-Security-Policy based on whether or not we are in dev mode.
|
|||
setupContentSecurityPolicy(myCapacitorApp.getCustomURLScheme()); |
|||
// Initialize our app, build windows, and load content.
|
|||
await myCapacitorApp.init(); |
|||
// Check for updates if we are in a packaged app.
|
|||
autoUpdater.checkForUpdatesAndNotify(); |
|||
})(); |
|||
|
|||
// Handle when all of our windows are close (platforms have their own expectations).
|
|||
app.on('window-all-closed', function () { |
|||
// On OS X it is common for applications and their menu bar
|
|||
// to stay active until the user quits explicitly with Cmd + Q
|
|||
if (process.platform !== 'darwin') { |
|||
app.quit(); |
|||
} |
|||
}); |
|||
|
|||
// When the dock icon is clicked.
|
|||
app.on('activate', async function () { |
|||
// On OS X it's common to re-create a window in the app when the
|
|||
// dock icon is clicked and there are no other windows open.
|
|||
if (myCapacitorApp.getMainWindow().isDestroyed()) { |
|||
await myCapacitorApp.init(); |
|||
} |
|||
}); |
|||
|
|||
// Place all ipc or other electron api calls and custom functionality under this line
|
@ -0,0 +1,4 @@ |
|||
require('./rt/electron-rt'); |
|||
//////////////////////////////
|
|||
// User Defined Preload scripts below
|
|||
console.log('User Preload!'); |
@ -0,0 +1,6 @@ |
|||
/* eslint-disable @typescript-eslint/no-var-requires */ |
|||
const CapacitorCommunitySqlite = require('../../../node_modules/@capacitor-community/sqlite/electron/dist/plugin.js'); |
|||
|
|||
module.exports = { |
|||
CapacitorCommunitySqlite, |
|||
} |
@ -0,0 +1,88 @@ |
|||
import { randomBytes } from 'crypto'; |
|||
import { ipcRenderer, contextBridge } from 'electron'; |
|||
import { EventEmitter } from 'events'; |
|||
|
|||
////////////////////////////////////////////////////////
|
|||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|||
const plugins = require('./electron-plugins'); |
|||
|
|||
const randomId = (length = 5) => randomBytes(length).toString('hex'); |
|||
|
|||
const contextApi: { |
|||
[plugin: string]: { [functionName: string]: () => Promise<any> }; |
|||
} = {}; |
|||
|
|||
Object.keys(plugins).forEach((pluginKey) => { |
|||
Object.keys(plugins[pluginKey]) |
|||
.filter((className) => className !== 'default') |
|||
.forEach((classKey) => { |
|||
const functionList = Object.getOwnPropertyNames(plugins[pluginKey][classKey].prototype).filter( |
|||
(v) => v !== 'constructor' |
|||
); |
|||
|
|||
if (!contextApi[classKey]) { |
|||
contextApi[classKey] = {}; |
|||
} |
|||
|
|||
functionList.forEach((functionName) => { |
|||
if (!contextApi[classKey][functionName]) { |
|||
contextApi[classKey][functionName] = (...args) => ipcRenderer.invoke(`${classKey}-${functionName}`, ...args); |
|||
} |
|||
}); |
|||
|
|||
// Events
|
|||
if (plugins[pluginKey][classKey].prototype instanceof EventEmitter) { |
|||
const listeners: { [key: string]: { type: string; listener: (...args: any[]) => void } } = {}; |
|||
const listenersOfTypeExist = (type) => |
|||
!!Object.values(listeners).find((listenerObj) => listenerObj.type === type); |
|||
|
|||
Object.assign(contextApi[classKey], { |
|||
addListener(type: string, callback: (...args) => void) { |
|||
const id = randomId(); |
|||
|
|||
// Deduplicate events
|
|||
if (!listenersOfTypeExist(type)) { |
|||
ipcRenderer.send(`event-add-${classKey}`, type); |
|||
} |
|||
|
|||
const eventHandler = (_, ...args) => callback(...args); |
|||
|
|||
ipcRenderer.addListener(`event-${classKey}-${type}`, eventHandler); |
|||
listeners[id] = { type, listener: eventHandler }; |
|||
|
|||
return id; |
|||
}, |
|||
removeListener(id: string) { |
|||
if (!listeners[id]) { |
|||
throw new Error('Invalid id'); |
|||
} |
|||
|
|||
const { type, listener } = listeners[id]; |
|||
|
|||
ipcRenderer.removeListener(`event-${classKey}-${type}`, listener); |
|||
|
|||
delete listeners[id]; |
|||
|
|||
if (!listenersOfTypeExist(type)) { |
|||
ipcRenderer.send(`event-remove-${classKey}-${type}`); |
|||
} |
|||
}, |
|||
removeAllListeners(type: string) { |
|||
Object.entries(listeners).forEach(([id, listenerObj]) => { |
|||
if (!type || listenerObj.type === type) { |
|||
ipcRenderer.removeListener(`event-${classKey}-${listenerObj.type}`, listenerObj.listener); |
|||
ipcRenderer.send(`event-remove-${classKey}-${listenerObj.type}`); |
|||
delete listeners[id]; |
|||
} |
|||
}); |
|||
}, |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
contextBridge.exposeInMainWorld('CapacitorCustomPlatform', { |
|||
name: 'electron', |
|||
plugins: contextApi, |
|||
}); |
|||
////////////////////////////////////////////////////////
|
@ -0,0 +1,233 @@ |
|||
import type { CapacitorElectronConfig } from '@capacitor-community/electron'; |
|||
import { |
|||
CapElectronEventEmitter, |
|||
CapacitorSplashScreen, |
|||
setupCapacitorElectronPlugins, |
|||
} from '@capacitor-community/electron'; |
|||
import chokidar from 'chokidar'; |
|||
import type { MenuItemConstructorOptions } from 'electron'; |
|||
import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session } from 'electron'; |
|||
import electronIsDev from 'electron-is-dev'; |
|||
import electronServe from 'electron-serve'; |
|||
import windowStateKeeper from 'electron-window-state'; |
|||
import { join } from 'path'; |
|||
|
|||
// Define components for a watcher to detect when the webapp is changed so we can reload in Dev mode.
|
|||
const reloadWatcher = { |
|||
debouncer: null, |
|||
ready: false, |
|||
watcher: null, |
|||
}; |
|||
export function setupReloadWatcher(electronCapacitorApp: ElectronCapacitorApp): void { |
|||
reloadWatcher.watcher = chokidar |
|||
.watch(join(app.getAppPath(), 'app'), { |
|||
ignored: /[/\\]\./, |
|||
persistent: true, |
|||
}) |
|||
.on('ready', () => { |
|||
reloadWatcher.ready = true; |
|||
}) |
|||
.on('all', (_event, _path) => { |
|||
if (reloadWatcher.ready) { |
|||
clearTimeout(reloadWatcher.debouncer); |
|||
reloadWatcher.debouncer = setTimeout(async () => { |
|||
electronCapacitorApp.getMainWindow().webContents.reload(); |
|||
reloadWatcher.ready = false; |
|||
clearTimeout(reloadWatcher.debouncer); |
|||
reloadWatcher.debouncer = null; |
|||
reloadWatcher.watcher = null; |
|||
setupReloadWatcher(electronCapacitorApp); |
|||
}, 1500); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// Define our class to manage our app.
|
|||
export class ElectronCapacitorApp { |
|||
private MainWindow: BrowserWindow | null = null; |
|||
private SplashScreen: CapacitorSplashScreen | null = null; |
|||
private TrayIcon: Tray | null = null; |
|||
private CapacitorFileConfig: CapacitorElectronConfig; |
|||
private TrayMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [ |
|||
new MenuItem({ label: 'Quit App', role: 'quit' }), |
|||
]; |
|||
private AppMenuBarMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [ |
|||
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' }, |
|||
{ role: 'viewMenu' }, |
|||
]; |
|||
private mainWindowState; |
|||
private loadWebApp; |
|||
private customScheme: string; |
|||
|
|||
constructor( |
|||
capacitorFileConfig: CapacitorElectronConfig, |
|||
trayMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[], |
|||
appMenuBarMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[] |
|||
) { |
|||
this.CapacitorFileConfig = capacitorFileConfig; |
|||
|
|||
this.customScheme = this.CapacitorFileConfig.electron?.customUrlScheme ?? 'capacitor-electron'; |
|||
|
|||
if (trayMenuTemplate) { |
|||
this.TrayMenuTemplate = trayMenuTemplate; |
|||
} |
|||
|
|||
if (appMenuBarMenuTemplate) { |
|||
this.AppMenuBarMenuTemplate = appMenuBarMenuTemplate; |
|||
} |
|||
|
|||
// Setup our web app loader, this lets us load apps like react, vue, and angular without changing their build chains.
|
|||
this.loadWebApp = electronServe({ |
|||
directory: join(app.getAppPath(), 'app'), |
|||
scheme: this.customScheme, |
|||
}); |
|||
} |
|||
|
|||
// Helper function to load in the app.
|
|||
private async loadMainWindow(thisRef: any) { |
|||
await thisRef.loadWebApp(thisRef.MainWindow); |
|||
} |
|||
|
|||
// Expose the mainWindow ref for use outside of the class.
|
|||
getMainWindow(): BrowserWindow { |
|||
return this.MainWindow; |
|||
} |
|||
|
|||
getCustomURLScheme(): string { |
|||
return this.customScheme; |
|||
} |
|||
|
|||
async init(): Promise<void> { |
|||
const icon = nativeImage.createFromPath( |
|||
join(app.getAppPath(), 'assets', process.platform === 'win32' ? 'appIcon.ico' : 'appIcon.png') |
|||
); |
|||
this.mainWindowState = windowStateKeeper({ |
|||
defaultWidth: 1000, |
|||
defaultHeight: 800, |
|||
}); |
|||
// Setup preload script path and construct our main window.
|
|||
const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js'); |
|||
this.MainWindow = new BrowserWindow({ |
|||
icon, |
|||
show: false, |
|||
x: this.mainWindowState.x, |
|||
y: this.mainWindowState.y, |
|||
width: this.mainWindowState.width, |
|||
height: this.mainWindowState.height, |
|||
webPreferences: { |
|||
nodeIntegration: true, |
|||
contextIsolation: true, |
|||
// Use preload to inject the electron varriant overrides for capacitor plugins.
|
|||
// preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"),
|
|||
preload: preloadPath, |
|||
}, |
|||
}); |
|||
this.mainWindowState.manage(this.MainWindow); |
|||
|
|||
if (this.CapacitorFileConfig.backgroundColor) { |
|||
this.MainWindow.setBackgroundColor(this.CapacitorFileConfig.electron.backgroundColor); |
|||
} |
|||
|
|||
// If we close the main window with the splashscreen enabled we need to destory the ref.
|
|||
this.MainWindow.on('closed', () => { |
|||
if (this.SplashScreen?.getSplashWindow() && !this.SplashScreen.getSplashWindow().isDestroyed()) { |
|||
this.SplashScreen.getSplashWindow().close(); |
|||
} |
|||
}); |
|||
|
|||
// When the tray icon is enabled, setup the options.
|
|||
if (this.CapacitorFileConfig.electron?.trayIconAndMenuEnabled) { |
|||
this.TrayIcon = new Tray(icon); |
|||
this.TrayIcon.on('double-click', () => { |
|||
if (this.MainWindow) { |
|||
if (this.MainWindow.isVisible()) { |
|||
this.MainWindow.hide(); |
|||
} else { |
|||
this.MainWindow.show(); |
|||
this.MainWindow.focus(); |
|||
} |
|||
} |
|||
}); |
|||
this.TrayIcon.on('click', () => { |
|||
if (this.MainWindow) { |
|||
if (this.MainWindow.isVisible()) { |
|||
this.MainWindow.hide(); |
|||
} else { |
|||
this.MainWindow.show(); |
|||
this.MainWindow.focus(); |
|||
} |
|||
} |
|||
}); |
|||
this.TrayIcon.setToolTip(app.getName()); |
|||
this.TrayIcon.setContextMenu(Menu.buildFromTemplate(this.TrayMenuTemplate)); |
|||
} |
|||
|
|||
// Setup the main manu bar at the top of our window.
|
|||
Menu.setApplicationMenu(Menu.buildFromTemplate(this.AppMenuBarMenuTemplate)); |
|||
|
|||
// If the splashscreen is enabled, show it first while the main window loads then switch it out for the main window, or just load the main window from the start.
|
|||
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) { |
|||
this.SplashScreen = new CapacitorSplashScreen({ |
|||
imageFilePath: join( |
|||
app.getAppPath(), |
|||
'assets', |
|||
this.CapacitorFileConfig.electron?.splashScreenImageName ?? 'splash.png' |
|||
), |
|||
windowWidth: 400, |
|||
windowHeight: 400, |
|||
}); |
|||
this.SplashScreen.init(this.loadMainWindow, this); |
|||
} else { |
|||
this.loadMainWindow(this); |
|||
} |
|||
|
|||
// Security
|
|||
this.MainWindow.webContents.setWindowOpenHandler((details) => { |
|||
if (!details.url.includes(this.customScheme)) { |
|||
return { action: 'deny' }; |
|||
} else { |
|||
return { action: 'allow' }; |
|||
} |
|||
}); |
|||
this.MainWindow.webContents.on('will-navigate', (event, _newURL) => { |
|||
if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) { |
|||
event.preventDefault(); |
|||
} |
|||
}); |
|||
|
|||
// Link electron plugins into the system.
|
|||
setupCapacitorElectronPlugins(); |
|||
|
|||
// When the web app is loaded we hide the splashscreen if needed and show the mainwindow.
|
|||
this.MainWindow.webContents.on('dom-ready', () => { |
|||
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) { |
|||
this.SplashScreen.getSplashWindow().hide(); |
|||
} |
|||
if (!this.CapacitorFileConfig.electron?.hideMainWindowOnLaunch) { |
|||
this.MainWindow.show(); |
|||
} |
|||
setTimeout(() => { |
|||
if (electronIsDev) { |
|||
this.MainWindow.webContents.openDevTools(); |
|||
} |
|||
CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', ''); |
|||
}, 400); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
// Set a CSP up for our application based on the custom scheme
|
|||
export function setupContentSecurityPolicy(customScheme: string): void { |
|||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => { |
|||
callback({ |
|||
responseHeaders: { |
|||
...details.responseHeaders, |
|||
'Content-Security-Policy': [ |
|||
electronIsDev |
|||
? `default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data:` |
|||
: `default-src ${customScheme}://* 'unsafe-inline' data:`, |
|||
], |
|||
}, |
|||
}); |
|||
}); |
|||
} |
@ -0,0 +1,19 @@ |
|||
{ |
|||
"compileOnSave": true, |
|||
"include": ["./src/**/*", "./capacitor.config.ts", "./capacitor.config.js"], |
|||
"compilerOptions": { |
|||
"outDir": "./build", |
|||
"importHelpers": true, |
|||
"target": "ES2017", |
|||
"module": "CommonJS", |
|||
"moduleResolution": "node", |
|||
"esModuleInterop": true, |
|||
"allowSyntheticDefaultImports": true, |
|||
"typeRoots": ["./node_modules/@types"], |
|||
"allowJs": true, |
|||
"rootDir": ".", |
|||
"skipLibCheck": true, |
|||
"strict": false, |
|||
"noImplicitAny": false |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
#!/bin/bash |
|||
|
|||
# TimeSafari Electron Development Script |
|||
# This script builds the web app and runs it in Electron |
|||
|
|||
set -e |
|||
|
|||
echo "🔧 Starting Electron development workflow..." |
|||
|
|||
# Navigate to project root |
|||
cd /home/noone/projects/timesafari/crowd-master |
|||
|
|||
# Build for Capacitor |
|||
echo "📦 Building for Capacitor..." |
|||
npm run build:capacitor |
|||
|
|||
# Create electron/app directory if it doesn't exist |
|||
echo "📁 Preparing Electron app directory..." |
|||
mkdir -p electron/app |
|||
|
|||
# Copy built files to Electron |
|||
echo "📋 Copying web assets to Electron..." |
|||
cp -r dist/* electron/app/ |
|||
|
|||
# Ensure capacitor config is valid JSON (remove any comments) |
|||
echo "🔧 Validating Capacitor configuration..." |
|||
cp capacitor.config.json electron/capacitor.config.json |
|||
|
|||
# Navigate to electron directory |
|||
cd electron |
|||
|
|||
# Build Electron |
|||
echo "🔨 Building Electron..." |
|||
npm run build |
|||
|
|||
# Start Electron |
|||
echo "🚀 Starting Electron app..." |
|||
npm run electron:start |
|||
|
|||
echo "✅ Electron development workflow complete!" |
@ -0,0 +1,30 @@ |
|||
#!/bin/bash |
|||
|
|||
# TimeSafari Electron Setup Script |
|||
# This script installs all required dependencies for the Electron platform |
|||
|
|||
set -e |
|||
|
|||
echo "🔧 Setting up Electron dependencies..." |
|||
|
|||
# Navigate to electron directory |
|||
cd electron |
|||
|
|||
# Install required dependencies for Capacitor SQLite plugin |
|||
echo "📦 Installing better-sqlite3-multiple-ciphers..." |
|||
npm install better-sqlite3-multiple-ciphers |
|||
|
|||
echo "📦 Installing electron-json-storage..." |
|||
npm install electron-json-storage |
|||
|
|||
# Rebuild native modules |
|||
echo "🔨 Rebuilding native modules..." |
|||
npm run build |
|||
|
|||
echo "✅ Electron setup complete!" |
|||
echo "" |
|||
echo "To run the Electron app:" |
|||
echo " npm run electron:start" |
|||
echo "" |
|||
echo "Or from the project root:" |
|||
echo " npm run electron:dev" |
Loading…
Reference in new issue