diff --git a/chrome/content/zotero/collectionTree.jsx b/chrome/content/zotero/collectionTree.jsx index a0472c0f09..1719cc5bf3 100644 --- a/chrome/content/zotero/collectionTree.jsx +++ b/chrome/content/zotero/collectionTree.jsx @@ -1293,6 +1293,45 @@ var CollectionTree = class CollectionTree extends LibraryTree { return false; } + getIconName(index) { + const treeRow = this.getRow(index); + let collectionType = treeRow.type; + let icon = collectionType; + + switch (collectionType) { + case 'group': + icon = 'library-group'; + break; + case 'feed': + // Better alternative needed: https://github.com/zotero/zotero/pull/902#issuecomment-183185973 + /* + if (treeRow.ref.updating) { + collectionType += 'Updating'; + } else */if (treeRow.ref.lastCheckError) { + icon += '-error'; + } + break; + case 'trash': + if (this._trashNotEmpty[treeRow.ref.libraryID]) { + icon += '-full'; + } + break; + + case 'feeds': + icon = 'feed-library'; + break; + + case 'header': + if (treeRow.ref.id == 'group-libraries-header') { + icon = 'groups'; + } + break; + case 'separator': + return null; + } + return icon; + } + //////////////////////////////////////////////////////////////////////////////// /// @@ -2234,47 +2273,11 @@ var CollectionTree = class CollectionTree extends LibraryTree { } _getIcon(index) { - const treeRow = this.getRow(index); - var collectionType = treeRow.type; - - if (collectionType == 'separator') { - return document.createElement('span'); - } + let iconName = this.getIconName(index); - let icon = collectionType; - - switch (collectionType) { - case 'group': - icon = 'library-group'; - break; - case 'feed': - // Better alternative needed: https://github.com/zotero/zotero/pull/902#issuecomment-183185973 - /* - if (treeRow.ref.updating) { - collectionType += 'Updating'; - } else */if (treeRow.ref.lastCheckError) { - icon += '-error'; - } - break; - - case 'trash': - if (this._trashNotEmpty[treeRow.ref.libraryID]) { - icon += '-full'; - } - break; - - case 'feeds': - icon = 'feed-library'; - break; - - case 'header': - if (treeRow.ref.id == 'group-libraries-header') { - icon = 'groups'; - } - break; - } - - return getCSSIcon(icon); + return iconName + ? getCSSIcon(iconName) + : document.createElement('span'); } /** diff --git a/chrome/content/zotero/components/icons.jsx b/chrome/content/zotero/components/icons.jsx index 6cd0913df8..76cb63ecb3 100644 --- a/chrome/content/zotero/components/icons.jsx +++ b/chrome/content/zotero/components/icons.jsx @@ -109,22 +109,6 @@ i('Twisty', ( )); -i('ArrowLeft', ( - /* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - - -)); -i('ArrowRight', ( - /* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - - -)); i('Cross', "chrome://zotero/skin/cross.png"); i('Tick', "chrome://zotero/skin/tick.png"); i('ArrowRefresh', "chrome://zotero/skin/arrow_refresh.png"); @@ -253,14 +237,14 @@ module.exports.getCSSIcon = function (key) { let iconEl = document.createElement('span'); iconEl.classList.add('icon'); iconEl.classList.add('icon-css'); - iconEl.classList.add(key); + iconEl.classList.add(`icon-${key}`); cssIconsCache.set(key, iconEl); } return cssIconsCache.get(key).cloneNode(true); }; -module.exports.getCSSItemTypeIcon = function (itemType, key = 'icon-item-type') { +module.exports.getCSSItemTypeIcon = function (itemType, key = 'item-type') { let icon = module.exports.getCSSIcon(key); icon.dataset.itemType = itemType; return icon; diff --git a/chrome/content/zotero/components/tabBar.jsx b/chrome/content/zotero/components/tabBar.jsx index ae06bfb401..7fa6743277 100644 --- a/chrome/content/zotero/components/tabBar.jsx +++ b/chrome/content/zotero/components/tabBar.jsx @@ -27,7 +27,7 @@ import React, { forwardRef, useState, useRef, useImperativeHandle, useEffect, useLayoutEffect } from 'react'; import cx from 'classnames'; -const { IconXmark, IconArrowLeft, IconArrowRight } = require('./icons'); +const { CSSIcon } = require('./icons'); const SCROLL_ARROW_SCROLL_BY = 222; @@ -312,7 +312,7 @@ const TabBar = forwardRef(function (props, ref) { } } - function renderTab({ id, title, selected, iconBackgroundImage }, index) { + function renderTab({ id, title, selected, icon }, index) { return (
-
{iconBackgroundImage && - }{title}
+ {icon} +
{title}
handleTabClose(event, id)} > - +
); } - + return (
+ > + +
+ > + +
); }); +TabBar.displayName = 'TabBar'; + export default TabBar; diff --git a/chrome/content/zotero/itemTree.jsx b/chrome/content/zotero/itemTree.jsx index 2bfeaf6ad6..a2f068cb67 100644 --- a/chrome/content/zotero/itemTree.jsx +++ b/chrome/content/zotero/itemTree.jsx @@ -2744,19 +2744,19 @@ var ItemTree = class ItemTree extends LibraryTree { // If the item has a child attachment if (type !== null && type != 'none') { if (type == 'pdf') { - icon = getCSSItemTypeIcon('attachmentPDF', 'icon-attachment-type'); + icon = getCSSItemTypeIcon('attachmentPDF', 'attachment-type'); ariaLabel = Zotero.getString('pane.item.attachments.hasPDF'); } else if (type == 'snapshot') { - icon = getCSSItemTypeIcon('attachmentSnapshot', 'icon-attachment-type'); + icon = getCSSItemTypeIcon('attachmentSnapshot', 'attachment-type'); ariaLabel = Zotero.getString('pane.item.attachments.hasSnapshot'); } else if (type == 'epub') { - icon = getCSSItemTypeIcon('attachmentEPUB', 'icon-attachment-type'); + icon = getCSSItemTypeIcon('attachmentEPUB', 'attachment-type'); ariaLabel = Zotero.getString('pane.item.attachments.hasEPUB'); } else { - icon = getCSSItemTypeIcon('attachmentFile', 'icon-attachment-type'); + icon = getCSSItemTypeIcon('attachmentFile', 'attachment-type'); ariaLabel = Zotero.getString('pane.item.attachments.has'); } diff --git a/chrome/content/zotero/tabs.js b/chrome/content/zotero/tabs.js index b3462d3963..4ca49e8a2e 100644 --- a/chrome/content/zotero/tabs.js +++ b/chrome/content/zotero/tabs.js @@ -29,6 +29,7 @@ var React = require('react'); var ReactDOM = require('react-dom'); import TabBar from 'components/tabBar'; +import { CSSIcon, CSSItemTypeIcon } from 'components/icons'; // Reduce loaded tabs limit if the system has 8 GB or less memory. // TODO: Revise this after upgrading to Zotero 7 @@ -81,13 +82,33 @@ var Zotero_Tabs = new function () { }; this._update = function () { - this._tabBarRef.current.setTabs(this._tabs.map(tab => ({ - id: tab.id, - type: tab.type, - title: tab.title, - selected: tab.id == this._selectedID, - iconBackgroundImage: tab.iconBackgroundImage - }))); + this._tabBarRef.current.setTabs(this._tabs.map((tab) => { + let icon = null; + if (tab.id === 'zotero-pane') { + let index = ZoteroPane.collectionsView?.selection?.focused; + if (typeof index !== 'undefined' && ZoteroPane.collectionsView.getRow(index)) { + let iconName = ZoteroPane.collectionsView.getIconName(index); + icon = ; + } + } + else if (tab.data?.itemID) { + try { + let item = Zotero.Items.get(tab.data.itemID); + icon = ; + } + catch (e) { + // item might not yet be loaded, we will get the icon on the next update + } + } + + return { + id: tab.id, + type: tab.type, + title: tab.title, + selected: tab.id == this._selectedID, + icon, + }; + })); // Disable File > Close menuitem if multiple tabs are open const multipleTabsOpen = this._tabs.length > 1; document.getElementById('cmd_close').setAttribute('disabled', multipleTabsOpen); @@ -243,23 +264,9 @@ var Zotero_Tabs = new function () { return; } tab.title = title; - Zotero_Tabs.updateLibraryTabIcon(); this._update(); }; - this.updateLibraryTabIcon = () => { - let index = ZoteroPane.collectionsView.selection.focused; - if (!ZoteroPane.collectionsView.getRow(index)) { - return; - } - let icon = ZoteroPane.collectionsView._getIcon(index); - var { tab } = this._getTab('zotero-pane'); - if (!tab || !icon.style.backgroundImage) { - return; - } - tab.iconBackgroundImage = icon.style.backgroundImage; - }; - /** * Close tabs * diff --git a/chrome/content/zotero/zoteroPane.xhtml b/chrome/content/zotero/zoteroPane.xhtml index 0378c0c170..2ed1a7df8c 100644 --- a/chrome/content/zotero/zoteroPane.xhtml +++ b/chrome/content/zotero/zoteroPane.xhtml @@ -754,7 +754,7 @@ - +
diff --git a/chrome/skin/default/zotero/20/universal/chevron-tabs.svg b/chrome/skin/default/zotero/20/universal/chevron-tabs.svg new file mode 100644 index 0000000000..1f78adc320 --- /dev/null +++ b/chrome/skin/default/zotero/20/universal/chevron-tabs.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scss/base/_base.scss b/scss/base/_base.scss index 2784558c89..b89644c6e0 100644 --- a/scss/base/_base.scss +++ b/scss/base/_base.scss @@ -19,3 +19,7 @@ width: 290px; background: var(--material-background); } + +#zotero-title-bar { + border-bottom: var(--material-panedivider); +} \ No newline at end of file diff --git a/scss/components/_collection-tree.scss b/scss/components/_collection-tree.scss index de2473d695..fb940e0382 100644 --- a/scss/components/_collection-tree.scss +++ b/scss/components/_collection-tree.scss @@ -3,6 +3,14 @@ $icons: ( library-group, publications, trash-full, trash, unfiled, retracted, search, ); +@each $icon in $icons { + .icon-css.icon-#{$icon} { + @include focus-states using ($color) { + @include svgicon($icon, $color, "16", "collection-tree"); + } + } +} + #zotero-collections-tree-container { height: 5.2em; } @@ -22,7 +30,7 @@ $icons: ( } @each $icon in $icons { - .icon-css.#{$icon} { + .icon-css.icon-#{$icon} { @include focus-states using ($color) { @include svgicon($icon, $color, "16", "collection-tree"); } diff --git a/scss/components/_icons.scss b/scss/components/_icons.scss index 48adf1c131..27d1a055c1 100644 --- a/scss/components/_icons.scss +++ b/scss/components/_icons.scss @@ -25,6 +25,11 @@ -moz-appearance: none !important; } +.icon-20 { + width: 20px; + height: 20px; +} + .icon-16 { width: 16px; height: 16px; @@ -40,6 +45,8 @@ $-icons: ( chevron-6: 8, filter: 16, note: 16, + x-8: 16, + chevron-tabs: 20 ); @each $icon, $size in $-icons { diff --git a/scss/components/_mainWindow.scss b/scss/components/_mainWindow.scss index 5828608eb2..c3f4c31447 100644 --- a/scss/components/_mainWindow.scss +++ b/scss/components/_mainWindow.scss @@ -1,12 +1,7 @@ // Styling for displaying tabs in the title bar :root:not([legacytoolbar="true"]) { & { - @media (-moz-platform: windows) { - --tab-min-height: 36px; - } - @media not (-moz-platform: windows) { - --tab-min-height: 30px; - } + --tab-min-height: 36px; --tabs-border-color: rgba(0,0,0,.3); --tabline-color: #0a84ff; @@ -138,14 +133,6 @@ background: var(--material-tabbar); -moz-window-dragging: drag; } - - #tab-bar-container .tab { - background: transparent; - } - - #tab-bar-container .tab.selected { - background: var(--material-tabbar) - } .zotero-toolbar { -moz-appearance: none; diff --git a/scss/components/_tabBar.scss b/scss/components/_tabBar.scss index 3b60ca0c1a..d416eb34e3 100644 --- a/scss/components/_tabBar.scss +++ b/scss/components/_tabBar.scss @@ -4,7 +4,7 @@ --safe-area-end: 0; --tab-border: .5px solid var(--tab-border); - min-height: 30px; + min-height: var(--tab-min-height); &:not([hidden]) { // display: flex overrides the hidden attribute @@ -39,51 +39,77 @@ } .scroll-start-arrow, .scroll-end-arrow { - height: 30px; - padding: 0 3px; + padding: 0 4px; align-items: center; - z-index: 1; - color: var(--fill-tertiary); + color: var(--fill-secondary); display: none; box-shadow: none; - // Add bottom border to scroll arrows, - // and negate the extra vertical pixel added by the border with a negative margin - border-bottom: var(--tab-border); - margin-bottom: -1px; - box-sizing: border-box; + z-index: 1; + + > button { + width: auto; + padding: 0; + border: 0; + height: 28px; + border-radius: 5px; + background: transparent; + } .icon { display: flex; + fill: var(--fill-tertiary); } &.active { - color: var(--fill-secondary); - &:hover { - background-color: var(--fill-quinary); + button { + &:hover { + background-color: var(--fill-quinary); + } + + &:active { + background-color: var(--fill-quarternary); + } + } + + .icon { + fill: var(--fill-secondary); } } } .scroll-start-arrow { border-right: var(--material-border-transparent); + border-left: var(--material-border-quarternary); + &.active { border-right: var(--material-panedivider); - box-shadow: 3px 0 3px -3px rgba(0, 0, 0, .3); + box-shadow: 0.5px 0.5px 0px -0.5px rgba(0, 0, 0, 0.05), 3px 0px 3px -3px rgba(0, 0, 0, .3) } } .scroll-end-arrow { border-left: var(--material-border-transparent); + &.active { border-left: var(--material-panedivider); - box-shadow: -3px 0 3px -3px rgba(0, 0, 0, .3) + box-shadow: -0.5px 0.5px 0px -0.5px rgba(0, 0, 0, 0.05), -3px 0 3px -3px rgba(0, 0, 0, .3) + } + + .icon { + transform: rotate(180deg); } } .pinned-tabs { display: none; + padding-right: 8px; + + .tabs { + overflow: unset; + } + .tab { - max-width: 110px; + max-width: 100px; } } @@ -108,74 +134,76 @@ overflow: hidden; left: 0; right: 0; + padding: 4px 1px; + column-gap: 4px; - &:before { - content: ""; - width: 0; - min-width: 0; - border-inline-end: var(--tab-border); - } - - &:after { - content: ""; - flex: 1 0 0; - width: 100%; - min-width: 0; - // Add bottom border to the space between the last tab and the spacer in #tab-bar-container::after - border-bottom: var(--tab-border); - border-inline-start: var(--tab-border); + .pinned-tabs & { + padding: 4px 0; } } .tab { box-sizing: border-box; - -moz-appearance: none; max-width: 200px; flex: 1 1 200px; - height: 30px; - line-height: 30px; position: relative; color: var(--fill-primary); text-align: center; - padding: 0 22px; - min-width: 110px; - - &:not(:last-child) { - border-inline-end: var(--tab-border); - } - + min-width: 100px; + height: 28px; + line-height: 16px; + padding: 6px 22px 6px 6px; + display: flex; + border-radius: 5px; + transition: background-color 0.1s ease-out; + &.selected { background: var(--material-button); box-shadow: 0px 0px 0px 0.5px rgba(0, 0, 0, 0.05), 0px 0.5px 2.5px 0px rgba(0, 0, 0, 0.30); } - + &.dragging { - border-inline-start: var(--tab-border); z-index: 1; } - &.dragging + & { - border-inline-start: var(--tab-border); + &:not(:last-child) { + border-inline-end: var(--tab-border); + } + + &:not(.selected):hover { + background-color: var(--fill-quinary); + } + + &:first-child { + padding: 6px; // no 22px padding for X button because pinned tab cannot be closed + + .tab-close { + display: none; + } } .tab-name { - line-height: 30px; - overflow-x: hidden; - text-overflow: ellipsis; - display: -moz-box; - -moz-box-pack: center; - position: relative; - top: -2px; - overflow-y: hidden; + background: linear-gradient(to left, transparent 0px, var(--fill-primary) 20px) text; + flex: 1 1 100%; + line-height: 16px; + margin-inline-start: 4px; + overflow: hidden; + text-align: left; + -webkit-text-fill-color: transparent; + white-space: nowrap; - .icon-bg { - margin-inline-end: 7px; - margin-top: -2px; + @include state('.tab:first-child') { + background: linear-gradient(to left, transparent 0px, var(--fill-primary) 10px) text; } } + .tab-icon { + flex: 0 0 16px; + } + .tab-close { position: absolute; + transition: background-color 0.1s ease-out; &:dir(ltr) { right: 6px; @@ -205,10 +233,6 @@ } } - &:first-child .tab-close { - display: none; - } - &:not(.selected) { border-bottom: var(--tab-border); } diff --git a/scss/mac/_tabBar.scss b/scss/mac/_tabBar.scss index c11f6cd4be..7db31ab7f9 100644 --- a/scss/mac/_tabBar.scss +++ b/scss/mac/_tabBar.scss @@ -1,5 +1,5 @@ #tab-bar-container { - --safe-area-start: 78px; + --safe-area-start: 73px; --safe-area-end: 20px; }