From 0d2f3a60cd17934b40351116c22416d20817154e Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Fri, 10 Jan 2020 16:59:51 +0100 Subject: [PATCH 1/6] Remove separator when none of undo and redo button are visible --- resources/js/service-webview.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/resources/js/service-webview.js b/resources/js/service-webview.js index 53cf596..3af70d4 100644 --- a/resources/js/service-webview.js +++ b/resources/js/service-webview.js @@ -55,25 +55,29 @@ webContents.on('context-menu', (event, props) => { // Text clipboard if (editFlags.canUndo || editFlags.canRedo || editFlags.canCut || editFlags.canCopy || editFlags.canPaste || editFlags.canDelete) { - if (menu.items.length > 0) { - menu.append(new MenuItem({type: 'separator'})); - } - if (editFlags.canUndo) { - menu.append(new MenuItem({ - label: 'Undo', - role: 'undo', - })); - } - if (editFlags.canRedo) { - menu.append(new MenuItem({ - label: 'Redo', - role: 'redo', - })); + if (editFlags.canUndo || editFlags.canRedo) { + if (menu.items.length > 0) { + menu.append(new MenuItem({type: 'separator'})); + } + + if (editFlags.canUndo) { + menu.append(new MenuItem({ + label: 'Undo', + role: 'undo', + })); + } + if (editFlags.canRedo) { + menu.append(new MenuItem({ + label: 'Redo', + role: 'redo', + })); + } } if (menu.items.length > 0) { menu.append(new MenuItem({type: 'separator'})); } + menu.append(new MenuItem({ label: 'Cut', role: 'cut', From f1c66b1db122ab953aabb283ad507c47c30c9fd2 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Sun, 12 Jan 2020 13:07:37 +0100 Subject: [PATCH 2/6] Service navigation context menu: add Home button and disable action buttons when something is in progress --- resources/js/index.js | 82 ++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/resources/js/index.js b/resources/js/index.js index 84ec886..2cc24f9 100644 --- a/resources/js/index.js +++ b/resources/js/index.js @@ -18,43 +18,53 @@ let addButton; // Service context menu -const serviceContextMenu = new Menu(); -serviceContextMenu.append(new MenuItem({ - label: 'Reload', click: () => { - reloadService(serviceContextMenu.serviceId); - } -})); -serviceContextMenu.append(new MenuItem({ - label: 'Close', click: () => { - unloadService(serviceContextMenu.serviceId); - } -})); -serviceContextMenu.append(new MenuItem({type: "separator"})); -serviceContextMenu.append(new MenuItem({ - label: 'Edit', click: () => { - ipcRenderer.send('openServiceSettings', serviceContextMenu.serviceId); - } -})); -serviceContextMenu.append(new MenuItem({ - label: 'Delete', click: () => { - dialog.showMessageBox(remote.getCurrentWindow(), { - type: 'question', - title: 'Confirm', - message: 'Are you sure you want to delete this service?', - buttons: ['Cancel', 'Confirm'], - cancelId: 0, - }).then(result => { - if (result.response === 1) { - ipcRenderer.send('deleteService', serviceContextMenu.serviceId); - } - }).catch(console.error); - } -})); - -function openServiceContextMenu(event, index) { +function openServiceContextMenu(event, serviceId) { event.preventDefault(); - serviceContextMenu.serviceId = index; - serviceContextMenu.popup({window: remote.getCurrentWindow()}); + const service = services[serviceId]; + + const menu = new Menu(); + const ready = service.view && service.viewReady, notReady = !service.view && !service.viewReady; + menu.append(new MenuItem({ + label: 'Home', click: () => { + service.view.loadURL(service.url) + .catch(console.error); + }, + enabled: ready, + })); + menu.append(new MenuItem({ + label: ready ? 'Reload' : 'Load', click: () => { + reloadService(serviceId); + }, + enabled: ready || notReady, + })); + menu.append(new MenuItem({ + label: 'Close', click: () => { + unloadService(serviceId); + }, + enabled: ready, + })); + menu.append(new MenuItem({type: "separator"})); + menu.append(new MenuItem({ + label: 'Edit', click: () => { + ipcRenderer.send('openServiceSettings', serviceId); + } + })); + menu.append(new MenuItem({ + label: 'Delete', click: () => { + dialog.showMessageBox(remote.getCurrentWindow(), { + type: 'question', + title: 'Confirm', + message: 'Are you sure you want to delete this service?', + buttons: ['Cancel', 'Confirm'], + cancelId: 0, + }).then(result => { + if (result.response === 1) { + ipcRenderer.send('deleteService', serviceId); + } + }).catch(console.error); + } + })); + menu.popup({window: remote.getCurrentWindow()}); } From d723dc2511534b5016f43f356d93c34649f84065 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Sun, 12 Jan 2020 13:29:24 +0100 Subject: [PATCH 3/6] Update main window title with service navigation --- resources/js/index.js | 17 ++++++++++++++++- src/Meta.js | 19 ++++++++++++++++++- src/main.js | 12 +++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/resources/js/index.js b/resources/js/index.js index 2cc24f9..eac4dca 100644 --- a/resources/js/index.js +++ b/resources/js/index.js @@ -8,6 +8,7 @@ const { dialog, } = remote; +const appInfo = {}; const icons = []; let services = []; @@ -68,7 +69,11 @@ function openServiceContextMenu(event, serviceId) { } -ipcRenderer.on('data', (event, brandIcons, solidIcons, actualServices, actualSelectedService) => { +ipcRenderer.on('data', (event, appData, brandIcons, solidIcons, actualServices, actualSelectedService) => { + // App info + appInfo.title = appData.title; + + // Icons for (const icon of brandIcons) { icons.push(icon); } @@ -332,6 +337,16 @@ function updateNavigation() { if (view && view.canGoBack()) backButton.classList.remove('disabled'); else backButton.classList.add('disabled'); } + + updateWindowTitle(); +} + +function updateWindowTitle() { + if (selectedService === null) { + ipcRenderer.send('updateWindowTitle', null); + } else { + ipcRenderer.send('updateWindowTitle', selectedService, services[selectedService].view.getWebContents().getTitle()); + } } function goForward() { diff --git a/src/Meta.js b/src/Meta.js index 4ee96b3..65dec44 100644 --- a/src/Meta.js +++ b/src/Meta.js @@ -1,12 +1,29 @@ export default class Meta { + static #title = 'Tabs'; static #devMode = null; + static get title() { + return this.#title; + } + static isDevMode() { - if(this.#devMode === null) { + if (this.#devMode === null) { this.#devMode = process.argv.length > 2 && process.argv[2] === '--dev'; console.debug('Dev mode:', this.#devMode); } return this.#devMode; } + + /** + * @param service {Service} + * @param viewTitle {string} + */ + static getTitleForService(service, viewTitle) { + let suffix = ''; + if (typeof viewTitle === 'string' && viewTitle.length > 0) { + suffix = ' - ' + viewTitle; + } + return this.title + ' - ' + service.name + suffix; + } } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 9a6a5bd..6d86ed6 100644 --- a/src/main.js +++ b/src/main.js @@ -58,6 +58,7 @@ function createWindow() { }, autoHideMenuBar: true, icon: iconPath, + title: Meta.title, }); window.maximize(); window.on('closed', () => { @@ -157,12 +158,21 @@ function createWindow() { window.webContents.send('deleteService', id); }); + ipcMain.on('updateWindowTitle', (event, serviceId, viewTitle) => { + if (serviceId === null) { + window.setTitle(Meta.title); + } else { + const service = config.services[serviceId]; + window.setTitle(Meta.getTitleForService(service, viewTitle)); + } + }); + console.log('> App started'); } function sendData() { console.log('Syncing data'); - window.webContents.send('data', brandIcons, solidIcons, config.services, selectedService); + window.webContents.send('data', Meta.title, brandIcons, solidIcons, config.services, selectedService); } function setActiveService(index) { From c05b2eca8df4af430a8f47bbd753f56bc6500e06 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Tue, 21 Jan 2020 13:03:32 +0100 Subject: [PATCH 4/6] Make services reorderable (drag and drop) --- resources/index.html | 1 + resources/js/index.js | 95 ++++++++++++++++++++++++++++++++++++++- resources/style/index.css | 45 +++++++++++++++++++ src/main.js | 21 +++++++++ 4 files changed, 160 insertions(+), 2 deletions(-) diff --git a/resources/index.html b/resources/index.html index 4e74d31..01e5523 100644 --- a/resources/index.html +++ b/resources/index.html @@ -21,6 +21,7 @@
    + diff --git a/resources/js/index.js b/resources/js/index.js index eac4dca..b59ec95 100644 --- a/resources/js/index.js +++ b/resources/js/index.js @@ -96,6 +96,19 @@ ipcRenderer.on('data', (event, appData, brandIcons, solidIcons, actualServices, createService(i); } + // Init drag last position + const lastDragPosition = document.getElementById('service-last-drag-position'); + lastDragPosition.addEventListener('dragover', () => { + const index = services.length; + if (draggedId !== index && draggedId !== index - 1) { + resetDrag(); + lastDragTarget = dragTargetId = index; + lastDragPosition.classList.remove('hidden'); + lastDragPosition.classList.add('drag-target'); + } + }); + + // Set active service if (actualSelectedService < 0 || actualSelectedService >= services.length) { actualSelectedService = 0; } @@ -129,6 +142,31 @@ ipcRenderer.on('updateService', (e, id, data) => { } }); +ipcRenderer.on('reorderService', (e, serviceId, targetId) => { + const oldServices = services; + services = []; + + for (let i = 0; i < targetId; i++) { + if (i !== serviceId) { + services.push(oldServices[i]); + } + } + services.push(oldServices[serviceId]); + const newId = services.length - 1; + for (let i = targetId; i < oldServices.length; i++) { + if (i !== serviceId) { + services.push(oldServices[i]); + } + } + + document.getElementById('service-selector').innerHTML = ''; + for (let i = 0; i < services.length; i++) { + services[i].li = undefined; + createService(i); + } + setActiveService(newId); +}); + ipcRenderer.on('deleteService', (e, id) => { const nav = document.querySelector('#service-selector'); @@ -199,6 +237,55 @@ function createService(index, nextNavButton) { if (service.autoLoad) { loadService(index, service); } + + initDrag(index, li); +} + +let draggedId; +let lastDragTarget = -1; +let dragTargetId = -1; +let dragTargetCount = 0; + +function initDrag(index, li) { + li.serviceId = index; + li.draggable = true; + li.addEventListener('dragstart', (event) => { + draggedId = index; + event.dataTransfer.dropEffect = 'move'; + document.getElementById('service-last-drag-position').classList.remove('hidden'); + }); + li.addEventListener('dragover', () => { + if (draggedId !== index && draggedId !== index - 1) { + resetDrag(); + lastDragTarget = dragTargetId = index; + document.getElementById('service-last-drag-position').classList.remove('hidden'); + li.classList.add('drag-target'); + } + }); + li.addEventListener('dragend', () => { + reorderService(draggedId, lastDragTarget); + resetDrag(); + }); +} + +function resetDrag() { + lastDragTarget = -1; + dragTargetId = -1; + dragTargetCount = 0; + document.getElementById('service-selector').querySelectorAll('li').forEach(li => { + li.classList.remove('drag-target'); + }); + const lastDragPosition = document.getElementById('service-last-drag-position'); + lastDragPosition.classList.remove('drag-target'); + lastDragPosition.classList.add('hidden'); +} + +function reorderService(serviceId, targetId) { + console.log('Reordering service', serviceId, targetId); + if (targetId >= 0) { + setActiveService(null); + ipcRenderer.send('reorderService', serviceId, targetId); + } } document.addEventListener('DOMContentLoaded', () => { @@ -215,7 +302,9 @@ document.addEventListener('DOMContentLoaded', () => { function setActiveService(serviceId) { const currentService = services[serviceId]; process.nextTick(() => { - loadService(serviceId, currentService); + if (currentService) { + loadService(serviceId, currentService); + } // Hide previous service if (services[selectedService] && services[selectedService].view) { @@ -223,7 +312,9 @@ function setActiveService(serviceId) { } // Show service - currentService.view.classList.add('active'); + if (currentService) { + currentService.view.classList.add('active'); + } // Save active service ID selectedService = serviceId; diff --git a/resources/style/index.css b/resources/style/index.css index dd364aa..726e0bc 100644 --- a/resources/style/index.css +++ b/resources/style/index.css @@ -10,6 +10,51 @@ body { padding: 0; } +#service-selector [draggable] { + user-select: none; + background-color: rgb(43, 43, 43); +} + +#service-selector [draggable] img { + -webkit-user-drag: none; + user-drag: none; +} + +#service-selector .drag-target button { + height: 144px; + padding-top: 88px; +} + +#service-selector .drag-target button::after, +#service-last-drag-position.drag-target { + content: ""; + height: 72px; + border: 1px dashed #fff; + box-sizing: border-box; + + background-color: rgb(43, 43, 43); +} +#service-selector .drag-target button::after { + position: absolute; + left: 0; + top: 0; + width: 100%; +} +#service-last-drag-position:not(.hidden):not(.drag-target) { + display: block; + padding: 16px 4px; + background-color: #fff5; +} +#service-last-drag-position:not(.drag-target)::after { + content: ""; + display: block; + border-bottom: 1px solid #fff; +} + +#service-selector .drag-target::after { + top: 75% !important; +} + *:focus { outline-color: rgb(118, 93, 176); } diff --git a/src/main.js b/src/main.js index 6d86ed6..20e39c8 100644 --- a/src/main.js +++ b/src/main.js @@ -158,6 +158,27 @@ function createWindow() { window.webContents.send('deleteService', id); }); + ipcMain.on('reorderService', (e, serviceId, targetId) => { + console.log('Reordering services', serviceId, targetId); + + const oldServices = config.services; + config.services = []; + + for (let i = 0; i < targetId; i++) { + if (i !== serviceId) { + config.services.push(oldServices[i]); + } + } + config.services.push(oldServices[serviceId]); + for (let i = targetId; i < oldServices.length; i++) { + if (i !== serviceId) { + config.services.push(oldServices[i]); + } + } + + e.reply('reorderService', serviceId, targetId); + }); + ipcMain.on('updateWindowTitle', (event, serviceId, viewTitle) => { if (serviceId === null) { window.setTitle(Meta.title); From 3a7a711826d10f4c779c3a01f3d4db17decd2a99 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Tue, 21 Jan 2020 13:06:59 +0100 Subject: [PATCH 5/6] Save configuration on service reordering --- src/main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.js b/src/main.js index 20e39c8..5494bc6 100644 --- a/src/main.js +++ b/src/main.js @@ -177,6 +177,7 @@ function createWindow() { } e.reply('reorderService', serviceId, targetId); + config.save(); }); ipcMain.on('updateWindowTitle', (event, serviceId, viewTitle) => { From e4a3efbd762efe139213d36ddc9428bb3f81c13e Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Tue, 21 Jan 2020 13:07:31 +0100 Subject: [PATCH 6/6] Version 0.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ecf249..80d6547 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tabs", - "version": "0.3.0", + "version": "0.4.0", "description": "Persistent and separate browser tabs in one window", "author": { "name": "Alice Gaudon",