diff --git a/assets/locales/en.json b/assets/locales/en.json index 08e22a715..a7d4b91c6 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -15,7 +15,6 @@ "viewOnGitHub": "View on GitHub", "status": "Status", "files": "Files", - "settings": "Settings", "quit": "Quit", "versions": "Versions", "screenshotTaken": "Screenshot taken", @@ -116,5 +115,14 @@ "itemsFailedNotification": { "title": "Failed to add items", "message": "Could not add your items to your node." + }, + "settings": { + "settings": "Settings", + "preferences": "Preferences", + "launchOnStartup": "Launch at Login", + "ipfsCommandLineTools": "Command Line Tools", + "takeScreenshotShortcut": "Take Screenshot Shortcut", + "downloadHashShortcut": "Download Hash Shortcut", + "npmOnIpfs": "NPM on IPFS Experiment" } } diff --git a/package.json b/package.json index f7fcec0e7..71bf41c5a 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,9 @@ "lint": "standard", "test": "cross-env NODE_ENV=test mocha test/unit/**/*.spec.js", "test:e2e": "xvfb-maybe cross-env NODE_ENV=test mocha test/e2e/**/*.e2e.js --exit", - "postinstall": "run-s install-app-deps build:webui", + "postinstall": "run-s install-app-deps", "install-app-deps": "electron-builder install-app-deps", - "clean:webui": "shx rm -rf assets/webui/", - "build": "run-s clean:webui build:*", - "build:webui": "run-s build:webui:*", - "build:webui:download": "npx ipfs-or-gateway -c Qmexhq2sBHnXQbvyP2GfUdbnY7HCagH2Mw5vUNSBn2nxip -p assets/webui/ -t 360000 --verbose", - "build:webui:minimize": "shx rm -rf assets/webui/static/js/*.map && shx rm -rf assets/webui/static/css/*.map", - "build:binaries": "electron-builder --publish onTag" + "build": "electron-builder --publish onTag" }, "pre-commit": [ "lint" diff --git a/src/auto-launch.js b/src/auto-launch.js index b28e4f27d..e58cf84de 100644 --- a/src/auto-launch.js +++ b/src/auto-launch.js @@ -96,5 +96,7 @@ module.exports = async function (ctx) { } activate(store.get(CONFIG_KEY, false)) - createToggler(ctx, CONFIG_KEY, activate) + createToggler(CONFIG_KEY, activate) } + +module.exports.CONFIG_KEY = CONFIG_KEY diff --git a/src/create-toggler.js b/src/create-toggler.js index c1b3f86bd..64e2823cb 100644 --- a/src/create-toggler.js +++ b/src/create-toggler.js @@ -2,28 +2,21 @@ const { ipcMain } = require('electron') const store = require('./common/store') const logger = require('./common/logger') -module.exports = function ({ webui }, settingsOption, activate) { - ipcMain.on('config.toggle', async (_, opt) => { - if (opt !== settingsOption) { - return - } - +module.exports = function (settingsOption, activate) { + ipcMain.on(`toggle_${settingsOption}`, async () => { const oldValue = store.get(settingsOption, null) const newValue = !oldValue - let success = false + + // TODO: refactor: tell the user if didn't work or not available. + // Receive prompt() to ask user if they're sure they want to enable for some. if (await activate(newValue, oldValue)) { store.set(settingsOption, newValue) - success = true const action = newValue ? 'enabled' : 'disabled' logger.info(`[${settingsOption}] ${action}`) } - webui.webContents.send('config.changed', { - config: store.store, - changed: settingsOption, - success - }) + ipcMain.emit('configUpdated') }) } diff --git a/src/download-hash.js b/src/download-hash.js index 40c2aa3c5..a73bbd00c 100644 --- a/src/download-hash.js +++ b/src/download-hash.js @@ -99,3 +99,4 @@ module.exports = function (ctx) { module.exports.downloadHash = downloadHash module.exports.SHORTCUT = SHORTCUT +module.exports.CONFIG_KEY = CONFIG_KEY diff --git a/src/index.js b/src/index.js index 4ffc68170..9743daf12 100644 --- a/src/index.js +++ b/src/index.js @@ -67,7 +67,7 @@ async function run () { await setupAppMenu(ctx) await setupAutoUpdater(ctx) // ctx.checkForUpdates - await setupWebUI(ctx) // ctx.webui, launchWebUI + await setupWebUI(ctx) // launchWebUI await setupTray(ctx) // ctx.tray await setupDaemon(ctx) // ctx.getIpfsd, startIpfs, stopIpfs, restartIpfs diff --git a/src/ipfs-on-path/index.js b/src/ipfs-on-path/index.js index 04011c1ef..dcc5d5610 100644 --- a/src/ipfs-on-path/index.js +++ b/src/ipfs-on-path/index.js @@ -11,7 +11,7 @@ const { recoverableErrorDialog } = require('../dialogs') const CONFIG_KEY = 'ipfsOnPath' module.exports = async function (ctx) { - createToggler(ctx, CONFIG_KEY, async (value, oldValue) => { + createToggler(CONFIG_KEY, async (value, oldValue) => { if (value === oldValue || (oldValue === null && !value)) return if (value === true) return run('install') return run('uninstall') @@ -20,6 +20,8 @@ module.exports = async function (ctx) { firstTime() } +module.exports.CONFIG_KEY = CONFIG_KEY + async function firstTime () { // Check if we've done this before. if (store.get(CONFIG_KEY, null) !== null) { diff --git a/src/npm-on-ipfs/index.js b/src/npm-on-ipfs/index.js index 13dd1833b..bad403fe9 100644 --- a/src/npm-on-ipfs/index.js +++ b/src/npm-on-ipfs/index.js @@ -9,7 +9,7 @@ const CONFIG_KEY = 'experiments.npmOnIpfs' module.exports = function (ctx) { let interval = null - createToggler(ctx, CONFIG_KEY, async (value, oldValue) => { + createToggler(CONFIG_KEY, async (value, oldValue) => { if (value === oldValue || oldValue === null) return true // If the user is telling to (un)install even though they have (un)installed @@ -43,6 +43,8 @@ module.exports = function (ctx) { } } +module.exports.CONFIG_KEY = CONFIG_KEY + function isPkgInstalled () { return !!which.sync('ipfs-npm', { nothrow: true }) } diff --git a/src/second-instance.js b/src/second-instance.js index 6bf441091..2a81bc3bc 100644 --- a/src/second-instance.js +++ b/src/second-instance.js @@ -8,10 +8,6 @@ module.exports = async function (ctx) { return } - if (await filesHandler(argv, ctx)) { - return - } - - ctx.launchWebUI() + await filesHandler(argv, ctx) }) } diff --git a/src/setup-global-shortcut.js b/src/setup-global-shortcut.js index 2d219f9fa..bf3c3f7da 100644 --- a/src/setup-global-shortcut.js +++ b/src/setup-global-shortcut.js @@ -19,7 +19,7 @@ module.exports = function (ctx, { settingsOption, accelerator, action }) { } activate(store.get(settingsOption, false)) - createToggler(ctx, settingsOption, activate) + createToggler(settingsOption, activate) if (!IS_MAC) { return diff --git a/src/take-screenshot.js b/src/take-screenshot.js index 0d5bcad94..4bc76b355 100644 --- a/src/take-screenshot.js +++ b/src/take-screenshot.js @@ -96,9 +96,10 @@ function handleScreenshot (ctx) { } function takeScreenshot (ctx) { - const { webui } = ctx - logger.info('[screenshot] taking screenshot') - webui.webContents.send('screenshot') + // TODO: fix + // const { webui } = ctx + // logger.info('[screenshot] taking screenshot') + // webui.webContents.send('screenshot') } module.exports = function (ctx) { @@ -115,3 +116,4 @@ module.exports = function (ctx) { module.exports.takeScreenshot = takeScreenshot module.exports.SHORTCUT = SHORTCUT +module.exports.CONFIG_KEY = CONFIG_KEY diff --git a/src/tray.js b/src/tray.js index b75cd8b3e..8f6fed7cc 100644 --- a/src/tray.js +++ b/src/tray.js @@ -1,8 +1,6 @@ const { Menu, Tray, shell, app, ipcMain } = require('electron') const i18n = require('i18next') const path = require('path') -const { SHORTCUT: SCREENSHOT_SHORTCUT, takeScreenshot } = require('./take-screenshot') -const { SHORTCUT: HASH_SHORTCUT, downloadHash } = require('./download-hash') const addToIpfs = require('./add-to-ipfs') const { STATUS } = require('./daemon') const logger = require('./common/logger') @@ -10,6 +8,30 @@ const store = require('./common/store') const { IS_MAC, IS_WIN, VERSION, GO_IPFS_VERSION } = require('./common/consts') const moveRepositoryLocation = require('./move-repository-location') +const { SHORTCUT: SCREENSHOT_SHORTCUT, CONFIG_KEY: SCREENSHOT_KEY, takeScreenshot } = require('./take-screenshot') +const { SHORTCUT: HASH_SHORTCUT, CONFIG_KEY: HASH_KEY, downloadHash } = require('./download-hash') +const { CONFIG_KEY: AUTO_LAUNCH_KEY } = require('./auto-launch') +const { CONFIG_KEY: IPFS_PATH_KEY } = require('./ipfs-on-path') +const { CONFIG_KEY: NPM_IPFS_KEY } = require('./npm-on-ipfs') + +const CONFIG_KEYS = [ + AUTO_LAUNCH_KEY, + IPFS_PATH_KEY, + NPM_IPFS_KEY, + SCREENSHOT_KEY, + HASH_KEY +] + +function buildCheckbox (key, label) { + return { + id: key, + label: i18n.t(label), + click: () => { ipcMain.emit(`toggle_${key}`) }, + type: 'checkbox', + checked: false + } +} + // Notes on this: we are only supporting accelerators on macOS for now because // they natively work as soon as the menu opens. They don't work like that on Windows // or other OSes and must be registered globally. They still collide with global @@ -57,10 +79,6 @@ function buildMenu (ctx) { label: i18n.t('files'), click: () => { ctx.launchWebUI('/files') } }, - { - label: i18n.t('settings'), - click: () => { ctx.launchWebUI('/settings') } - }, { type: 'separator' }, { id: 'takeScreenshot', @@ -77,6 +95,16 @@ function buildMenu (ctx) { enabled: false }, { type: 'separator' }, + { + label: IS_MAC ? i18n.t('settings.preferences') : i18n.t('settings.settings'), + submenu: [ + buildCheckbox(AUTO_LAUNCH_KEY, 'settings.launchOnStartup'), + buildCheckbox(IPFS_PATH_KEY, 'settings.ipfsCommandLineTools'), + buildCheckbox(SCREENSHOT_KEY, 'settings.takeScreenshotShortcut'), + buildCheckbox(HASH_KEY, 'settings.downloadHashShortcut'), + buildCheckbox(NPM_IPFS_KEY, 'settings.npmOnIpfs') + ] + }, { label: i18n.t('advanced'), submenu: [ @@ -175,6 +203,7 @@ module.exports = function (ctx) { menu.on('menu-will-close', () => { ipcMain.emit('menubar-will-close') }) updateStatus(status) + updateConfig() } const updateStatus = data => { @@ -207,8 +236,15 @@ module.exports = function (ctx) { } } + const updateConfig = () => { + for (const key of CONFIG_KEYS) { + menu.getMenuItemById(key).checked = store.get(key, false) + } + } + ipcMain.on('ipfsd', (status) => { updateStatus(status) }) ipcMain.on('languageUpdated', () => { setupMenu(status) }) + ipcMain.on('configUpdated', (config) => { updateConfig(config) }) setupMenu() ctx.tray = tray diff --git a/src/webui.js b/src/webui.js new file mode 100644 index 000000000..842ffdac9 --- /dev/null +++ b/src/webui.js @@ -0,0 +1,16 @@ +const logger = require('./common/logger') +const { shell } = require('electron') + +module.exports = async function (ctx) { + ctx.launchWebUI = (url) => { + if (!url) { + logger.info('[web ui] launching web ui') + } else { + logger.info(`[web ui] navigate to ${url}`) + } + + // TODO: correct api port + // TODO: use hash directly so we can make sure we open the correct url + shell.openExternal(`http://localhost:5001/webui/${url}`) + } +} diff --git a/src/webui/connection-status.js b/src/webui/connection-status.js deleted file mode 100644 index f8f0688b8..000000000 --- a/src/webui/connection-status.js +++ /dev/null @@ -1,11 +0,0 @@ -const { ipcRenderer } = require('electron') - -module.exports = function () { - const handler = () => { - ipcRenderer.send('online-status-changed', navigator.onLine) - } - - window.addEventListener('online', handler) - window.addEventListener('offline', handler) - handler() -} diff --git a/src/webui/index.js b/src/webui/index.js deleted file mode 100644 index 0797c3a45..000000000 --- a/src/webui/index.js +++ /dev/null @@ -1,121 +0,0 @@ -const { screen, BrowserWindow, ipcMain, app, session } = require('electron') -const { join } = require('path') -const { URL } = require('url') -const serve = require('electron-serve') -const openExternal = require('./open-external') -const logger = require('../common/logger') -const store = require('../common/store') -const dock = require('../dock') - -serve({ scheme: 'webui', directory: join(__dirname, '../../assets/webui') }) - -const createWindow = () => { - const dimensions = screen.getPrimaryDisplay() - - const window = new BrowserWindow({ - title: 'IPFS Desktop', - show: false, - autoHideMenuBar: true, - titleBarStyle: 'hiddenInset', - fullscreenWindowTitle: true, - width: store.get('window.width', dimensions.width < 1440 ? dimensions.width : 1440), - height: store.get('window.height', dimensions.height < 900 ? dimensions.height : 900), - webPreferences: { - preload: join(__dirname, 'preload.js'), - webSecurity: false, - allowRunningInsecureContent: false, - nodeIntegration: process.env.NODE_ENV === 'test' - } - }) - - window.webContents.on('crashed', event => { - logger.error(`[web ui] crashed: ${event.toString()}`) - }) - - window.webContents.on('unresponsive', event => { - logger.error(`[web ui] unresponsive: ${event.toString()}`) - }) - - window.on('resize', () => { - const dim = window.getSize() - store.set('window.width', dim[0]) - store.set('window.height', dim[1]) - }) - - window.on('close', (event) => { - event.preventDefault() - window.hide() - dock.hide() - logger.info('[web ui] window hidden') - }) - - app.on('before-quit', () => { - // Makes sure the app quits even though we prevent - // the closing of this window. - window.removeAllListeners('close') - }) - - return window -} - -module.exports = async function (ctx) { - openExternal() - - const window = createWindow(ctx) - let apiAddress = null - - ctx.webui = window - - ctx.launchWebUI = (url, { focus = true } = {}) => { - if (!url) { - logger.info('[web ui] launching web ui') - } else { - logger.info(`[web ui] navigate to ${url}`) - window.webContents.send('updatedPage', url) - } - - if (focus) { - window.show() - window.focus() - dock.show() - } - } - - const url = new URL('/', 'webui://-') - url.hash = '/blank' - url.searchParams.set('deviceId', ctx.countlyDeviceId) - - function updateLanguage () { - url.searchParams.set('lng', store.get('language')) - } - - ipcMain.on('ipfsd', async () => { - const ipfsd = await ctx.getIpfsd(true) - - if (ipfsd && ipfsd.apiAddr !== apiAddress) { - apiAddress = ipfsd.apiAddr - url.searchParams.set('api', apiAddress) - updateLanguage() - window.loadURL(url.toString()) - } - }) - - ipcMain.on('config.get', () => { - window.webContents.send('config.changed', { config: store.store }) - }) - - session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { - delete details.requestHeaders.Origin - callback({ cancel: false, requestHeaders: details.requestHeaders }) // eslint-disable-line - }) - - return new Promise(resolve => { - window.once('ready-to-show', () => { - logger.info('[web ui] window ready') - resolve() - }) - - updateLanguage() - window.loadURL(url.toString()) - }) -} diff --git a/src/webui/open-external.js b/src/webui/open-external.js deleted file mode 100644 index 67ac68752..000000000 --- a/src/webui/open-external.js +++ /dev/null @@ -1,19 +0,0 @@ -const { app, shell } = require('electron') - -module.exports = function () { - app.on('web-contents-created', (_, contents) => { - contents.on('will-navigate', (event, url) => { - const parsedUrl = new URL(url) - - if (parsedUrl.origin !== 'webui://-') { - event.preventDefault() - shell.openExternal(url) - } - }) - - contents.on('new-window', (event, url) => { - event.preventDefault() - shell.openExternal(url) - }) - }) -} diff --git a/src/webui/preload.js b/src/webui/preload.js deleted file mode 100644 index fca175db4..000000000 --- a/src/webui/preload.js +++ /dev/null @@ -1,113 +0,0 @@ -const toPull = require('stream-to-pull-stream') -const { ipcRenderer, remote } = require('electron') -const readdir = require('recursive-readdir') -const fs = require('fs-extra') -const path = require('path') -const screenshotHook = require('./screenshot') -const connectionHook = require('./connection-status') -const { COUNTLY_KEY, VERSION } = require('../common/consts') - -screenshotHook() -connectionHook() - -const urlParams = new URLSearchParams(window.location.search) - -var originalSetItem = window.localStorage.setItem -window.localStorage.setItem = function () { - if (arguments[0] === 'i18nextLng') { - ipcRenderer.send('updateLanguage', arguments[1]) - } - - originalSetItem.apply(this, arguments) -} - -let previousHash = null - -ipcRenderer.on('updatedPage', (_, url) => { - previousHash = url - window.location.hash = url -}) - -document.addEventListener('visibilitychange', () => { - if (document.hidden) { - previousHash = window.location.hash - window.location.hash = '/blank' - } else { - window.location.hash = previousHash - } -}) - -window.ipfsDesktop = { - countlyAppKey: COUNTLY_KEY, - - countlyDeviceId: urlParams.get('deviceId'), - - countlyActions: [ - 'ADD_VIA_DESKTOP', - 'DAEMON_START', - 'DAEMON_STOP', - 'DOWNLOAD_HASH', - 'MOVE_REPOSITORY', - 'SCREENSHOT_TAKEN' - ], - - version: VERSION, - - onConfigChanged: (listener) => { - ipcRenderer.on('config.changed', (_, config) => { - listener(config) - }) - - ipcRenderer.send('config.get') - }, - - toggleSetting: (setting) => { - ipcRenderer.send('config.toggle', setting) - }, - - configHasChanged: () => { - ipcRenderer.send('ipfsConfigChanged') - }, - - selectDirectory: () => { - return new Promise(resolve => { - remote.dialog.showOpenDialog(remote.getCurrentWindow(), { - title: 'Select a directory', - properties: [ - 'openDirectory', - 'createDirectory' - ] - }, async (res) => { - if (!res || res.length === 0) { - return resolve() - } - - const files = [] - - const prefix = path.dirname(res[0]) - - for (const path of await readdir(res[0])) { - const size = (await fs.stat(path)).size - files.push({ - path: path.substring(prefix.length, path.length), - content: toPull.source(fs.createReadStream(path)), - size: size - }) - } - - resolve(files) - }) - }) - }, - - removeConsent: (consent) => { - ipcRenderer.send('countly.removeConsent', consent) - }, - - addConsent: (consent) => { - ipcRenderer.send('countly.addConsent', consent) - } -} - -// Inject api address -window.localStorage.setItem('ipfsApi', urlParams.get('api'))