<!DOCTYPE html> <html lang="en"> <head> <title>Tabs</title> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous"> <link rel="stylesheet" href="layout.css"> <style> body { display: flex; flex-direction: row; } #service-selector { flex-grow: 1; display: block; margin: 0; padding: 0; } *:focus { outline-color: rgb(118, 93, 176); } #navigation button { display: block; width: 100%; padding: 16px; margin: 0; height: 72px; color: #fff; border: 0; background: transparent; cursor: pointer; } #navigation button img { width: 32px; } #navigation button i { font-size: 32px; } #service-selector li.active button { position: relative; background-color: #fff2; } #service-selector li.active button::before { content: ""; display: block; position: absolute; left: 0; top: 0; height: 100%; border-left: 4px solid #ffffff2e; } #navigation button:hover { background-color: #fff3; } #history { padding: 8px; } #history button { display: inline; width: 24px; height: 24px; padding: initial; font-size: 12px; background: #fff1; border: 1px solid #fff4; border-radius: 72px; color: #fff; cursor: pointer; } #history button i { font-size: inherit; } #history button.disabled { color: #888; border: transparent; background: transparent; cursor: initial; } #history button:focus, #history button:hover { outline: none; border-color: #fff9; } #history button:hover:not(.disabled) { outline: none; background-color: #fff2; } #history button:active:not(.disabled) { background-color: #fff4; } #navigation #add-button:not(:hover) { opacity: 0.75; } #services { position: relative; flex-grow: 1; } #services > *:not(.loader) { height: 100%; } #services > :not(.active):not(.loader) { display: none; } #services > .loader { width: 64px; height: 64px; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: -1; } </style> </head> <body> <div id="navigation"> <div id="history"> <button id="back"><i class="fas fa-arrow-left"></i></button> <button id="forward" class="disabled"><i class="fas fa-arrow-right"></i></button> </div> <ul id="service-selector"></ul> <button id="add-button"><i class="fa fa-plus"></i></button> <button id="settings-button"><i class="fa fa-cog"></i></button> </div> <div id="services"> <div class="loader"></div> </div> <script> const { remote, ipcRenderer, } = require('electron'); const { Menu, MenuItem, } = remote; const icons = []; let services = []; let selectedService = 0; let forwardButton; let backButton; let addButton; // Service context menu const serviceContextMenu = new Menu(); const settingsItem = new MenuItem({ label: 'Edit', click: () => { ipcRenderer.send('openServiceSettings', serviceContextMenu.serviceId); } }); serviceContextMenu.append(settingsItem); function openServiceContextMenu(event, index) { event.preventDefault(); serviceContextMenu.serviceId = index; serviceContextMenu.popup({window: remote.getCurrentWindow()}); } ipcRenderer.on('data', (event, brandIcons, solidIcons, actualServices, actualSelectedService) => { for (const icon of brandIcons) { icons.push(icon); } for (const icon of solidIcons) { icons.push(icon); } console.log('Updating services ...'); services = actualServices; const nav = document.querySelector('#service-selector'); while (nav.children.length > 0) { nav.removeChild(nav.children[0]); } const serviceContainer = document.querySelector('#services'); serviceContainer.querySelectorAll(":scope > webview").forEach(w => serviceContainer.removeChild(w)); for (let i = 0; i < services.length; i++) { createService(i); } setActiveService(actualSelectedService); }); ipcRenderer.on('updateService', (e, id, data) => { if (id === null) { services.push(data); createService(services.length - 1); } else { let nav = document.querySelector('#service-selector'); // Remove nav const oldNavButton = nav.querySelector('li:nth-of-type(' + (id + 1) + ')'); const nextNavButton = oldNavButton.nextSibling; nav.removeChild(oldNavButton); // Remove webview if (services[id].view) { const serviceContainer = document.querySelector('#services'); serviceContainer.removeChild(services[id].view); } // Create new service services[id] = data; createService(id, nextNavButton); if (parseInt(selectedService) === id) { setActiveService(id); } } }); function createService(index, nextNavButton) { let service = services[index]; let li = document.createElement('li'); service.li = li; let button = document.createElement('button'); button.dataset.serviceId = '' + index; button.dataset.tooltip = service.name; button.addEventListener('click', () => { setActiveService(button.dataset.serviceId); ipcRenderer.send('setActiveService', button.dataset.serviceId); }); button.addEventListener('contextmenu', e => openServiceContextMenu(e, index)); let icon; if (service.useFavicon && service.favicon != null) { icon = document.createElement('img'); icon.src = service.favicon; icon.alt = service.name; } else if (service.isImage) { icon = document.createElement('img'); icon.src = service.icon; icon.alt = service.name; } else { icon = document.createElement('i'); const iconProperties = icons.find(i => i.name === service.icon); if (iconProperties) { iconProperties.faIcon.split(' ').forEach(cl => { icon.classList.add(cl); }); } } button.appendChild(icon); li.appendChild(button); li.button = button; const nav = document.querySelector('#service-selector'); if (nextNavButton === nav || nextNavButton === undefined) { nav.appendChild(li); } else { nav.insertBefore(li, nextNavButton); } } document.addEventListener('DOMContentLoaded', () => { forwardButton = document.querySelector('#forward'); forwardButton.addEventListener('click', () => goForward()); backButton = document.querySelector('#back'); backButton.addEventListener('click', () => goBack()); addButton = document.querySelector('#add-button'); addButton.addEventListener('click', () => ipcRenderer.send('openServiceSettings', null)); }); function setActiveService(serviceId) { let currentService = services[serviceId]; process.nextTick(() => { // Load service if not loaded yet if (!currentService.view) { currentService.view = document.createElement('webview'); currentService.view.setAttribute('src', currentService.url); currentService.view.setAttribute('partition', 'persist:service_' + currentService.partition); currentService.view.setAttribute('autosize', "true"); document.querySelector('#services').appendChild(currentService.view); currentService.view.addEventListener('dom-ready', event => { currentService.viewReady = true; updateNavigation(); }); currentService.view.addEventListener('page-favicon-updated', event => { console.debug('Loaded favicons for', currentService.name, event.favicons); if (event.favicons.length > 0) { ipcRenderer.send('setServiceFavicon', serviceId, event.favicons[0]); if (currentService.useFavicon) { const img = document.createElement('img'); img.src = event.favicons[0]; img.alt = currentService.name; img.onload = () => { currentService.li.button.innerHTML = ''; currentService.li.button.appendChild(img); }; } } }); } // Hide previous service if (services[selectedService].view) { services[selectedService].view.classList.remove('active'); } // Show service currentService.view.classList.add('active'); // Save active service ID selectedService = serviceId; // Refresh navigation updateNavigation(); }); } function updateNavigation() { console.debug('Updating navigation'); // Update active list element for (let i = 0; i < services.length; i++) { let service = services[i]; if (parseInt(selectedService) === i) { service.li.classList.add('active'); } else { service.li.classList.remove('active'); } } if (services[selectedService].viewReady) { console.debug('Updating navigation buttons because view is ready'); // Update history navigation let view = services[selectedService].view; if (view && view.canGoForward()) forwardButton.classList.remove('disabled'); else forwardButton.classList.add('disabled'); if (view && view.canGoBack()) backButton.classList.remove('disabled'); else backButton.classList.add('disabled'); } } function goForward() { let view = services[selectedService].view; if (view) view.getWebContents().goForward(); } function goBack() { let view = services[selectedService].view; if (view) view.getWebContents().goBack(); } </script> </body> </html>