1
0
mirror of https://github.com/rhysd/Mstdn.git synced 2025-02-01 05:52:11 +01:00

first main process logic

This commit is contained in:
rhysd 2017-04-15 16:32:44 +09:00
parent 8504fdf901
commit ed0a1682d3
5 changed files with 271 additions and 2 deletions

169
main/app.ts Normal file
View File

@ -0,0 +1,169 @@
import * as path from 'path';
import {app, BrowserWindow, globalShortcut, Tray} from 'electron';
import windowState = require('electron-window-state');
import * as menubar from 'menubar';
import log from './log';
import {Config} from './config'
const IS_DEBUG = process.env.NODE_ENV === 'development';
const IS_DARWIN = process.platform === 'darwin';
const APP_ICON = path.join(__dirname, '..', 'resources', 'icon.png');
const DEFAULT_WIDTH = 340;
const DEFAULT_HEIGHT = 400;
export class App {
private host: string;
constructor(private win: Electron.BrowserWindow, private config: Config) {
if (config.accounts.length === 0) {
throw new Error('No account found. Please check the config.');
}
this.host = this.config.accounts[0].host;
}
open() {
this.win.loadURL(`https://${this.host}`);
this.win.show();
}
}
function trayIcon(color: string) {
return path.join(__dirname, '..', 'resources', `tray-icon-${
color === 'white' ? 'white' : 'black'
}@2x.png`);
}
export default function startApp (config: Config) {
return (config.normal_window ? startNormalWindow : startMenuBar)(config)
.then(win => new App(win, config));
}
function startNormalWindow(config: Config): Promise<Electron.BrowserWindow> {
log.debug('Setup a normal window');
return new Promise<Electron.BrowserWindow>(resolve => {
const state = windowState({
defaultWidth: 600,
defaultHeight: 800,
});
const win = new BrowserWindow({
width: state.width,
height: state.height,
x: state.x,
y: state.y,
icon: APP_ICON,
show: false,
useContentSize: true,
autoHideMenuBar: true,
webPreferences: {
nodeIntegration: false,
sandbox: true,
},
});
win.once('ready-to-show', () => {
win.show();
});
win.once('closed', () => {
app.quit();
});
if (state.isFullScreen) {
win.setFullScreen(true);
} else if (state.isMaximized) {
win.maximize();
}
state.manage(win);
const toggleWindow = () => {
if (win.isFocused()) {
log.debug('Toggle window: shown -> hidden');
if (IS_DARWIN) {
app.hide();
} else {
win.hide();
}
} else {
log.debug('Toggle window: hidden -> shown');
win.show();
}
};
win.webContents.on('dom-ready', () => {
log.debug('Send config to renderer procress');
win.webContents.send('mstdn:config', config);
});
win.webContents.once('dom-ready', () => {
log.debug('Normal window application was launched');
if (config.hot_key) {
globalShortcut.register(config.hot_key, toggleWindow);
log.debug('Hot key was set to:', config.hot_key);
}
if (IS_DEBUG) {
win.webContents.openDevTools({mode: 'detach'});
}
resolve(win);
});
const normalIcon = trayIcon(config.icon_color);
const tray = new Tray(normalIcon);
tray.on('click', toggleWindow);
tray.on('double-click', toggleWindow);
if (IS_DARWIN) {
tray.setHighlightMode('never');
}
});
}
function startMenuBar(config: Config): Promise<Electron.BrowserWindow> {
log.debug('Setup a menubar window');
return new Promise<Electron.BrowserWindow>(resolve => {
const state = windowState({
defaultWidth: DEFAULT_WIDTH,
defaultHeight: DEFAULT_HEIGHT,
});
const icon = trayIcon(config.icon_color);
const mb = menubar({
icon,
width: state.width,
height: state.height,
alwaysOnTop: IS_DEBUG || !!config.always_on_top,
tooltip: 'Mstdn',
showDockIcon: true,
autoHideMenuBar: true,
useContentSize: true,
show: false,
webPreferences: {
nodeIntegration: false,
sandbox: true,
},
});
mb.once('ready', () => mb.showWindow());
mb.once('after-create-window', () => {
log.debug('Menubar application was launched');
if (config.hot_key) {
globalShortcut.register(config.hot_key, () => {
if (mb.window.isFocused()) {
log.debug('Toggle window: shown -> hidden');
mb.hideWindow();
} else {
log.debug('Toggle window: hidden -> shown');
mb.showWindow();
}
});
log.debug('Hot key was set to:', config.hot_key);
}
if (IS_DEBUG) {
mb.window.webContents.openDevTools({mode: 'detach'});
}
mb.window.webContents.on('dom-ready', () => {
log.debug('Send config to renderer procress');
mb.window.webContents.send('mstdn:config', config);
});
state.manage(mb.window);
resolve(mb.window);
});
mb.once('after-close', () => {
app.quit();
});
});
}

68
main/config.ts Normal file
View File

@ -0,0 +1,68 @@
import {app, systemPreferences} from 'electron';
import * as fs from 'fs';
import {join} from 'path';
import log from './log';
export interface Account {
host: string;
name: string;
}
export interface Config {
hot_key: string;
icon_color: string;
always_on_top: boolean;
normal_window: boolean;
zoom_factor: number;
accounts: Account[];
keymaps: {[key: string]: string};
}
function makeDefaultConfig(): Config {
const IsDarkMode = (process.platform === 'darwin') && systemPreferences.isDarkMode();
const menubarBroken = process.platform === 'win32';
return {
hot_key: 'CmdOrCtrl+Shift+S',
icon_color: IsDarkMode ? 'white' : 'black',
always_on_top: false,
normal_window: menubarBroken,
zoom_factor: 1.0,
accounts: [],
keymaps: {},
};
}
export default function loadConfig(): Promise<Config> {
return new Promise<Config>(resolve => {
const dir = app.getPath('userData');
const file = join(dir, 'config.json');
fs.readFile(file, 'utf8', (err, json) => {
if (err) {
log.info('Configuration file was not found, will create:', file);
const default_config = makeDefaultConfig();
// Note:
// If calling writeFile() directly here, it tries to create config file before Electron
// runtime creates data directory. As the result, writeFile() would fail to create a file.
if (app.isReady()) {
fs.writeFile(file, JSON.stringify(default_config, null, 2));
} else {
app.once('ready', () => fs.writeFile(file, JSON.stringify(default_config, null, 2)));
}
return resolve(default_config);
}
try {
const config = JSON.parse(json);
if (config.hot_key && config.hot_key.startsWith('mod+')) {
config.hot_key = `CmdOrCtrl+${config.hot_key.slice(4)}`;
}
log.debug('Configuration was loaded successfully', config);
resolve(config);
} catch (e) {
log.error('Error on loading JSON file, will load default configuration:', e.message);
resolve(makeDefaultConfig());
}
});
});
}

22
main/index.ts Normal file
View File

@ -0,0 +1,22 @@
import {app} from 'electron';
import log from './log';
import startApp from './app';
import loadConfig from './config';
const appReady = new Promise<void>(resolve => app.once('ready', resolve));
process.on('unhandledRejection', (reason: string) => {
log.error('FATAL: Unhandled rejection! Reason:', reason);
});
app.on('will-quit', () => {
log.debug('Application is quitting');
});
Promise.all([
loadConfig(),
appReady
]).then(([config, _]) => startApp(config)).then(win => {
win.open();
log.debug('Application launched!');
});

9
main/log.ts Normal file
View File

@ -0,0 +1,9 @@
import * as log from 'loglevel';
if (process.env.NODE_ENV === 'development') {
log.setLevel('debug');
} else {
log.setLevel('info');
}
export default log;

View File

@ -1,7 +1,7 @@
{
"name": "mstdn",
"productName": "Mstdn",
"version": "0.0.0",
"version": "0.0.1",
"description": "Tiny web-based mastodon client for your desktop",
"main": "main/index.js",
"bin": {
@ -35,6 +35,7 @@
"devDependencies": {
"@types/electron": "^1.4.35",
"@types/electron-window-state": "^2.0.28",
"@types/loglevel": "^1.4.29",
"@types/mousetrap": "^1.5.33",
"@types/node": "^7.0.12",
"electron-packager": "^8.6.0",
@ -46,7 +47,7 @@
"electron": "^1.6.2",
"electron-window-state": "^4.1.1",
"loglevel": "^1.4.1",
"menubar": "github:rhysd/menubar#rhysd-fixes",
"menubar": "github:rhysd/menubar#mstdn",
"mousetrap": "^1.6.1"
}
}