From bd97df5ee920f2ce9007ae49032c5a6f0a86149a Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 11 May 2019 19:44:58 +0200 Subject: [PATCH] refactored chrome tabs into "tab row" --- libraries/chrome-tabs/chrome-tabs.css | 144 ----- .../javascripts/services/note_detail.js | 43 +- .../javascripts/services/tab_context.js | 11 +- .../public/javascripts/services/tab_row.js | 502 +++++++++--------- src/public/stylesheets/desktop.css | 145 +++++ src/views/desktop.ejs | 3 - src/views/tabs.ejs | 4 +- 7 files changed, 426 insertions(+), 426 deletions(-) delete mode 100644 libraries/chrome-tabs/chrome-tabs.css rename libraries/chrome-tabs/chrome-tabs.js => src/public/javascripts/services/tab_row.js (60%) diff --git a/libraries/chrome-tabs/chrome-tabs.css b/libraries/chrome-tabs/chrome-tabs.css deleted file mode 100644 index ddfba0936..000000000 --- a/libraries/chrome-tabs/chrome-tabs.css +++ /dev/null @@ -1,144 +0,0 @@ -.chrome-tabs { - box-sizing: border-box; - position: relative; - height: 33px; - background: var(--main-background-color); - border-radius: 5px 5px 0 0; - overflow: hidden; - grid-area: tabs; - margin-top: 5px; -} -.chrome-tabs * { - box-sizing: inherit; - font: inherit; -} -.chrome-tabs .chrome-tabs-content { - position: relative; - width: 100%; - height: 100%; -} -.chrome-tabs .chrome-tab { - position: absolute; - left: 0; - height: 36px; - width: 240px; - border: 0; - margin: 0; - z-index: 1; - pointer-events: none; -} - -.chrome-tabs .chrome-tab[active] { - z-index: 5; -} - -.chrome-tabs .chrome-tab, -.chrome-tabs .chrome-tab * { - user-select: none; - cursor: default; -} - -.chrome-tabs .chrome-tab.chrome-tab-was-just-added { - top: 10px; - animation: chrome-tab-was-just-added 120ms forwards ease-in-out; -} -.chrome-tabs .chrome-tab .chrome-tab-content { - position: absolute; - display: flex; - top: 0; - bottom: 0; - left: var(--tab-content-margin); - right: var(--tab-content-margin); - padding: 5px 8px; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - overflow: hidden; - pointer-events: all; - background-color: var(--accented-background-color); -} - -.chrome-tabs .chrome-tab[active] .chrome-tab-content { - background-color: var(--more-accented-background-color); -} - -.chrome-tabs .chrome-tab[is-mini] .chrome-tab-content { - padding-left: 2px; - padding-right: 2px; -} -.chrome-tabs .chrome-tab .chrome-tab-title { - flex: 1; - vertical-align: top; - overflow: hidden; - white-space: nowrap; - color: var(--muted-text-color); -} -.chrome-tabs .chrome-tab[is-small] .chrome-tab-title { - margin-left: 0; -} -.chrome-tabs .chrome-tab[active] .chrome-tab-title { - color: var(--main-text-color); -} -.chrome-tabs .chrome-tab .chrome-tab-drag-handle { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - border-top-left-radius: 8px; - border-top-right-radius: 8px; -} -.chrome-tabs .chrome-tab .chrome-tab-close { - flex-grow: 0; - flex-shrink: 0; - border-radius: 50%; - z-index: 100; - width: 24px; - height: 24px; - text-align: center; -} - -.chrome-tabs .chrome-tab .chrome-tab-close span { - font-size: 24px; - position: relative; - top: -6px; -} - -.chrome-tabs .chrome-tab .chrome-tab-close:hover { - background-color: var(--hover-item-background-color); - color: var(--hover-item-text-color); -} - -.chrome-tabs .chrome-tab[is-smaller] .chrome-tab-close { - margin-left: auto; -} -.chrome-tabs .chrome-tab[is-mini]:not([active]) .chrome-tab-close { - display: none; -} -.chrome-tabs .chrome-tab[is-mini][active] .chrome-tab-close { - margin-left: auto; - margin-right: auto; -} -@-moz-keyframes chrome-tab-was-just-added { - to { - top: 0; - } -} -@-webkit-keyframes chrome-tab-was-just-added { - to { - top: 0; - } -} -@-o-keyframes chrome-tab-was-just-added { - to { - top: 0; - } -} -@keyframes chrome-tab-was-just-added { - to { - top: 0; - } -} -.chrome-tabs.chrome-tabs-is-sorting .chrome-tab:not(.chrome-tab-is-dragging), -.chrome-tabs:not(.chrome-tabs-is-sorting) .chrome-tab.chrome-tab-was-just-dragged { - transition: transform 120ms ease-in-out; -} \ No newline at end of file diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 72eb3194d..0272c6eee 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -10,10 +10,7 @@ import utils from "./utils.js"; import importDialog from "../dialogs/import.js"; import contextMenuService from "./context_menu.js"; import treeUtils from "./tree_utils.js"; - -const chromeTabsEl = document.querySelector('.chrome-tabs'); -const chromeTabs = new ChromeTabs(); -chromeTabs.init(chromeTabsEl); +import tabRow from "./tab_row.js"; const $tabContentsContainer = $("#note-tab-container"); const $savedIndicator = $(".saved-indicator"); @@ -190,7 +187,7 @@ async function loadNoteDetail(notePath, options = {}) { if (tabContexts.length === 0 || newTab) { // if it's a new tab explicitly by user then it's in background - ctx = new TabContext(chromeTabs, newTab); + ctx = new TabContext(tabRow); tabContexts.push(ctx); } else { @@ -210,7 +207,7 @@ async function loadNoteDetail(notePath, options = {}) { if (activate) { // will also trigger showTab via event - chromeTabs.setCurrentTab(ctx.tab); + tabRow.setCurrentTab(ctx.tab); } } @@ -222,7 +219,7 @@ async function loadNote(noteId) { async function filterTabs(noteId) { for (const tc of tabContexts) { - chromeTabs.removeTab(tc.tab); + tabRow.removeTab(tc.tab); } await loadNoteDetail(noteId, { @@ -287,7 +284,7 @@ $tabContentsContainer.on("drop", e => { }); }); -chromeTabsEl.addEventListener('activeTabChange', ({ detail }) => { +tabRow.el.addEventListener('activeTabChange', ({ detail }) => { const tabId = detail.tabEl.getAttribute('data-tab-id'); showTab(tabId); @@ -295,7 +292,7 @@ chromeTabsEl.addEventListener('activeTabChange', ({ detail }) => { console.log(`Activated tab ${tabId}`); }); -chromeTabsEl.addEventListener('tabRemove', async ({ detail }) => { +tabRow.el.addEventListener('tabRemove', async ({ detail }) => { const tabId = parseInt(detail.tabEl.getAttribute('data-tab-id')); await saveNotesIfChanged(); @@ -311,8 +308,8 @@ chromeTabsEl.addEventListener('tabRemove', async ({ detail }) => { console.log(`Removed tab ${tabId}`); }); -$(chromeTabsEl).on('contextmenu', '.chrome-tab', e => { - const tab = $(e.target).closest(".chrome-tab"); +$(tabRow.el).on('contextmenu', '.note-tab', e => { + const tab = $(e.target).closest(".note-tab"); contextMenuService.initContextMenu(e, { getContextMenuItems: () => { @@ -322,7 +319,7 @@ $(chromeTabsEl).on('contextmenu', '.chrome-tab', e => { }, selectContextMenuItem: (e, cmd) => { if (cmd === 'removeAllTabsExceptForThis') { - chromeTabs.removeAllTabsExceptForThis(tab[0]); + tabRow.removeAllTabsExceptForThis(tab[0]); } } }); @@ -335,30 +332,30 @@ if (utils.isElectron()) { return; } - chromeTabs.removeTab(chromeTabs.activeTabEl); + tabRow.removeTab(tabRow.activeTabEl); }); utils.bindShortcut('ctrl+tab', () => { - const nextTab = chromeTabs.nextTabEl; + const nextTab = tabRow.nextTabEl; if (nextTab) { - chromeTabs.setCurrentTab(nextTab); + tabRow.setCurrentTab(nextTab); } }); utils.bindShortcut('ctrl+shift+tab', () => { - const prevTab = chromeTabs.previousTabEl; + const prevTab = tabRow.previousTabEl; if (prevTab) { - chromeTabs.setCurrentTab(prevTab); + tabRow.setCurrentTab(prevTab); } }); } -chromeTabsEl.addEventListener('activeTabChange', openTabsChanged); -chromeTabsEl.addEventListener('tabAdd', openTabsChanged); -chromeTabsEl.addEventListener('tabRemove', openTabsChanged); -chromeTabsEl.addEventListener('tabReorder', openTabsChanged); +tabRow.el.addEventListener('activeTabChange', openTabsChanged); +tabRow.el.addEventListener('tabAdd', openTabsChanged); +tabRow.el.addEventListener('tabRemove', openTabsChanged); +tabRow.el.addEventListener('tabReorder', openTabsChanged); let tabsChangedTaskId = null; @@ -378,10 +375,10 @@ function openTabsChanged() { } async function saveOpenTabs() { - const activeTabEl = chromeTabs.activeTabEl; + const activeTabEl = tabRow.activeTabEl; const openTabs = []; - for (const tabEl of chromeTabs.tabEls) { + for (const tabEl of tabRow.tabEls) { const tabId = parseInt(tabEl.getAttribute('data-tab-id')); const tabContext = tabContexts.find(tc => tc.tabId === tabId); diff --git a/src/public/javascripts/services/tab_context.js b/src/public/javascripts/services/tab_context.js index 9042fc1fa..d27ccde6c 100644 --- a/src/public/javascripts/services/tab_context.js +++ b/src/public/javascripts/services/tab_context.js @@ -33,10 +33,13 @@ const componentClasses = { let tabIdCounter = 1; class TabContext { - constructor(chromeTabs) { + /** + * @param {TabRow} tabRow + */ + constructor(tabRow) { this.tabId = tabIdCounter++; - this.chromeTabs = chromeTabs; - this.tab = this.chromeTabs.addTab({ + this.tabRow = tabRow; + this.tab = this.tabRow.addTab({ title: '', // will be set later id: this.tabId }, { @@ -84,7 +87,7 @@ class TabContext { this.tab.setAttribute('data-note-id', this.noteId); this.$tabContent.attr('data-note-id', note.noteId); - this.chromeTabs.updateTab(this.tab, {title: note.title}); + this.tabRow.updateTab(this.tab, {title: note.title}); this.attributes.invalidateAttributes(); diff --git a/libraries/chrome-tabs/chrome-tabs.js b/src/public/javascripts/services/tab_row.js similarity index 60% rename from libraries/chrome-tabs/chrome-tabs.js rename to src/public/javascripts/services/tab_row.js index c38beb8bf..99b4446e7 100644 --- a/libraries/chrome-tabs/chrome-tabs.js +++ b/src/public/javascripts/services/tab_row.js @@ -7,390 +7,392 @@ !function(i,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(t){return e(i,t)}):"object"==typeof module&&module.exports?module.exports=e(i,require("jquery")):i.jQueryBridget=e(i,i.jQuery)}(window,function(t,i){"use strict";var c=Array.prototype.slice,e=t.console,p=void 0===e?function(){}:function(t){e.error(t)};function n(d,o,u){(u=u||i||t.jQuery)&&(o.prototype.option||(o.prototype.option=function(t){u.isPlainObject(t)&&(this.options=u.extend(!0,this.options,t))}),u.fn[d]=function(t){if("string"==typeof t){var i=c.call(arguments,1);return s=i,a="$()."+d+'("'+(r=t)+'")',(e=this).each(function(t,i){var e=u.data(i,d);if(e){var n=e[r];if(n&&"_"!=r.charAt(0)){var o=n.apply(e,s);h=void 0===h?o:h}else p(a+" is not a valid method")}else p(d+" not initialized. Cannot call methods, i.e. "+a)}),void 0!==h?h:e}var e,r,s,h,a,n;return n=t,this.each(function(t,i){var e=u.data(i,d);e?(e.option(n),e._init()):(e=new o(i,n),u.data(i,d,e))}),this},r(u))}function r(t){!t||t&&t.bridget||(t.bridget=n)}return r(i||t.jQuery),n}),function(t,i){"use strict";"function"==typeof define&&define.amd?define("get-size/get-size",[],function(){return i()}):"object"==typeof module&&module.exports?module.exports=i():t.getSize=i()}(window,function(){"use strict";function m(t){var i=parseFloat(t);return-1==t.indexOf("%")&&!isNaN(i)&&i}var e="undefined"==typeof console?function(){}:function(t){console.error(t)},y=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],b=y.length;function E(t){var i=getComputedStyle(t);return i||e("Style returned "+i+". Are you running this code in a hidden iframe on Firefox? See http://bit.ly/getsizebug1"),i}var _,x=!1;function P(t){if(function(){if(!x){x=!0;var t=document.createElement("div");t.style.width="200px",t.style.padding="1px 2px 3px 4px",t.style.borderStyle="solid",t.style.borderWidth="1px 2px 3px 4px",t.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(t);var e=E(t);P.isBoxSizeOuter=_=200==m(e.width),i.removeChild(t)}}(),"string"==typeof t&&(t=document.querySelector(t)),t&&"object"==typeof t&&t.nodeType){var i=E(t);if("none"==i.display)return function(){for(var t={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},i=0;i {}; +const noop = _ => {}; - const closest = (value, array) => { +const closest = (value, array) => { let closest = Infinity; let closestIndex = -1; array.forEach((v, i) => { - if (Math.abs(value - v) < closest) { - closest = Math.abs(value - v); - closestIndex = i - } + if (Math.abs(value - v) < closest) { + closest = Math.abs(value - v); + closestIndex = i + } }); return closestIndex; - }; +}; - const tabTemplate = ` -
-
-
-
-
×
-
-
- `; +const tabTemplate = ` +
+
+
+
+
×
+
+
+`; - const defaultTapProperties = { +const defaultTapProperties = { title: 'New tab' - }; +}; - let instanceId = 0; +let instanceId = 0; - class ChromeTabs { +class TabRow { constructor() { - this.draggabillies = [] + this.draggabillies = [] } init(el) { - this.el = el; + this.el = el; - this.instanceId = instanceId; - this.el.setAttribute('data-chrome-tabs-instance-id', this.instanceId); - instanceId += 1; + this.instanceId = instanceId; + this.el.setAttribute('data-note-tab-row-instance-id', this.instanceId); + instanceId += 1; - this.setupCustomProperties(); - this.setupStyleEl(); - this.setupEvents(); - this.layoutTabs(); - this.setupDraggabilly(); - this.setVisibility(); + this.setupCustomProperties(); + this.setupStyleEl(); + this.setupEvents(); + this.layoutTabs(); + this.setupDraggabilly(); + this.setVisibility(); } emit(eventName, data) { - this.el.dispatchEvent(new CustomEvent(eventName, { detail: data })); + this.el.dispatchEvent(new CustomEvent(eventName, { detail: data })); } setupCustomProperties() { - this.el.style.setProperty('--tab-content-margin', `${ TAB_CONTENT_MARGIN }px`); + this.el.style.setProperty('--tab-content-margin', `${ TAB_CONTENT_MARGIN }px`); } setupStyleEl() { - this.styleEl = document.createElement('style'); - this.el.appendChild(this.styleEl); + this.styleEl = document.createElement('style'); + this.el.appendChild(this.styleEl); } setupEvents() { - window.addEventListener('resize', _ => { - this.cleanUpPreviouslyDraggedTabs(); - this.layoutTabs(); - }); + window.addEventListener('resize', _ => { + this.cleanUpPreviouslyDraggedTabs(); + this.layoutTabs(); + }); - this.tabEls.forEach((tabEl) => this.setTabCloseEventListener(tabEl)); + this.tabEls.forEach((tabEl) => this.setTabCloseEventListener(tabEl)); } setVisibility() { - this.el.style.display = this.tabEls.length > 1 ? "block" : "none"; + this.el.style.display = this.tabEls.length > 1 ? "block" : "none"; } get tabEls() { - return Array.prototype.slice.call(this.el.querySelectorAll('.chrome-tab')); + return Array.prototype.slice.call(this.el.querySelectorAll('.note-tab')); } get tabContentEl() { - return this.el.querySelector('.chrome-tabs-content'); + return this.el.querySelector('.note-tab-row-content'); } get tabContentWidths() { - const numberOfTabs = this.tabEls.length; - const tabsContentWidth = this.tabContentEl.clientWidth; - const tabsCumulativeOverlappedWidth = (numberOfTabs - 1) * TAB_CONTENT_OVERLAP_DISTANCE; - const targetWidth = (tabsContentWidth - (2 * TAB_CONTENT_MARGIN) + tabsCumulativeOverlappedWidth) / numberOfTabs; - const clampedTargetWidth = Math.max(TAB_CONTENT_MIN_WIDTH, Math.min(TAB_CONTENT_MAX_WIDTH, targetWidth)); - const flooredClampedTargetWidth = Math.floor(clampedTargetWidth); - const totalTabsWidthUsingTarget = (flooredClampedTargetWidth * numberOfTabs) + (2 * TAB_CONTENT_MARGIN) - tabsCumulativeOverlappedWidth; - const totalExtraWidthDueToFlooring = tabsContentWidth - totalTabsWidthUsingTarget; + const numberOfTabs = this.tabEls.length; + const tabsContentWidth = this.tabContentEl.clientWidth; + const tabsCumulativeOverlappedWidth = (numberOfTabs - 1) * TAB_CONTENT_OVERLAP_DISTANCE; + const targetWidth = (tabsContentWidth - (2 * TAB_CONTENT_MARGIN) + tabsCumulativeOverlappedWidth) / numberOfTabs; + const clampedTargetWidth = Math.max(TAB_CONTENT_MIN_WIDTH, Math.min(TAB_CONTENT_MAX_WIDTH, targetWidth)); + const flooredClampedTargetWidth = Math.floor(clampedTargetWidth); + const totalTabsWidthUsingTarget = (flooredClampedTargetWidth * numberOfTabs) + (2 * TAB_CONTENT_MARGIN) - tabsCumulativeOverlappedWidth; + const totalExtraWidthDueToFlooring = tabsContentWidth - totalTabsWidthUsingTarget; - const widths = []; - let extraWidthRemaining = totalExtraWidthDueToFlooring; - for (let i = 0; i < numberOfTabs; i += 1) { - const extraWidth = flooredClampedTargetWidth < TAB_CONTENT_MAX_WIDTH && extraWidthRemaining > 0 ? 1 : 0; - widths.push(flooredClampedTargetWidth + extraWidth); - if (extraWidthRemaining > 0) extraWidthRemaining -= 1; - } + const widths = []; + let extraWidthRemaining = totalExtraWidthDueToFlooring; + for (let i = 0; i < numberOfTabs; i += 1) { + const extraWidth = flooredClampedTargetWidth < TAB_CONTENT_MAX_WIDTH && extraWidthRemaining > 0 ? 1 : 0; + widths.push(flooredClampedTargetWidth + extraWidth); + if (extraWidthRemaining > 0) extraWidthRemaining -= 1; + } - return widths + return widths } get tabContentPositions() { - const positions = []; - const tabContentWidths = this.tabContentWidths; + const positions = []; + const tabContentWidths = this.tabContentWidths; - let position = TAB_CONTENT_MARGIN; - tabContentWidths.forEach((width, i) => { - const offset = i * TAB_CONTENT_OVERLAP_DISTANCE; - positions.push(position - offset); - position += width; - }); + let position = TAB_CONTENT_MARGIN; + tabContentWidths.forEach((width, i) => { + const offset = i * TAB_CONTENT_OVERLAP_DISTANCE; + positions.push(position - offset); + position += width; + }); - return positions; + return positions; } get tabPositions() { - const positions = []; + const positions = []; - this.tabContentPositions.forEach((contentPosition) => { - positions.push(contentPosition - TAB_CONTENT_MARGIN); - }); + this.tabContentPositions.forEach((contentPosition) => { + positions.push(contentPosition - TAB_CONTENT_MARGIN); + }); - return positions; + return positions; } layoutTabs() { - const tabContentWidths = this.tabContentWidths; + const tabContentWidths = this.tabContentWidths; - this.tabEls.forEach((tabEl, i) => { - const contentWidth = tabContentWidths[i]; - const width = contentWidth + (2 * TAB_CONTENT_MARGIN); + this.tabEls.forEach((tabEl, i) => { + const contentWidth = tabContentWidths[i]; + const width = contentWidth + (2 * TAB_CONTENT_MARGIN); - tabEl.style.width = width + 'px'; - tabEl.removeAttribute('is-small'); - tabEl.removeAttribute('is-smaller'); - tabEl.removeAttribute('is-mini'); + tabEl.style.width = width + 'px'; + tabEl.removeAttribute('is-small'); + tabEl.removeAttribute('is-smaller'); + tabEl.removeAttribute('is-mini'); - if (contentWidth < TAB_SIZE_SMALL) tabEl.setAttribute('is-small', ''); - if (contentWidth < TAB_SIZE_SMALLER) tabEl.setAttribute('is-smaller', ''); - if (contentWidth < TAB_SIZE_MINI) tabEl.setAttribute('is-mini', ''); - }); + if (contentWidth < TAB_SIZE_SMALL) tabEl.setAttribute('is-small', ''); + if (contentWidth < TAB_SIZE_SMALLER) tabEl.setAttribute('is-smaller', ''); + if (contentWidth < TAB_SIZE_MINI) tabEl.setAttribute('is-mini', ''); + }); - let styleHTML = ''; - this.tabPositions.forEach((position, i) => { - styleHTML += ` - .chrome-tabs[data-chrome-tabs-instance-id="${ this.instanceId }"] .chrome-tab:nth-child(${ i + 1 }) { - transform: translate3d(${ position }px, 0, 0) - } - ` - }); + let styleHTML = ''; + this.tabPositions.forEach((position, i) => { + styleHTML += ` + .note-tab-row[data-note-tab-row-instance-id="${ this.instanceId }"] .note-tab:nth-child(${ i + 1 }) { + transform: translate3d(${ position }px, 0, 0) + } + ` + }); - this.styleEl.innerHTML = styleHTML; + this.styleEl.innerHTML = styleHTML; } addTab(tabProperties, { animate = true, background = false } = {}) { - const div = document.createElement('div'); - div.innerHTML = tabTemplate; - const tabEl = div.firstElementChild; + const div = document.createElement('div'); + div.innerHTML = tabTemplate; + const tabEl = div.firstElementChild; - if (animate) { - tabEl.classList.add('chrome-tab-was-just-added'); - setTimeout(() => tabEl.classList.remove('chrome-tab-was-just-added'), 500); - } + if (animate) { + tabEl.classList.add('note-tab-was-just-added'); + setTimeout(() => tabEl.classList.remove('note-tab-was-just-added'), 500); + } - tabProperties = Object.assign({}, defaultTapProperties, tabProperties); - this.tabContentEl.appendChild(tabEl); - this.setVisibility(); - this.setTabCloseEventListener(tabEl); - this.updateTab(tabEl, tabProperties); - this.emit('tabAdd', { tabEl }); - if (!background) this.setCurrentTab(tabEl); - this.cleanUpPreviouslyDraggedTabs(); - this.layoutTabs(); - this.setupDraggabilly(); + tabProperties = Object.assign({}, defaultTapProperties, tabProperties); + this.tabContentEl.appendChild(tabEl); + this.setVisibility(); + this.setTabCloseEventListener(tabEl); + this.updateTab(tabEl, tabProperties); + this.emit('tabAdd', { tabEl }); + if (!background) this.setCurrentTab(tabEl); + this.cleanUpPreviouslyDraggedTabs(); + this.layoutTabs(); + this.setupDraggabilly(); - return tabEl; + return tabEl; } setTabCloseEventListener(tabEl) { - tabEl.querySelector('.chrome-tab-close').addEventListener('click', _ => this.removeTab(tabEl)); + tabEl.querySelector('.note-tab-close').addEventListener('click', _ => this.removeTab(tabEl)); } get activeTabEl() { - return this.el.querySelector('.chrome-tab[active]'); + return this.el.querySelector('.note-tab[active]'); } get previousTabEl() { - const tabEls = this.tabEls; + const tabEls = this.tabEls; - if (tabEls.length <= 1) { - return null; - } - - let prev = tabEls[tabEls.length - 1]; - - for (const tabEl of tabEls) { - if (tabEl.hasAttribute("active")) { - return prev; + if (tabEls.length <= 1) { + return null; } - prev = tabEl; - } + let prev = tabEls[tabEls.length - 1]; - return null; + for (const tabEl of tabEls) { + if (tabEl.hasAttribute("active")) { + return prev; + } + + prev = tabEl; + } + + return null; } get nextTabEl() { - const tabEls = this.tabEls; + const tabEls = this.tabEls; - if (tabEls.length <= 1) { - return null; - } - - let prev = tabEls[tabEls.length - 1]; - - for (const tabEl of tabEls) { - if (prev.hasAttribute("active")) { - return tabEl; + if (tabEls.length <= 1) { + return null; } - prev = tabEl; - } + let prev = tabEls[tabEls.length - 1]; - return null; + for (const tabEl of tabEls) { + if (prev.hasAttribute("active")) { + return tabEl; + } + + prev = tabEl; + } + + return null; } hasActiveTab() { - return !!this.activeTabEl; + return !!this.activeTabEl; } setCurrentTab(tabEl) { - const activeTabEl = this.activeTabEl; - if (activeTabEl === tabEl) return; - if (activeTabEl) activeTabEl.removeAttribute('active'); - tabEl.setAttribute('active', ''); - this.emit('activeTabChange', { tabEl }); + const activeTabEl = this.activeTabEl; + if (activeTabEl === tabEl) return; + if (activeTabEl) activeTabEl.removeAttribute('active'); + tabEl.setAttribute('active', ''); + this.emit('activeTabChange', { tabEl }); } removeTab(tabEl) { - if (tabEl === this.activeTabEl) { - if (tabEl.nextElementSibling) { - this.setCurrentTab(tabEl.nextElementSibling) - } else if (tabEl.previousElementSibling) { - this.setCurrentTab(tabEl.previousElementSibling) + if (tabEl === this.activeTabEl) { + if (tabEl.nextElementSibling) { + this.setCurrentTab(tabEl.nextElementSibling) + } else if (tabEl.previousElementSibling) { + this.setCurrentTab(tabEl.previousElementSibling) + } } - } - tabEl.parentNode.removeChild(tabEl); - this.emit('tabRemove', { tabEl }); - this.cleanUpPreviouslyDraggedTabs(); - this.layoutTabs(); - this.setupDraggabilly(); - this.setVisibility(); + tabEl.parentNode.removeChild(tabEl); + this.emit('tabRemove', { tabEl }); + this.cleanUpPreviouslyDraggedTabs(); + this.layoutTabs(); + this.setupDraggabilly(); + this.setVisibility(); } removeAllTabsExceptForThis(remainingTabEl) { - for (const tabEl of this.tabEls) { - if (remainingTabEl !== tabEl) { - this.removeTab(tabEl); + for (const tabEl of this.tabEls) { + if (remainingTabEl !== tabEl) { + this.removeTab(tabEl); + } } - } } updateTab(tabEl, tabProperties) { - tabEl.querySelector('.chrome-tab-title').textContent = tabProperties.title; + tabEl.querySelector('.note-tab-title').textContent = tabProperties.title; - if (tabProperties.id) { - tabEl.setAttribute('data-tab-id', tabProperties.id); - } + if (tabProperties.id) { + tabEl.setAttribute('data-tab-id', tabProperties.id); + } } cleanUpPreviouslyDraggedTabs() { - this.tabEls.forEach((tabEl) => tabEl.classList.remove('chrome-tab-was-just-dragged')); + this.tabEls.forEach((tabEl) => tabEl.classList.remove('note-tab-was-just-dragged')); } setupDraggabilly() { - const tabEls = this.tabEls; - const tabPositions = this.tabPositions; + const tabEls = this.tabEls; + const tabPositions = this.tabPositions; - if (this.isDragging) { - this.isDragging = false; - this.el.classList.remove('chrome-tabs-is-sorting'); - this.draggabillyDragging.element.classList.remove('chrome-tab-is-dragging'); - this.draggabillyDragging.element.style.transform = ''; - this.draggabillyDragging.dragEnd(); - this.draggabillyDragging.isDragging = false; - this.draggabillyDragging.positionDrag = noop; // Prevent Draggabilly from updating tabEl.style.transform in later frames - this.draggabillyDragging.destroy(); - this.draggabillyDragging = null; - } + if (this.isDragging) { + this.isDragging = false; + this.el.classList.remove('note-tab-row-is-sorting'); + this.draggabillyDragging.element.classList.remove('note-tab-is-dragging'); + this.draggabillyDragging.element.style.transform = ''; + this.draggabillyDragging.dragEnd(); + this.draggabillyDragging.isDragging = false; + this.draggabillyDragging.positionDrag = noop; // Prevent Draggabilly from updating tabEl.style.transform in later frames + this.draggabillyDragging.destroy(); + this.draggabillyDragging = null; + } - this.draggabillies.forEach(d => d.destroy()); + this.draggabillies.forEach(d => d.destroy()); - tabEls.forEach((tabEl, originalIndex) => { - const originalTabPositionX = tabPositions[originalIndex]; - const draggabilly = new Draggabilly(tabEl, { - axis: 'x', - handle: '.chrome-tab-drag-handle', - containment: this.tabContentEl - }); + tabEls.forEach((tabEl, originalIndex) => { + const originalTabPositionX = tabPositions[originalIndex]; + const draggabilly = new Draggabilly(tabEl, { + axis: 'x', + handle: '.note-tab-drag-handle', + containment: this.tabContentEl + }); - this.draggabillies.push(draggabilly); + this.draggabillies.push(draggabilly); - draggabilly.on('pointerDown', _ => { - this.setCurrentTab(tabEl) - }); + draggabilly.on('pointerDown', _ => { + this.setCurrentTab(tabEl) + }); - draggabilly.on('dragStart', _ => { - this.isDragging = true; - this.draggabillyDragging = draggabilly; - tabEl.classList.add('chrome-tab-is-dragging'); - this.el.classList.add('chrome-tabs-is-sorting'); - }); + draggabilly.on('dragStart', _ => { + this.isDragging = true; + this.draggabillyDragging = draggabilly; + tabEl.classList.add('note-tab-is-dragging'); + this.el.classList.add('note-tab-row-is-sorting'); + }); - draggabilly.on('dragEnd', _ => { - this.isDragging = false; - const finalTranslateX = parseFloat(tabEl.style.left, 10); - tabEl.style.transform = `translate3d(0, 0, 0)`; + draggabilly.on('dragEnd', _ => { + this.isDragging = false; + const finalTranslateX = parseFloat(tabEl.style.left, 10); + tabEl.style.transform = `translate3d(0, 0, 0)`; - // Animate dragged tab back into its place - requestAnimationFrame(_ => { - tabEl.style.left = '0'; - tabEl.style.transform = `translate3d(${ finalTranslateX }px, 0, 0)`; + // Animate dragged tab back into its place + requestAnimationFrame(_ => { + tabEl.style.left = '0'; + tabEl.style.transform = `translate3d(${ finalTranslateX }px, 0, 0)`; - requestAnimationFrame(_ => { - tabEl.classList.remove('chrome-tab-is-dragging'); - this.el.classList.remove('chrome-tabs-is-sorting'); + requestAnimationFrame(_ => { + tabEl.classList.remove('note-tab-is-dragging'); + this.el.classList.remove('note-tab-row-is-sorting'); - tabEl.classList.add('chrome-tab-was-just-dragged'); + tabEl.classList.add('note-tab-was-just-dragged'); - requestAnimationFrame(_ => { - tabEl.style.transform = ''; + requestAnimationFrame(_ => { + tabEl.style.transform = ''; - this.layoutTabs(); - this.setupDraggabilly(); - }) + this.layoutTabs(); + this.setupDraggabilly(); + }) + }) + }) + }); + + draggabilly.on('dragMove', (event, pointer, moveVector) => { + // Current index be computed within the event since it can change during the dragMove + const tabEls = this.tabEls; + const currentIndex = tabEls.indexOf(tabEl); + + const currentTabPositionX = originalTabPositionX + moveVector.x; + const destinationIndexTarget = closest(currentTabPositionX, tabPositions); + const destinationIndex = Math.max(0, Math.min(tabEls.length, destinationIndexTarget)); + + if (currentIndex !== destinationIndex) { + this.animateTabMove(tabEl, currentIndex, destinationIndex); + } }) - }) - }); - - draggabilly.on('dragMove', (event, pointer, moveVector) => { - // Current index be computed within the event since it can change during the dragMove - const tabEls = this.tabEls; - const currentIndex = tabEls.indexOf(tabEl); - - const currentTabPositionX = originalTabPositionX + moveVector.x; - const destinationIndexTarget = closest(currentTabPositionX, tabPositions); - const destinationIndex = Math.max(0, Math.min(tabEls.length, destinationIndexTarget)); - - if (currentIndex !== destinationIndex) { - this.animateTabMove(tabEl, currentIndex, destinationIndex); - } }) - }) } animateTabMove(tabEl, originIndex, destinationIndex) { - if (destinationIndex < originIndex) { - tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex]); - } else { - tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex + 1]); - } - this.emit('tabReorder', { tabEl, originIndex, destinationIndex }); - this.layoutTabs(); + if (destinationIndex < originIndex) { + tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex]); + } else { + tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex + 1]); + } + this.emit('tabReorder', { tabEl, originIndex, destinationIndex }); + this.layoutTabs(); } - } +} - window.ChromeTabs = ChromeTabs; -})(); \ No newline at end of file +const noteTabRowEl = document.querySelector('.note-tab-row'); +const tabRow = new TabRow(); +tabRow.init(noteTabRowEl); + +export default tabRow; \ No newline at end of file diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index 568579c85..5a181d095 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -167,4 +167,149 @@ li.dropdown-submenu:hover > ul.dropdown-menu { flex-grow: 0; flex-shrink: 0; margin-top: 10px; +} + +.note-tab-row { + box-sizing: border-box; + position: relative; + height: 33px; + background: var(--main-background-color); + border-radius: 5px 5px 0 0; + overflow: hidden; + grid-area: tabs; + margin-top: 5px; +} +.note-tab-row * { + box-sizing: inherit; + font: inherit; +} +.note-tab-row .note-tab-row-content { + position: relative; + width: 100%; + height: 100%; +} +.note-tab-row .note-tab { + position: absolute; + left: 0; + height: 36px; + width: 240px; + border: 0; + margin: 0; + z-index: 1; + pointer-events: none; +} + +.note-tab-row .note-tab[active] { + z-index: 5; +} + +.note-tab-row .note-tab, +.note-tab-row .note-tab * { + user-select: none; + cursor: default; +} + +.note-tab-row .note-tab.note-tab-was-just-added { + top: 10px; + animation: note-tab-was-just-added 120ms forwards ease-in-out; +} +.note-tab-row .note-tab .note-tab-wrapper { + position: absolute; + display: flex; + top: 0; + bottom: 0; + left: var(--tab-content-margin); + right: var(--tab-content-margin); + padding: 5px 8px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + overflow: hidden; + pointer-events: all; + background-color: var(--accented-background-color); +} + +.note-tab-row .note-tab[active] .note-tab-wrapper { + background-color: var(--more-accented-background-color); +} + +.note-tab-row .note-tab[is-mini] .note-tab-wrapper { + padding-left: 2px; + padding-right: 2px; +} +.note-tab-row .note-tab .note-tab-title { + flex: 1; + vertical-align: top; + overflow: hidden; + white-space: nowrap; + color: var(--muted-text-color); +} +.note-tab-row .note-tab[is-small] .note-tab-title { + margin-left: 0; +} +.note-tab-row .note-tab[active] .note-tab-title { + color: var(--main-text-color); +} +.note-tab-row .note-tab .note-tab-drag-handle { + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + border-top-left-radius: 8px; + border-top-right-radius: 8px; +} +.note-tab-row .note-tab .note-tab-close { + flex-grow: 0; + flex-shrink: 0; + border-radius: 50%; + z-index: 100; + width: 24px; + height: 24px; + text-align: center; +} + +.note-tab-row .note-tab .note-tab-close span { + font-size: 24px; + position: relative; + top: -6px; +} + +.note-tab-row .note-tab .note-tab-close:hover { + background-color: var(--hover-item-background-color); + color: var(--hover-item-text-color); +} + +.note-tab-row .note-tab[is-smaller] .note-tab-close { + margin-left: auto; +} +.note-tab-row .note-tab[is-mini]:not([active]) .note-tab-close { + display: none; +} +.note-tab-row .note-tab[is-mini][active] .note-tab-close { + margin-left: auto; + margin-right: auto; +} +@-moz-keyframes note-tab-was-just-added { + to { + top: 0; + } +} +@-webkit-keyframes note-tab-was-just-added { + to { + top: 0; + } +} +@-o-keyframes note-tab-was-just-added { + to { + top: 0; + } +} +@keyframes note-tab-was-just-added { + to { + top: 0; + } +} +.note-tab-row.note-tab-row-is-sorting .note-tab:not(.note-tab-is-dragging), +.note-tab-row:not(.note-tab-row-is-sorting) .note-tab.note-tab-was-just-dragged { + transition: transform 120ms ease-in-out; } \ No newline at end of file diff --git a/src/views/desktop.ejs b/src/views/desktop.ejs index 2fce2eadf..c31c07272 100644 --- a/src/views/desktop.ejs +++ b/src/views/desktop.ejs @@ -210,9 +210,6 @@ - - - diff --git a/src/views/tabs.ejs b/src/views/tabs.ejs index ecb302c98..e2c19699a 100644 --- a/src/views/tabs.ejs +++ b/src/views/tabs.ejs @@ -1,5 +1,5 @@ -
-
+
+