Initial tab bar implementation
This commit is contained in:
parent
199619f40e
commit
875e9f674f
10 changed files with 929 additions and 490 deletions
110
chrome/content/zotero/components/tabBar.jsx
Normal file
110
chrome/content/zotero/components/tabBar.jsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2020 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
import React, { forwardRef, useState, useImperativeHandle, useEffect } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
const TabBar = forwardRef(function (props, ref) {
|
||||
const [tabs, setTabs] = useState(props.initialTabs);
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
addTab({ title, type }) {
|
||||
var newTabs = [...tabs];
|
||||
newTabs.push({ title, type });
|
||||
setTabs(newTabs);
|
||||
setSelectedIndex(newTabs.length - 1);
|
||||
},
|
||||
|
||||
renameTab(title, index) {
|
||||
var newTabs = tabs.map((tab, currentIndex) => {
|
||||
let newTab = Object.assign({}, tab);
|
||||
if (index == currentIndex) {
|
||||
newTab.title = title;
|
||||
}
|
||||
return newTab;
|
||||
});
|
||||
setTabs(newTabs);
|
||||
}
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (props.onTabSelected) {
|
||||
props.onTabSelected(selectedIndex);
|
||||
}
|
||||
}, [selectedIndex]);
|
||||
|
||||
|
||||
function removeTab(index) {
|
||||
var newTabs = [...tabs];
|
||||
newTabs.splice(index, 1);
|
||||
setTabs(newTabs);
|
||||
setSelectedIndex(selectedIndex - 1);
|
||||
if (props.onTabClosed) {
|
||||
props.onTabClosed(index);
|
||||
}
|
||||
}
|
||||
|
||||
function handleTabClick(event, index) {
|
||||
setSelectedIndex(index);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function handleTabClose(event, index) {
|
||||
removeTab(index);
|
||||
if (props.onTabClose) {
|
||||
props.onTabClose(index);
|
||||
}
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function renderTab(tab, index) {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={cx("tab", { selected: index == selectedIndex })}
|
||||
onClick={(event) => handleTabClick(event, index)}
|
||||
>
|
||||
<div className="tab-name">{tab.title}</div>
|
||||
<div
|
||||
className="tab-close"
|
||||
onClick={(event) => handleTabClose(event, index)}
|
||||
>
|
||||
X
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tabs">
|
||||
{ tabs.map((tab, index) => renderTab(tab, index)) }
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default TabBar;
|
|
@ -43,9 +43,13 @@
|
|||
|
||||
<window id="main-window"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
windowtype="navigator:browser"
|
||||
title="&brandShortName;"
|
||||
width="1000" height="600"
|
||||
drawintitlebar="true"
|
||||
tabsintitlebar="true"
|
||||
chromemargin="0,-1,-1,-1"
|
||||
persist="screenX screenY width height sizemode">
|
||||
<script type="application/javascript" src="resource://zotero/require.js"/>
|
||||
<script type="application/javascript" src="standalone.js"/>
|
||||
|
@ -111,7 +115,16 @@
|
|||
accesskey="&selectAllCmd.accesskey;"
|
||||
command="cmd_selectAll"/>
|
||||
</menupopup>
|
||||
|
||||
<vbox id="titlebar">
|
||||
<hbox id="titlebar-buttonbox-container" skipintoolbarset="true">
|
||||
<hbox id="titlebar-buttonbox">
|
||||
<toolbarbutton class="titlebar-button titlebar-min" oncommand="window.minimize();"/>
|
||||
<toolbarbutton class="titlebar-button titlebar-max" oncommand="onTitlebarMaxClick();"/>
|
||||
<toolbarbutton class="titlebar-button titlebar-close" command="cmd_closeWindow"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox id="tab-bar-container" style="-moz-window-dragging: drag"/>
|
||||
<toolbox id="navigator-toolbox" class="toolbox-top" mode="icons" defaultmode="icons">
|
||||
<!-- Menu -->
|
||||
<toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
|
||||
|
|
105
chrome/content/zotero/tabs.js
Normal file
105
chrome/content/zotero/tabs.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2020 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Using 'import' breaks hooks
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
import TabBar from 'components/tabBar';
|
||||
|
||||
var Zotero_Tabs = new function () {
|
||||
const HTML_NS = 'http://www.w3.org/1999/xhtml';
|
||||
|
||||
Object.defineProperty(this, 'deck', {
|
||||
get: () => document.getElementById('tabs-deck')
|
||||
});
|
||||
|
||||
this._tabBarRef = {};
|
||||
this._tabs = [
|
||||
{
|
||||
title: "",
|
||||
type: 'library'
|
||||
}
|
||||
];
|
||||
|
||||
this.init = function () {
|
||||
ReactDOM.render(
|
||||
<TabBar
|
||||
ref={this._tabBarRef}
|
||||
initialTabs={this._tabs}
|
||||
onTabSelected={this._onTabSelected.bind(this)}
|
||||
onTabClosed={this._onTabClosed.bind(this)}
|
||||
/>,
|
||||
document.getElementById('tab-bar-container')
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Element} - The element created in the deck
|
||||
*/
|
||||
this.add = function ({ title, type, url, index }) {
|
||||
this._tabBarRef.current.addTab({ title, type });
|
||||
|
||||
var elem;
|
||||
if (url) {
|
||||
elem = document.createElement('iframe');
|
||||
elem.setAttribute('type', 'content');
|
||||
elem.setAttribute('src', url);
|
||||
}
|
||||
else {
|
||||
elem = document.createElementNS(HTML_NS, 'div');
|
||||
elem.textContent = title;
|
||||
}
|
||||
|
||||
var deck = this.deck;
|
||||
deck.insertBefore(elem, index === undefined ? null : deck.childNodes[index]);
|
||||
|
||||
return elem;
|
||||
};
|
||||
|
||||
|
||||
this.rename = function (title, index) {
|
||||
if (index === undefined) {
|
||||
index = this.deck.selectedIndex;
|
||||
}
|
||||
this._tabs[index].title = title;
|
||||
this._tabBarRef.current.renameTab(title, index);
|
||||
};
|
||||
|
||||
|
||||
this._onTabSelected = function (index) {
|
||||
this.deck.selectedIndex = index;
|
||||
};
|
||||
|
||||
|
||||
this._onTabClosed = function (index) {
|
||||
this._tabs.splice(index, 1);
|
||||
var deck = this.deck;
|
||||
deck.removeChild(deck.childNodes[index]);
|
||||
};
|
||||
};
|
|
@ -144,6 +144,7 @@ var ZoteroPane = new function()
|
|||
Zotero.hiDPI = window.devicePixelRatio > 1;
|
||||
Zotero.hiDPISuffix = Zotero.hiDPI ? "@2x" : "";
|
||||
|
||||
Zotero_Tabs.init();
|
||||
ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
|
||||
|
||||
// Add a default progress window
|
||||
|
@ -1166,6 +1167,10 @@ var ZoteroPane = new function()
|
|||
return;
|
||||
}
|
||||
|
||||
// Rename tab
|
||||
// TODO: What if PDF is in front?
|
||||
Zotero_Tabs.rename(collectionTreeRow.getName());
|
||||
|
||||
// Clear quick search and tag selector when switching views
|
||||
document.getElementById('zotero-tb-search').value = "";
|
||||
if (ZoteroPane.tagSelector) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,8 +25,10 @@
|
|||
@import "components/createParent";
|
||||
@import "components/editable";
|
||||
@import "components/icons";
|
||||
@import "components/mainWindow";
|
||||
@import "components/progressMeter";
|
||||
@import "components/search";
|
||||
@import "components/syncButtonTooltip";
|
||||
@import "components/tabsBar";
|
||||
@import "components/tagsBox";
|
||||
@import "components/tagSelector";
|
||||
|
|
149
scss/components/_mainWindow.scss
Normal file
149
scss/components/_mainWindow.scss
Normal file
|
@ -0,0 +1,149 @@
|
|||
:root {
|
||||
--tab-min-height: 30px;
|
||||
--tabs-border-color: rgba(0,0,0,.3);
|
||||
--tabline-color: #0a84ff;
|
||||
|
||||
--toolbar-non-lwt-bgcolor: #f9f9fa;
|
||||
--toolbar-non-lwt-textcolor: #0c0c0d;
|
||||
--toolbar-non-lwt-bgimage: none;
|
||||
--toolbar-bgcolor: var(--toolbar-non-lwt-bgcolor);
|
||||
--toolbar-bgimage: var(--toolbar-non-lwt-bgimage);
|
||||
--chrome-content-separator-color: hsl(0, 0%, 60%);
|
||||
|
||||
--toolbarbutton-border-radius: 3px;
|
||||
--toolbarbutton-icon-fill-opacity: .85;
|
||||
--toolbarbutton-hover-background: hsla(0, 0%, 100%, .1)
|
||||
linear-gradient(hsla(0, 0%, 100%, .3),
|
||||
hsla(0, 0%, 100%, .1)) no-repeat;
|
||||
--toolbarbutton-hover-bordercolor: hsla(0, 0%, 0%, .2);
|
||||
--toolbarbutton-header-bordercolor: hsla(0, 0%, 0%, .2);
|
||||
--toolbarbutton-hover-boxshadow: 0 1px 0 hsla(0, 0%, 100%, .5),
|
||||
0 1px 0 hsla(0, 0%, 100%, .5) inset;
|
||||
|
||||
--toolbarbutton-active-background: hsla(0, 0%, 0%, .02)
|
||||
linear-gradient(hsla(0, 0%, 0%, .12),
|
||||
transparent) border-box;
|
||||
--toolbarbutton-active-bordercolor: hsla(0, 0%, 0%, .3);
|
||||
--toolbarbutton-active-boxshadow: 0 1px 0 hsla(0, 0%, 100%, .5),
|
||||
0 1px 0 hsla(0, 0%, 0%, .05) inset,
|
||||
0 1px 1px hsla(0, 0%, 0%, .2) inset;
|
||||
--toolbarbutton-inactive-bordercolor: rgba(0, 0, 0, 0.1);
|
||||
--toolbarbutton-inactive-boxshadow: 0 1px 0 hsla(0, 0%, 0%, .05) inset;
|
||||
--toolbarbutton-checkedhover-backgroundcolor: hsla(0, 0%, 0%, .09);
|
||||
--toolbarbutton-icon-fill-attention: var(--lwt-toolbarbutton-icon-fill-attention, #0a84ff);
|
||||
|
||||
--lwt-header-image: none;
|
||||
--row-grouped-header-bg-color: #d5d5d5;
|
||||
--row-grouped-header-bg-color-selected: #3874d1;
|
||||
--panel-separator-color: hsla(210, 4%, 10%, .14);
|
||||
--arrowpanel-dimmed: hsla(0, 0%, 80%, .3);
|
||||
--arrowpanel-dimmed-further: hsla(0, 0%, 80%, .45);
|
||||
--splitter-color: #bdbdbd;
|
||||
--urlbar-popup-url-color: hsl(210, 77%, 47%);
|
||||
--urlbar-popup-action-color: hsl(178, 100%, 28%);
|
||||
}
|
||||
|
||||
:root {
|
||||
--autocomplete-popup-background: -moz-field;
|
||||
--autocomplete-popup-color: -moz-fieldtext;
|
||||
--autocomplete-popup-border-color: ThreeDShadow;
|
||||
--autocomplete-popup-highlight-background: Highlight;
|
||||
--autocomplete-popup-highlight-color: HighlightText;
|
||||
/* Note: Setting this to 0 (without px) breaks CSS calculations for OSX. */
|
||||
--space-above-tabbar: 0px;
|
||||
}
|
||||
|
||||
:root:-moz-window-inactive {
|
||||
--toolbar-bgcolor: -moz-mac-chrome-inactive;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#titlebar {
|
||||
margin-bottom: -30px;
|
||||
-moz-box-pack: center;
|
||||
}
|
||||
|
||||
#personal-bookmarks {
|
||||
-moz-window-dragging: inherit;
|
||||
}
|
||||
|
||||
toolbarpaletteitem {
|
||||
-moz-window-dragging: no-drag;
|
||||
-moz-box-pack: start;
|
||||
}
|
||||
|
||||
.titlebar-buttonbox-container {
|
||||
-moz-box-ordinal-group: 1000;
|
||||
}
|
||||
|
||||
#titlebar-fullscreen-button {
|
||||
-moz-appearance: -moz-mac-fullscreen-button;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ::::: Tabs in Titlebar :::::: */
|
||||
|
||||
/**
|
||||
* For tabs in titlebar on OS X, we stretch the titlebar down so that the
|
||||
* tabstrip can overlap it.
|
||||
*/
|
||||
#main-window[tabsintitlebar] > #titlebar {
|
||||
min-height: calc(var(--tab-min-height) + var(--space-above-tabbar));
|
||||
}
|
||||
|
||||
#main-window[tabsintitlebar="true"]:not(:-moz-lwtheme) > #titlebar {
|
||||
-moz-appearance: -moz-window-titlebar;
|
||||
}
|
||||
|
||||
#main-window:not([tabsintitlebar]) .titlebar-placeholder {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
/* NB: these would be margin-inline-start/end if it wasn't for the fact that OS X
|
||||
* doesn't reverse the order of the items in the titlebar in RTL mode. */
|
||||
.titlebar-placeholder[type="caption-buttons"],
|
||||
#titlebar-buttonbox {
|
||||
margin-right: 12px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.titlebar-placeholder[type="fullscreen-button"],
|
||||
#titlebar-fullscreen-button {
|
||||
margin-right: 7px;
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
#titlebar-fullscreen-button {
|
||||
-moz-appearance: -moz-mac-fullscreen-button;
|
||||
}
|
||||
|
||||
#titlebar-buttonbox {
|
||||
-moz-appearance: -moz-window-button-box;
|
||||
}
|
||||
|
||||
/* Fullscreen and caption buttons don't move with RTL on OS X so override the automatic ordering. */
|
||||
#titlebar-fullscreen-button:-moz-locale-dir(ltr),
|
||||
#titlebar-buttonbox-container:-moz-locale-dir(rtl),
|
||||
.titlebar-placeholder[type="fullscreen-button"]:-moz-locale-dir(ltr),
|
||||
.titlebar-placeholder[type="caption-buttons"]:-moz-locale-dir(rtl) {
|
||||
-moz-box-ordinal-group: 1000;
|
||||
}
|
||||
|
||||
#titlebar-fullscreen-button:-moz-locale-dir(rtl),
|
||||
#titlebar-buttonbox-container:-moz-locale-dir(ltr),
|
||||
.titlebar-placeholder[type="caption-buttons"]:-moz-locale-dir(ltr),
|
||||
.titlebar-placeholder[type="fullscreen-button"]:-moz-locale-dir(rtl) {
|
||||
-moz-box-ordinal-group: 0;
|
||||
}
|
||||
|
||||
#main-window[sizemode="fullscreen"] .titlebar-placeholder[type="fullscreen-button"],
|
||||
#main-window[sizemode="fullscreen"] .titlebar-placeholder[type="caption-buttons"] {
|
||||
display: none;
|
||||
}
|
42
scss/components/_tabsBar.scss
Normal file
42
scss/components/_tabsBar.scss
Normal file
|
@ -0,0 +1,42 @@
|
|||
#tab-bar-container {
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tab {
|
||||
-moz-appearance: toolbar;
|
||||
width: 200px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
position: relative;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid black;
|
||||
margin-right: -1px;
|
||||
border-bottom: 1px solid transparent;
|
||||
color: #000;
|
||||
|
||||
.tab-name {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.tab-close {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 7px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover:not(:first-child) .tab-close {
|
||||
display: block;
|
||||
}
|
||||
}
|
9
scss/mac/_tabsBar.scss
Normal file
9
scss/mac/_tabsBar.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
.tabs {
|
||||
-moz-window-dragging: drag;
|
||||
margin-left: 78px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
-moz-window-dragging: no-drag;
|
||||
|
||||
}
|
|
@ -8,4 +8,5 @@
|
|||
@import "mac/createParent";
|
||||
@import "mac/editable";
|
||||
@import "mac/search";
|
||||
@import "mac/tabsBar";
|
||||
@import "mac/tag-selector";
|
||||
|
|
Loading…
Reference in a new issue