mirror of
https://github.com/anyproto/anytype-ts.git
synced 2025-06-08 05:57:02 +09:00
567 lines
17 KiB
JavaScript
567 lines
17 KiB
JavaScript
const { app, shell, Menu, Tray } = require('electron');
|
|
const { is } = require('electron-util');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const ConfigManager = require('./config.js');
|
|
const Util = require('./util.js');
|
|
const Separator = { type: 'separator' };
|
|
|
|
const DEFAULT_SHORTCUTS = {
|
|
createObject: [ 'CmdOrCtrl', 'N' ],
|
|
undo: [ 'CmdOrCtrl', 'Z' ],
|
|
redo: [ 'CmdOrCtrl', 'Shift', 'Z' ],
|
|
selectAll: [ 'CmdOrCtrl', 'A' ],
|
|
searchText: [ 'CmdOrCtrl', 'F' ],
|
|
print: [ 'CmdOrCtrl', 'P' ],
|
|
newWindow: [ 'CmdOrCtrl', 'Shift', 'N' ],
|
|
zoomIn: [ 'CmdOrCtrl', '=' ],
|
|
zoomOut: [ 'CmdOrCtrl', '-' ],
|
|
zoomReset: [ 'CmdOrCtrl', '0' ],
|
|
toggleFullscreen: [ 'CmdOrCtrl', 'Shift', 'F' ],
|
|
shortcut: [ 'Ctrl', 'Space' ],
|
|
};
|
|
|
|
class MenuManager {
|
|
|
|
win = null;
|
|
menu = null;
|
|
tray = null;
|
|
store = null;
|
|
|
|
setWindow (win) {
|
|
this.win = win;
|
|
};
|
|
|
|
initShortcuts () {
|
|
this.shortcuts = this.store.get('shortcuts') || {};
|
|
};
|
|
|
|
getAccelerator (id) {
|
|
let keys = this.shortcuts[id];
|
|
|
|
if (undefined === keys) {
|
|
return (DEFAULT_SHORTCUTS[id] || []).join('+');
|
|
};
|
|
|
|
keys = keys || [];
|
|
|
|
const ret = [];
|
|
for (const key of keys) {
|
|
if (key == 'ctrl') {
|
|
ret.push('CmdOrCtrl');
|
|
} else
|
|
if (key == 'shift') {
|
|
ret.push('Shift');
|
|
} else
|
|
if (key == 'alt') {
|
|
ret.push('Alt');
|
|
} else {
|
|
ret.push(key.toUpperCase());
|
|
};
|
|
};
|
|
return ret.join('+');
|
|
};
|
|
|
|
initMenu () {
|
|
this.initShortcuts();
|
|
|
|
const { config } = ConfigManager;
|
|
const Api = require('./api.js');
|
|
const WindowManager = require('./window.js');
|
|
const UpdateManager = require('./update.js');
|
|
const isAllowedUpdate = UpdateManager.isAllowed();
|
|
|
|
config.debug = config.debug || {};
|
|
config.flagsMw = config.flagsMw || {};
|
|
|
|
const menuParam = [
|
|
{
|
|
label: 'Anytype',
|
|
submenu: [
|
|
{ label: Util.translate('electronMenuAbout'), click: () => Util.send(this.win, 'popup', 'about', {}, true) },
|
|
|
|
Separator,
|
|
|
|
{ role: 'hide', label: Util.translate('electronMenuHide') },
|
|
{ role: 'hideothers', label: Util.translate('electronMenuHideOthers') },
|
|
{ role: 'unhide', label: Util.translate('electronMenuUnhide') },
|
|
|
|
{ type: 'separator', visible: isAllowedUpdate },
|
|
{ label: Util.translate('electronMenuCheckUpdates'), click: () => Api.updateCheck(this.win), visible: isAllowedUpdate },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('commonSettings'), submenu: this.menuSettings() },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuQuit'), accelerator: 'CmdOrCtrl+Q', click: () => Api.exit(this.win, false) },
|
|
]
|
|
},
|
|
{
|
|
role: 'fileMenu', label: Util.translate('electronMenuFile'),
|
|
submenu: [
|
|
{ label: Util.translate('commonNewObject'), accelerator: this.getAccelerator('createObject'), click: () => Util.send(this.win, 'commandGlobal', 'createObject') },
|
|
{ label: Util.translate('commonNewSpace'), click: () => Util.send(this.win, 'commandGlobal', 'createSpace') },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuImport'), click: () => this.openSettings('importIndex') },
|
|
{ label: Util.translate('electronMenuExport'), click: () => this.openSettings('exportIndex') },
|
|
{ label: Util.translate('electronMenuSaveAs'), click: () => Util.send(this.win, 'commandGlobal', 'save') },
|
|
|
|
Separator,
|
|
|
|
{
|
|
label: Util.translate('electronMenuOpen'), submenu: [
|
|
{ label: Util.translate('electronMenuWorkDirectory'), click: () => shell.openPath(Util.userPath()) },
|
|
{ label: Util.translate('electronMenuDataDirectory'), click: () => shell.openPath(Util.dataPath()) },
|
|
{ label: Util.translate('electronMenuConfigDirectory'), click: () => shell.openPath(Util.defaultUserDataPath()) },
|
|
{ label: Util.translate('electronMenuLogsDirectory'), click: () => shell.openPath(Util.logPath()) },
|
|
{
|
|
label: Util.translate('electronMenuCustomCss'),
|
|
click: () => {
|
|
const fp = path.join(Util.userPath(), 'custom.css');
|
|
|
|
if (!fs.existsSync(fp)) {
|
|
fs.writeFileSync(fp, '');
|
|
};
|
|
|
|
shell.openPath(fp);
|
|
},
|
|
},
|
|
]
|
|
},
|
|
|
|
Separator,
|
|
|
|
{
|
|
label: Util.translate('electronMenuApplyCustomCss'), type: 'checkbox', checked: !config.disableCss,
|
|
click: () => {
|
|
config.disableCss = !config.disableCss;
|
|
Api.setConfig(this.win, { disableCss: config.disableCss }, () => {
|
|
WindowManager.reloadAll();
|
|
});
|
|
},
|
|
},
|
|
|
|
Separator,
|
|
|
|
{ role: 'close', label: Util.translate('electronMenuClose') },
|
|
]
|
|
},
|
|
{
|
|
label: Util.translate('electronMenuEdit'),
|
|
submenu: [
|
|
{
|
|
label: Util.translate('electronMenuUndo'), accelerator: this.getAccelerator('undo'),
|
|
click: () => {
|
|
if (this.win) {
|
|
this.win.webContents.undo();
|
|
Util.send(this.win, 'commandGlobal', 'undo');
|
|
};
|
|
}
|
|
},
|
|
{
|
|
label: Util.translate('electronMenuRedo'), accelerator: this.getAccelerator('redo'),
|
|
click: () => {
|
|
if (this.win) {
|
|
this.win.webContents.redo();
|
|
Util.send(this.win, 'commandGlobal', 'redo');
|
|
};
|
|
}
|
|
},
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuCopy'), role: 'copy' },
|
|
{ label: Util.translate('electronMenuCut'), role: 'cut' },
|
|
{ label: Util.translate('electronMenuPaste'), role: 'paste' },
|
|
{
|
|
label: Util.translate('electronMenuPastePlain'), accelerator: 'CmdOrCtrl+Shift+V',
|
|
click: () => {
|
|
if (is.macos) {
|
|
Util.send(this.win, 'commandEditor', 'pastePlain');
|
|
};
|
|
},
|
|
},
|
|
|
|
Separator,
|
|
|
|
{
|
|
label: Util.translate('electronMenuSelectAll'), accelerator: this.getAccelerator('selectAll'),
|
|
click: () => {
|
|
if (this.win) {
|
|
this.win.webContents.selectAll();
|
|
Util.send(this.win, 'commandEditor', 'selectAll');
|
|
};
|
|
}
|
|
},
|
|
{ label: Util.translate('electronMenuSearch'), accelerator: this.getAccelerator('searchText'), click: () => Util.send(this.win, 'commandGlobal', 'search') },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuPrint'), accelerator: this.getAccelerator('print'), click: () => Util.send(this.win, 'commandGlobal', 'print') },
|
|
]
|
|
},
|
|
{
|
|
role: 'windowMenu', label: Util.translate('electronMenuWindow'),
|
|
submenu: [
|
|
{ label: Util.translate('electronMenuNewWindow'), accelerator: this.getAccelerator('newWindow'), click: () => WindowManager.createMain({ isChild: true }) },
|
|
|
|
Separator,
|
|
|
|
{ role: 'minimize', label: Util.translate('electronMenuMinimise') },
|
|
{ label: Util.translate('electronMenuZoomIn'), accelerator: this.getAccelerator('zoomIn'), click: () => Api.setZoom(this.win, this.win.webContents.getZoomLevel() + 1) },
|
|
{ label: Util.translate('electronMenuZoomOut'), accelerator: this.getAccelerator('zoomOut'), click: () => Api.setZoom(this.win, this.win.webContents.getZoomLevel() - 1) },
|
|
{ label: Util.translate('electronMenuZoomDefault'), accelerator: this.getAccelerator('zoomReset'), click: () => Api.setZoom(this.win, 0) },
|
|
{
|
|
label: Util.translate('electronMenuFullscreen'), accelerator: this.getAccelerator('toggleFullscreen'), type: 'checkbox', checked: this.win.isFullScreen(),
|
|
click: () => this.win.setFullScreen(!this.win.isFullScreen())
|
|
},
|
|
{ label: Util.translate('electronMenuReload'), accelerator: 'CmdOrCtrl+R', click: () => this.win.reload() }
|
|
]
|
|
},
|
|
{
|
|
label: Util.translate('electronMenuHelp'),
|
|
submenu: [
|
|
{
|
|
label: `${Util.translate('electronMenuReleaseNotes')} (${app.getVersion()})`,
|
|
click: () => Util.send(this.win, 'popup', 'help', { data: { document: 'whatsNew' } })
|
|
},
|
|
{
|
|
label: Util.translate('electronMenuShortcuts'), accelerator: this.getAccelerator('shortcut'),
|
|
click: () => Util.send(this.win, 'commandGlobal', 'shortcut')
|
|
},
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuGallery'), click: () => Util.send(this.win, 'commandGlobal', 'gallery') },
|
|
{ label: Util.translate('electronMenuCommunity'), click: () => Util.send(this.win, 'commandGlobal', 'community') },
|
|
{ label: Util.translate('electronMenuTutorial'), click: () => Util.send(this.win, 'commandGlobal', 'tutorial') },
|
|
{ label: Util.translate('electronMenuContact'), click: () => Util.send(this.win, 'commandGlobal', 'contact') },
|
|
{ label: Util.translate('electronMenuTech'), click: () => Util.send(this.win, 'commandGlobal', 'tech') },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuTerms'), click: () => Util.send(this.win, 'commandGlobal', 'terms') },
|
|
{ label: Util.translate('electronMenuPrivacy'), click: () => Util.send(this.win, 'commandGlobal', 'privacy') },
|
|
|
|
]
|
|
},
|
|
];
|
|
|
|
const flags = {
|
|
ui: Util.translate('electronMenuFlagInterface'),
|
|
hiddenObject: Util.translate('electronMenuFlagHidden'),
|
|
analytics: Util.translate('electronMenuFlagAnalytics'),
|
|
};
|
|
|
|
const flagsMw = {
|
|
request: Util.translate('electronMenuFlagMwRequest'),
|
|
event: Util.translate('electronMenuFlagMwEvent'),
|
|
sync: Util.translate('electronMenuFlagMwSync'),
|
|
file: Util.translate('electronMenuFlagMwFile'),
|
|
time: Util.translate('electronMenuFlagMwTime'),
|
|
json: Util.translate('electronMenuFlagMwJson'),
|
|
};
|
|
|
|
const flagMenu = [];
|
|
const flagMwMenu = [];
|
|
|
|
for (const i in flags) {
|
|
flagMenu.push({
|
|
label: flags[i], type: 'checkbox', checked: config.debug[i],
|
|
click: () => {
|
|
config.debug[i] = !config.debug[i];
|
|
Api.setConfig(this.win, { debug: config.debug });
|
|
|
|
if ([ 'hiddenObject' ].includes(i)) {
|
|
this.win.reload();
|
|
};
|
|
}
|
|
});
|
|
};
|
|
|
|
for (const i in flagsMw) {
|
|
flagMwMenu.push({
|
|
label: flagsMw[i], type: 'checkbox', checked: config.flagsMw[i],
|
|
click: () => {
|
|
config.flagsMw[i] = !config.flagsMw[i];
|
|
Api.setConfig(this.win, config);
|
|
}
|
|
});
|
|
};
|
|
|
|
flagMenu.push(Separator);
|
|
flagMenu.push({
|
|
label: Util.translate('electronMenuFlagMw'),
|
|
submenu: flagMwMenu,
|
|
});
|
|
|
|
menuParam.push({
|
|
label: Util.translate('electronMenuDebug'),
|
|
submenu: [
|
|
{ label: Util.translate('electronMenuFlags'), submenu: flagMenu },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuDebugSpace'), click: () => Util.send(this.win, 'commandGlobal', 'debugSpace') },
|
|
{ label: Util.translate('electronMenuDebugObject'), click: () => Util.send(this.win, 'commandGlobal', 'debugTree') },
|
|
{ label: Util.translate('electronMenuDebugProcess'), click: () => Util.send(this.win, 'commandGlobal', 'debugProcess') },
|
|
{ label: Util.translate('electronMenuDebugStat'), click: () => Util.send(this.win, 'commandGlobal', 'debugStat') },
|
|
{ label: Util.translate('electronMenuDebugReconcile'), click: () => Util.send(this.win, 'commandGlobal', 'debugReconcile') },
|
|
{ label: Util.translate('electronMenuDebugNet'), click: () => Util.send(this.win, 'commandGlobal', 'debugNet') },
|
|
{ label: Util.translate('electronMenuDebugLog'), click: () => Util.send(this.win, 'commandGlobal', 'debugLog') },
|
|
{ label: Util.translate('electronMenuDebugProfiler'), click: () => Util.send(this.win, 'commandGlobal', 'debugProfiler') },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuDevTools'), accelerator: 'Alt+CmdOrCtrl+I', click: () => this.win.toggleDevTools() },
|
|
]
|
|
});
|
|
|
|
const channels = ConfigManager.getChannels().map(it => {
|
|
it.click = () => {
|
|
if (!UpdateManager.isUpdating) {
|
|
Util.send(this.win, 'commandGlobal', 'releaseChannel', it.id);
|
|
};
|
|
};
|
|
return it;
|
|
});
|
|
|
|
if (channels.length > 1) {
|
|
menuParam.push({ label: Util.translate('electronMenuVersion'), submenu: channels });
|
|
};
|
|
|
|
const menuSudo = {
|
|
label: 'Sudo',
|
|
submenu: [
|
|
Separator,
|
|
|
|
{
|
|
label: 'Experimental features', type: 'checkbox', checked: config.experimental,
|
|
click: () => {
|
|
Api.setConfig(this.win, { experimental: !config.experimental });
|
|
this.win.reload();
|
|
}
|
|
},
|
|
|
|
Separator,
|
|
|
|
{
|
|
label: 'Test payments', type: 'checkbox', checked: config.testPayment,
|
|
click: () => {
|
|
Api.setConfig(this.win, { testPayment: !config.testPayment });
|
|
this.win.reload();
|
|
}
|
|
},
|
|
|
|
Separator,
|
|
|
|
{ label: 'Export templates', click: () => Util.send(this.win, 'commandGlobal', 'exportTemplates') },
|
|
{ label: 'Export objects', click: () => Util.send(this.win, 'commandGlobal', 'exportObjects') },
|
|
{ label: 'Export localstore', click: () => Util.send(this.win, 'commandGlobal', 'exportLocalstore') },
|
|
|
|
Separator,
|
|
|
|
{ label: 'Reset onboarding', click: () => Util.send(this.win, 'commandGlobal', 'resetOnboarding') },
|
|
{ label: 'Read all messages', click: () => Util.send(this.win, 'commandGlobal', 'readAllMessages') },
|
|
|
|
Separator,
|
|
|
|
{ label: 'Relaunch', click: () => Api.exit(this.win, true) },
|
|
]
|
|
};
|
|
|
|
if (config.sudo) {
|
|
menuParam.push(menuSudo);
|
|
};
|
|
|
|
this.menu = Menu.buildFromTemplate(menuParam);
|
|
Menu.setApplicationMenu(this.menu);
|
|
};
|
|
|
|
initTray () {
|
|
const { config } = ConfigManager;
|
|
const WindowManager = require('./window.js');
|
|
const Api = require('./api.js');
|
|
const UpdateManager = require('./update.js');
|
|
const isAllowedUpdate = UpdateManager.isAllowed();
|
|
|
|
this.destroy();
|
|
|
|
if (config.hideTray) {
|
|
return;
|
|
};
|
|
|
|
const icon = this.getTrayIcon();
|
|
|
|
this.tray = new Tray (icon);
|
|
this.tray.setToolTip('Anytype');
|
|
this.tray.setContextMenu(Menu.buildFromTemplate([
|
|
{ label: Util.translate('electronMenuOpenApp'), click: () => this.winShow() },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuNewWindow'), accelerator: this.getAccelerator('newWindow'), click: () => WindowManager.createMain({ isChild: true }) },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuCheckUpdates'), click: () => { this.winShow(); Api.updateCheck(this.win); }, visible: isAllowedUpdate },
|
|
{ label: Util.translate('commonSettings'), submenu: this.menuSettings() },
|
|
|
|
Separator,
|
|
|
|
{ label: Util.translate('electronMenuQuit'), click: () => { this.winHide(); Api.exit(this.win, '', false); } },
|
|
]));
|
|
|
|
// Force on top and focus because in some case Electron fail with this.winShow()
|
|
this.tray.on('double-click', () => {
|
|
if (this.win && !this.win.isDestroyed()) {
|
|
this.win.setAlwaysOnTop(true);
|
|
this.winShow();
|
|
this.win.setAlwaysOnTop(false);
|
|
};
|
|
});
|
|
};
|
|
|
|
winShow () {
|
|
if (this.win && !this.win.isDestroyed()) {
|
|
this.win.show();
|
|
};
|
|
};
|
|
|
|
winHide () {
|
|
if (this.win && !this.win.isDestroyed()) {
|
|
this.win.hide();
|
|
};
|
|
};
|
|
|
|
menuSettings () {
|
|
const { config } = ConfigManager;
|
|
const Api = require('./api.js');
|
|
const Locale = require('../../dist/lib/json/locale.json');
|
|
const lang = Util.getLang();
|
|
const langMenu = [];
|
|
|
|
for (const key of Util.enabledLangs()) {
|
|
langMenu.push({
|
|
label: Locale[key], type: 'checkbox', checked: key == lang,
|
|
click: () => Util.send(this.win, 'commandGlobal', 'interfaceLang', key)
|
|
});
|
|
};
|
|
|
|
return [
|
|
{
|
|
label: Util.translate('electronMenuAccountSettings'), click: () => {
|
|
this.winShow();
|
|
this.openSettings('');
|
|
}
|
|
},
|
|
{
|
|
label: Util.translate('electronMenuSpaceSettings'), click: () => {
|
|
this.winShow();
|
|
this.openSettings('spaceIndex');
|
|
}
|
|
},
|
|
|
|
Separator,
|
|
|
|
{
|
|
label: Util.translate('electronMenuImport'), click: () => {
|
|
this.winShow();
|
|
this.openSettings('importIndex');
|
|
}
|
|
},
|
|
{
|
|
label: Util.translate('electronMenuExport'), click: () => {
|
|
this.winShow();
|
|
this.openSettings('exportIndex');
|
|
}
|
|
},
|
|
|
|
{ label: Util.translate('electronMenuLanguage'), submenu: langMenu },
|
|
|
|
Separator,
|
|
|
|
{
|
|
label: Util.translate('electronMenuShowTray'), type: 'checkbox', checked: !config.hideTray, click: () => {
|
|
Api.setConfig(this.win, { hideTray: !config.hideTray });
|
|
this.initTray();
|
|
}
|
|
},
|
|
|
|
(is.windows || is.linux) ? {
|
|
label: Util.translate('electronMenuShowMenu'), type: 'checkbox', checked: config.showMenuBar, click: () => {
|
|
const { config } = ConfigManager;
|
|
|
|
Api.setMenuBarVisibility(this.win, !config.showMenuBar);
|
|
this.initTray();
|
|
}
|
|
} : null,
|
|
|
|
Separator,
|
|
|
|
{
|
|
label: Util.translate('commonNewObject'), accelerator:this.getAccelerator('createObject'), click: () => {
|
|
this.winShow();
|
|
Util.send(this.win, 'commandGlobal', 'createObject');
|
|
}
|
|
},
|
|
].filter(it => it);
|
|
};
|
|
|
|
openSettings (page) {
|
|
const Api = require('./api.js');
|
|
|
|
if (Api.isPinChecked) {
|
|
Util.send(this.win, 'route', '/main/settings/' + page);
|
|
};
|
|
};
|
|
|
|
updateTrayIcon () {
|
|
if (this.tray && this.tray.setImage) {
|
|
const icon = this.getTrayIcon();
|
|
if (icon) {
|
|
this.tray.setImage(icon);
|
|
};
|
|
};
|
|
};
|
|
|
|
getTrayIcon () {
|
|
let icon = '';
|
|
|
|
if (is.windows) {
|
|
icon = path.join('icons', '32x32.png');
|
|
} else
|
|
if (is.linux) {
|
|
const env = process.env.ORIGINAL_XDG_CURRENT_DESKTOP;
|
|
const panelAlwaysDark = env.includes('GNOME') || (env == 'Unity'); // for GNOME shell env, including ubuntu -- the panel is always dark
|
|
|
|
if (panelAlwaysDark) {
|
|
icon = 'iconTrayWhite.png';
|
|
} else
|
|
if (Util.getTheme() == 'dark') {
|
|
icon = 'iconTrayWhite.png';
|
|
} else {
|
|
icon = 'iconTrayBlack.png';
|
|
};
|
|
} else
|
|
if (is.macos) {
|
|
icon = `iconTrayTemplate.png`;
|
|
};
|
|
|
|
return icon ? path.join(Util.imagePath(), icon) : '';
|
|
};
|
|
|
|
destroy () {
|
|
if (this.tray && !this.tray.isDestroyed()) {
|
|
this.tray.destroy();
|
|
this.tray = null;
|
|
};
|
|
};
|
|
|
|
};
|
|
|
|
module.exports = new MenuManager();
|