From 5437c28b25c04290d72821ec5aa4cba89c640054 Mon Sep 17 00:00:00 2001 From: rhysd Date: Mon, 17 Apr 2017 18:40:55 +0900 Subject: [PATCH] implement account switcher --- main/account_switcher.ts | 66 ++++++++++++++ main/app.ts | 32 ++++--- main/default_menu.ts | 189 +++++++++++++++++++++++++++++++++++++++ renderer/index.ts | 16 +++- renderer/ipc.ts | 2 +- typings/ipc.d.ts | 1 + 6 files changed, 290 insertions(+), 16 deletions(-) create mode 100644 main/account_switcher.ts create mode 100644 main/default_menu.ts diff --git a/main/account_switcher.ts b/main/account_switcher.ts new file mode 100644 index 0000000..c09213d --- /dev/null +++ b/main/account_switcher.ts @@ -0,0 +1,66 @@ +import {EventEmitter} from 'events'; +import {Menu, MenuItem, session} from 'electron'; +import log from './log'; +import {Account} from './config'; + +export function partitionForAccount(account: Account) { + return `persist:mstdn:${account.name}:${account.host}`; +} + +export default class AccountSwitcher extends EventEmitter { + accounts: Account[]; + current: Account; + + constructor(private win: Electron.BrowserWindow, accounts: Account[]) { + super(); + const submenu = [] as Electron.MenuItemOptions[]; + + for (const account of accounts.filter(a => a.name !== '')) { + let name = account.name; + if (!name.startsWith('@')) { + name = '@' + name; + } + name += '@' + account.host; + submenu.push({ + label: name, + type: 'radio', + checked: false, + click: () => this.switchAccountTo(account), + }); + } + + this.accounts = accounts; + if (accounts.length === 0) { + return; + } + + submenu[0].checked = true; + this.current = accounts[0]; + + const item = new MenuItem({ + label: 'Accounts', + type: 'submenu', + submenu, + }); + + const menu = Menu.getApplicationMenu(); + // Insert item before 'Help' + menu.insert(menu.items.length - 1, item); + Menu.setApplicationMenu(menu); + } + + switchAccountTo(account: Account) { + if (this.current.name === account.name && this.current.host === account.host) { + log.debug('Current account is already @' + account.name); + return; + } + log.debug('Switch to account', account); + this.emit('will-switch', account); + + log.error('TODO: Switch partition'); + + this.win.webContents.send('mstdn:change-account', account); + this.emit('did-switch', account); + this.current = account; + } +} diff --git a/main/app.ts b/main/app.ts index a7683ed..2acd6be 100644 --- a/main/app.ts +++ b/main/app.ts @@ -3,7 +3,9 @@ import {app, BrowserWindow, globalShortcut, Tray, shell, dialog, Menu} from 'ele import windowState = require('electron-window-state'); import * as menubar from 'menubar'; import log from './log'; -import {Config, Account} from './config'; +import {Config} from './config'; +import AccountSwitcher, {partitionForAccount} from './account_switcher'; +import defaultMenu from './default_menu'; const IS_DEBUG = process.env.NODE_ENV === 'development'; const IS_DARWIN = process.platform === 'darwin'; @@ -11,27 +13,26 @@ const APP_ICON = path.join(__dirname, '..', 'resources', 'icon', 'icon.png'); const PRELOAD_JS = path.join(__dirname, '..', 'renderer', 'preload.js'); export class App { - private account: Account; + private switcher: AccountSwitcher; constructor(private win: Electron.BrowserWindow, private config: Config) { if (config.accounts.length === 0) { throw new Error('No account found. Please check the config.'); } - this.account = this.config.accounts[0]; - } + if (IS_DARWIN) { + app.dock.setIcon(APP_ICON); + } + Menu.setApplicationMenu(defaultMenu()); + this.switcher = new AccountSwitcher(win, this.config.accounts); + this.switcher.on('did-switch', () => this.open()); - open() { if (!IS_DARWIN) { // Users can still access menu bar with pressing Alt key. this.win.setMenu(Menu.getApplicationMenu()); } - this.win.webContents.on('dom-ready', () => { - this.win.show(); - }); - this.win.loadURL(`https://${this.account.host}${this.account.default_page}`); this.win.webContents.on('will-navigate', (e, url) => { - if (!url.startsWith(`https://${this.account.host}`)) { + if (!url.startsWith(`https://${this.switcher.current.host}`)) { e.preventDefault(); shell.openExternal(url); } @@ -40,6 +41,11 @@ export class App { e.preventDefault(); shell.openExternal(url); }); + } + + open() { + this.win.loadURL(`https://${this.switcher.current.host}${this.switcher.current.default_page}`); + this.win.webContents.session.setPermissionRequestHandler((contents, permission, callback) => { if (permission !== 'geolocation' && permission !== 'media') { // Granted @@ -91,6 +97,7 @@ function startNormalWindow(config: Config): Promise { nodeIntegration: false, sandbox: true, preload: PRELOAD_JS, + partition: partitionForAccount(config.accounts[0]), }, }); win.once('ready-to-show', () => { @@ -142,7 +149,6 @@ function startNormalWindow(config: Config): Promise { tray.on('double-click', toggleWindow); if (IS_DARWIN) { tray.setHighlightMode('never'); - app.dock.setIcon(APP_ICON); } resolve(win); @@ -163,13 +169,15 @@ function startMenuBar(config: Config): Promise { height: state.height, alwaysOnTop: IS_DEBUG || !!config.always_on_top, tooltip: 'Mstdn', - autoHideMenuBar: true, useContentSize: true, + autoHideMenuBar: true, show: false, + showDockIcon: true, webPreferences: { nodeIntegration: false, sandbox: true, preload: PRELOAD_JS, + partition: partitionForAccount(config.accounts[0]), }, }); mb.once('ready', () => mb.showWindow()); diff --git a/main/default_menu.ts b/main/default_menu.ts new file mode 100644 index 0000000..fddf259 --- /dev/null +++ b/main/default_menu.ts @@ -0,0 +1,189 @@ +import * as path from 'path'; +import {Menu, shell, app} from 'electron'; + +export default function defaultMenu() { + const template: Electron.MenuItemOptions[] = [ + { + label: 'Edit', + submenu: [ + { + label: 'Edit Config', + click() { + shell.openItem(path.join(app.getPath('userData'), 'config.json')); + } + }, + { + type: 'separator' + }, + { + role: 'undo' + }, + { + role: 'redo' + }, + { + type: 'separator' + }, + { + role: 'cut' + }, + { + role: 'copy' + }, + { + role: 'paste' + }, + { + role: 'pasteandmatchstyle' + }, + { + role: 'delete' + }, + { + role: 'selectall' + } + ] + }, + { + label: 'View', + submenu: [ + { + role: 'reload' + }, + { + role: 'toggledevtools' + }, + { + type: 'separator' + }, + { + role: 'resetzoom' + }, + { + role: 'zoomin' + }, + { + role: 'zoomout' + }, + { + type: 'separator' + }, + { + role: 'togglefullscreen' + } + ] + }, + { + role: 'window', + submenu: [ + { + role: 'minimize' + }, + { + role: 'close' + } + ] + }, + { + role: 'help', + submenu: [ + { + label: 'Learn More', + click () { + shell.openExternal('https://github.com/rhysd/Mstdn#readme'); + } + }, + { + label: 'Search Issues', + click () { + shell.openExternal('https://github.com/rhysd/Mstdn/issues'); + } + } + ] + } + ]; + + if (process.platform === 'darwin') { + template.unshift({ + label: 'Mstdn', + submenu: [ + { + role: 'about' + }, + { + type: 'separator' + }, + { + role: 'services', + submenu: [] + }, + { + type: 'separator' + }, + { + role: 'hide' + }, + { + role: 'hideothers' + }, + { + role: 'unhide' + }, + { + type: 'separator' + }, + { + role: 'quit' + } + ] + }); + + (template[1].submenu as Electron.MenuItemOptions[]).push( + { + type: 'separator' + }, + { + label: 'Speech', + submenu: [ + { + role: 'startspeaking' + }, + { + role: 'stopspeaking' + } + ] + } + ); + + template[3].submenu = [ + { + role: 'close' + }, + { + role: 'minimize' + }, + { + role: 'zoom' + }, + { + type: 'separator' + }, + { + role: 'front' + } + ]; + } else { + template.unshift( + { + label: 'File', + submenu: [ + { + role: 'quit' + } + ] + } + ); + } + + return Menu.buildFromTemplate(template); +} diff --git a/renderer/index.ts b/renderer/index.ts index d9135a2..f4edf45 100644 --- a/renderer/index.ts +++ b/renderer/index.ts @@ -1,5 +1,5 @@ import * as Mousetrap from 'mousetrap'; -import {Config} from '../main/config'; +import {Config, Account} from '../main/config'; import * as Ipc from './ipc'; import log from './log'; @@ -52,8 +52,18 @@ function setupKeybinds(keybinds: {[key: string]: string}, host: string) { } } -Ipc.on('mstdn:config', (config: Config) => { - // TODO: Temporary. It should be fixed on supporting multi-account. +let config: Config | null = null; + +Ipc.on('mstdn:config', (c: Config) => { + config = c; const host = config.accounts[0].host; setupKeybinds(config.keymaps, host); }); + +Ipc.on('mstdn:change-account', (account: Account) => { + if (config === null) { + log.error('FATAL: config is null at receiving mstdn:change-account'); + return; + } + setupKeybinds(config.keymaps, account.host); +}) diff --git a/renderer/ipc.ts b/renderer/ipc.ts index a954c5c..b5389e8 100644 --- a/renderer/ipc.ts +++ b/renderer/ipc.ts @@ -5,7 +5,7 @@ const ipc = electron.ipcRenderer; export function on(channel: IpcChannel, callback: (...args: any[]) => void) { ipc.on(channel, (...args: any[]) => { - log.debug('IPC: Received from:', channel, args); + log.info('IPC: Received from:', channel, args); callback(...args); }); } diff --git a/typings/ipc.d.ts b/typings/ipc.d.ts index becc519..2312831 100644 --- a/typings/ipc.d.ts +++ b/typings/ipc.d.ts @@ -1,3 +1,4 @@ type IpcChannel = 'mstdn:config' + | 'mstdn:change-account' ;