Restore tabs state (#2148)

This commit is contained in:
Martynas Bagdonas 2021-08-23 20:02:57 +03:00 committed by GitHub
parent f9035c8fda
commit 85b142ccb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 243 additions and 68 deletions

View file

@ -58,7 +58,7 @@ var ZoteroContextPane = new function () {
this.update = _update; this.update = _update;
this.getActiveEditor = _getActiveEditor; this.getActiveEditor = _getActiveEditor;
this.onLoad = function () { this.init = function () {
if (!Zotero) { if (!Zotero) {
return; return;
} }
@ -83,8 +83,33 @@ var ZoteroContextPane = new function () {
else { else {
_tabToolbar.style.right = 0; _tabToolbar.style.right = 0;
} }
// vbox
var vbox = document.createElement('vbox');
vbox.setAttribute('flex', '1');
_init(); _contextPaneInner.append(vbox);
// Toolbar extension
var toolbarExtension = document.createElement('box');
toolbarExtension.style.height = '32px';
toolbarExtension.id = 'zotero-context-toolbar-extension';
_panesDeck = document.createElement('deck');
_panesDeck.setAttribute('flex', 1);
_panesDeck.setAttribute('selectedIndex', 0);
vbox.append(toolbarExtension, _panesDeck);
// Item pane deck
_itemPaneDeck = document.createElement('deck');
// Notes pane deck
_notesPaneDeck = document.createElement('deck');
_notesPaneDeck.style.backgroundColor = 'white';
_notesPaneDeck.setAttribute('flex', 1);
_notesPaneDeck.className = 'notes-pane-deck';
_panesDeck.append(_itemPaneDeck, _notesPaneDeck);
this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'tab'], 'contextPane'); this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'tab'], 'contextPane');
window.addEventListener('resize', _update); window.addEventListener('resize', _update);
@ -94,7 +119,7 @@ var ZoteroContextPane = new function () {
Zotero.Reader.onChangeSidebarOpen = _updatePaneWidth; Zotero.Reader.onChangeSidebarOpen = _updatePaneWidth;
}; };
this.onUnload = function () { this.destroy = function () {
_itemToggle.removeEventListener('click', _toggleItemButton); _itemToggle.removeEventListener('click', _toggleItemButton);
_notesToggle.removeEventListener('click', _toggleNotesButton); _notesToggle.removeEventListener('click', _toggleNotesButton);
window.removeEventListener('resize', _update); window.removeEventListener('resize', _update);
@ -106,7 +131,7 @@ var ZoteroContextPane = new function () {
_notesContexts = []; _notesContexts = [];
}; };
this.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData) { this.notify = function (action, type, ids, extraData) {
if (type == 'item') { if (type == 'item') {
// Update, remove or re-create item panes // Update, remove or re-create item panes
for (let context of _itemContexts.slice()) { for (let context of _itemContexts.slice()) {
@ -174,14 +199,14 @@ var ZoteroContextPane = new function () {
|| !document.activeElement.closest('.context-node iframe[anonid="editor-view"]'))) { || !document.activeElement.closest('.context-node iframe[anonid="editor-view"]'))) {
reader.focus(); reader.focus();
} }
var attachment = await Zotero.Items.getAsync(reader.itemID);
if (attachment) {
_selectNotesContext(attachment.libraryID);
var notesContext = _getNotesContext(attachment.libraryID);
notesContext.updateFromCache();
}
})(); })();
var attachment = Zotero.Items.get(reader.itemID);
if (attachment) {
_selectNotesContext(attachment.libraryID);
var notesContext = _getNotesContext(attachment.libraryID);
notesContext.updateFromCache();
}
} }
_contextPaneSplitter.setAttribute('hidden', false); _contextPaneSplitter.setAttribute('hidden', false);
@ -193,7 +218,7 @@ var ZoteroContextPane = new function () {
_update(); _update();
} }
} }
}); };
function _toggleItemButton() { function _toggleItemButton() {
_togglePane(0); _togglePane(0);
@ -346,35 +371,6 @@ var ZoteroContextPane = new function () {
splitter.setAttribute('state', hide ? 'collapsed' : 'open'); splitter.setAttribute('state', hide ? 'collapsed' : 'open');
_update(); _update();
} }
function _init() {
// vbox
var vbox = document.createElement('vbox');
vbox.setAttribute('flex', '1');
_contextPaneInner.append(vbox);
// Toolbar extension
var toolbarExtension = document.createElement('box');
toolbarExtension.style.height = '32px';
toolbarExtension.id = 'zotero-context-toolbar-extension';
_panesDeck = document.createElement('deck');
_panesDeck.setAttribute('flex', 1);
_panesDeck.setAttribute('selectedIndex', 0);
vbox.append(toolbarExtension, _panesDeck);
// Item pane deck
_itemPaneDeck = document.createElement('deck');
// Notes pane deck
_notesPaneDeck = document.createElement('deck');
_notesPaneDeck.style.backgroundColor = 'white';
_notesPaneDeck.setAttribute('flex', 1);
_notesPaneDeck.className = 'notes-pane-deck';
_panesDeck.append(_itemPaneDeck, _notesPaneDeck);
}
function _getCurrentAttachment() { function _getCurrentAttachment() {
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
@ -737,7 +733,16 @@ var ZoteroContextPane = new function () {
} }
} }
function _addItemContext(tabID, itemID) { async function _addItemContext(tabID, itemID) {
var container = document.createElement('vbox');
container.id = tabID + '-context';
container.className = 'zotero-item-pane-content';
_itemPaneDeck.appendChild(container);
var { libraryID } = Zotero.Items.getLibraryAndKeyFromID(itemID);
var library = Zotero.Libraries.get(libraryID);
await library.waitForDataLoad('item');
var item = Zotero.Items.get(itemID); var item = Zotero.Items.get(itemID);
if (!item) { if (!item) {
return; return;
@ -745,11 +750,6 @@ var ZoteroContextPane = new function () {
var libraryID = item.libraryID; var libraryID = item.libraryID;
var readOnly = _isLibraryReadOnly(libraryID); var readOnly = _isLibraryReadOnly(libraryID);
var parentID = item.parentID; var parentID = item.parentID;
var container = document.createElement('vbox');
container.id = tabID + '-context';
container.className = 'zotero-item-pane-content';
_itemPaneDeck.appendChild(container);
var context = { var context = {
tabID, tabID,
@ -786,6 +786,3 @@ var ZoteroContextPane = new function () {
itemBox.item = parentItem; itemBox.item = parentItem;
} }
}; };
addEventListener('load', function (e) { ZoteroContextPane.onLoad(e); }, false);
addEventListener('unload', function (e) { ZoteroContextPane.onUnload(e); }, false);

View file

@ -85,17 +85,53 @@ var Zotero_Tabs = new function () {
); );
}; };
this.getState = function () {
return this._tabs.map((tab) => {
var o = {
type: tab.type,
title: tab.title,
};
if (tab.data) {
o.data = tab.data;
}
if (tab.id == this._selectedID) {
o.selected = true;
}
return o;
});
};
this.restoreState = function(tabs) {
for (let tab of tabs) {
if (tab.type === 'library') {
this.rename('zotero-pane', tab.title);
}
else if (tab.type === 'reader') {
if (Zotero.Items.exists(tab.data.itemID)) {
Zotero.Reader.open(tab.data.itemID,
null,
{
title: tab.title,
openInBackground: !tab.selected
}
);
}
}
}
};
/** /**
* Add a new tab * Add a new tab
* *
* @param {String} type * @param {String} type
* @param {String} title * @param {String} title
* @param {String} data - Extra data about the tab to pass to notifier and session
* @param {Integer} index * @param {Integer} index
* @param {Boolean} select * @param {Boolean} select
* @param {Function} onClose * @param {Function} onClose
* @return {{ id: string, container: XULElement}} id - tab id, container - a new tab container created in the deck * @return {{ id: string, container: XULElement}} id - tab id, container - a new tab container created in the deck
*/ */
this.add = function ({ type, title, index, select, onClose, notifierData }) { this.add = function ({ type, data, title, index, select, onClose }) {
if (typeof type != 'string') { if (typeof type != 'string') {
throw new Error(`'type' should be a string (was ${typeof type})`); throw new Error(`'type' should be a string (was ${typeof type})`);
} }
@ -112,11 +148,11 @@ var Zotero_Tabs = new function () {
var container = document.createElement('vbox'); var container = document.createElement('vbox');
container.id = id; container.id = id;
this.deck.appendChild(container); this.deck.appendChild(container);
var tab = { id, type, title, onClose }; var tab = { id, type, title, data, onClose };
index = index || this._tabs.length; index = index || this._tabs.length;
this._tabs.splice(index, 0, tab); this._tabs.splice(index, 0, tab);
this._update(); this._update();
Zotero.Notifier.trigger('add', 'tab', [id], { [id]: notifierData }, true); Zotero.Notifier.trigger('add', 'tab', [id], { [id]: data }, true);
if (select) { if (select) {
this.select(id); this.select(id);
} }

View file

@ -33,6 +33,7 @@ class ReaderInstance {
this._window = null; this._window = null;
this._iframeWindow = null; this._iframeWindow = null;
this._itemID = null; this._itemID = null;
this._title = '';
this._isReaderInitialized = false; this._isReaderInitialized = false;
this._showItemPaneToggle = false; this._showItemPaneToggle = false;
this._initPromise = new Promise((resolve, reject) => { this._initPromise = new Promise((resolve, reject) => {
@ -50,7 +51,11 @@ class ReaderInstance {
} }
async open({ itemID, state, location }) { async open({ itemID, state, location }) {
let item = await Zotero.Items.getAsync(itemID); let { libraryID } = Zotero.Items.getLibraryAndKeyFromID(itemID);
let library = Zotero.Libraries.get(libraryID);
await library.waitForDataLoad('item');
let item = Zotero.Items.get(itemID);
if (!item) { if (!item) {
return false; return false;
} }
@ -101,6 +106,7 @@ class ReaderInstance {
title = parentItem.getDisplayTitle(); title = parentItem.getDisplayTitle();
} }
} }
this._title = title;
this._setTitleValue(title); this._setTitleValue(title);
} }
@ -644,7 +650,7 @@ class ReaderInstance {
} }
class ReaderTab extends ReaderInstance { class ReaderTab extends ReaderInstance {
constructor({ itemID, sidebarWidth, sidebarOpen, bottomPlaceholderHeight }) { constructor({ itemID, title, sidebarWidth, sidebarOpen, bottomPlaceholderHeight, background }) {
super(); super();
this._itemID = itemID; this._itemID = itemID;
this._sidebarWidth = sidebarWidth; this._sidebarWidth = sidebarWidth;
@ -654,11 +660,11 @@ class ReaderTab extends ReaderInstance {
this._window = Services.wm.getMostRecentWindow('navigator:browser'); this._window = Services.wm.getMostRecentWindow('navigator:browser');
let { id, container } = this._window.Zotero_Tabs.add({ let { id, container } = this._window.Zotero_Tabs.add({
type: 'reader', type: 'reader',
title: '', title: title || '',
select: true, data: {
notifierData: {
itemID itemID
} },
select: !background
}); });
this.tabID = id; this.tabID = id;
this._tabContainer = container; this._tabContainer = container;
@ -832,6 +838,13 @@ class Reader {
return this._sidebarWidth; return this._sidebarWidth;
} }
async init() {
await Zotero.uiReadyPromise;
Zotero.Session.state.windows
.filter(x => x.type == 'reader' && Zotero.Items.exists(x.itemID))
.forEach(x => this.open(x.itemID, null, { title: x.title, openInWindow: true }));
}
_loadSidebarOpenState() { _loadSidebarOpenState() {
let win = Zotero.getMainWindow(); let win = Zotero.getMainWindow();
if (win) { if (win) {
@ -888,6 +901,10 @@ class Reader {
this.triggerAnnotationsImportCheck(reader._itemID); this.triggerAnnotationsImportCheck(reader._itemID);
} }
} }
if (event === 'add' || event === 'close') {
Zotero.Session.debounceSave();
}
} }
// Listen for parent item, PDF attachment and its annotations updates // Listen for parent item, PDF attachment and its annotations updates
else if (type === 'item') { else if (type === 'item') {
@ -932,19 +949,25 @@ class Reader {
getByTabID(tabID) { getByTabID(tabID) {
return this._readers.find(r => (r instanceof ReaderTab) && r.tabID === tabID); return this._readers.find(r => (r instanceof ReaderTab) && r.tabID === tabID);
} }
async openURI(itemURI, location, openWindow) { getWindowStates() {
let item = await Zotero.URI.getURIItem(itemURI); return this._readers
if (!item) return; .filter(r => r instanceof ReaderWindow)
await this.open(item.id, location, openWindow); .map(r => ({ type: 'reader', itemID: r._itemID, title: r._title }));
} }
async open(itemID, location, openWindow) { async openURI(itemURI, location, options) {
let item = await Zotero.URI.getURIItem(itemURI);
if (!item) return;
await this.open(item.id, location, options);
}
async open(itemID, location, { title, openInBackground, openInWindow } = {}) {
this._loadSidebarOpenState(); this._loadSidebarOpenState();
this.triggerAnnotationsImportCheck(itemID); this.triggerAnnotationsImportCheck(itemID);
let reader; let reader;
if (openWindow) { if (openInWindow) {
reader = this._readers.find(r => r._itemID === itemID && (r instanceof ReaderWindow)); reader = this._readers.find(r => r._itemID === itemID && (r instanceof ReaderWindow));
} }
else { else {
@ -960,7 +983,7 @@ class Reader {
reader.navigate(location); reader.navigate(location);
} }
} }
else if (openWindow) { else if (openInWindow) {
reader = new ReaderWindow({ reader = new ReaderWindow({
sidebarWidth: this._sidebarWidth, sidebarWidth: this._sidebarWidth,
sidebarOpen: this._sidebarOpen, sidebarOpen: this._sidebarOpen,
@ -970,13 +993,17 @@ class Reader {
if (!(await reader.open({ itemID, location }))) { if (!(await reader.open({ itemID, location }))) {
return; return;
} }
Zotero.Session.debounceSave();
reader._window.addEventListener('unload', () => { reader._window.addEventListener('unload', () => {
this._readers.splice(this._readers.indexOf(reader), 1); this._readers.splice(this._readers.indexOf(reader), 1);
Zotero.Session.debounceSave();
}); });
} }
else { else {
reader = new ReaderTab({ reader = new ReaderTab({
itemID, itemID,
title,
background: openInBackground,
sidebarWidth: this._sidebarWidth, sidebarWidth: this._sidebarWidth,
sidebarOpen: this._sidebarOpen, sidebarOpen: this._sidebarOpen,
bottomPlaceholderHeight: this._bottomPlaceholderHeight bottomPlaceholderHeight: this._bottomPlaceholderHeight

View file

@ -0,0 +1,80 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2021 Corporation for Digital Scholarship
Vienna, Virginia, USA
http://digitalscholar.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 *****
*/
Zotero.Session = new function () {
const SESSION_FILE_NAME = 'session.json';
const DEBOUNCED_SAVING_DELAY = 5 * 60 * 1000; // 5 min
let _state = {
windows: []
};
Zotero.defineProperty(this, 'state', {
get: () => {
return _state;
}
});
this.init = async function () {
try {
let sessionFile = OS.Path.join(Zotero.Profile.dir, SESSION_FILE_NAME);
let state = await Zotero.File.getContentsAsync(sessionFile);
_state = JSON.parse(state);
}
catch (e) {
Zotero.logError(e);
}
};
this.setLastClosedZoteroPaneState = function (state) {
_state.windows = [state];
};
this.debounceSave = Zotero.Utilities.debounce(() => {
this.save();
}, DEBOUNCED_SAVING_DELAY);
this.save = async function () {
try {
// Saving is triggered in `zotero.js` when a quit event is received,
// though if it was triggered by closing a window, ZoteroPane might
// be already destroyed at the time
let panes = Zotero.getZoteroPanes().map(x => x.getState());
let readers = Zotero.Reader.getWindowStates();
if (panes.length) {
_state.windows = [...readers, ...panes];
}
else if (readers.length) {
_state.windows = _state.windows.filter(x => x.type != 'reader');
_state.windows = [..._state.windows, ...readers];
}
let sessionFile = OS.Path.join(Zotero.Profile.dir, SESSION_FILE_NAME);
await Zotero.File.putContentsAsync(sessionFile, JSON.stringify(_state));
}
catch (e) {
Zotero.logError(e);
}
};
};

View file

@ -380,6 +380,12 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
// Make sure data directory isn't in Dropbox, etc. // Make sure data directory isn't in Dropbox, etc.
yield Zotero.DataDirectory.checkForUnsafeLocation(dataDir); yield Zotero.DataDirectory.checkForUnsafeLocation(dataDir);
Services.obs.addObserver({
observe: function () {
Zotero.Session.save();
}
}, "quit-application-granted", false);
// Register shutdown handler to call Zotero.shutdown() // Register shutdown handler to call Zotero.shutdown()
var _shutdownObserver = {observe:function() { Zotero.shutdown().done() }}; var _shutdownObserver = {observe:function() { Zotero.shutdown().done() }};
Services.obs.addObserver(_shutdownObserver, "quit-application", false); Services.obs.addObserver(_shutdownObserver, "quit-application", false);
@ -690,6 +696,8 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
yield Zotero.CharacterSets.init(); yield Zotero.CharacterSets.init();
yield Zotero.RelationPredicates.init(); yield Zotero.RelationPredicates.init();
yield Zotero.Session.init();
Zotero.locked = false; Zotero.locked = false;
// Initialize various services // Initialize various services
@ -727,6 +735,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
yield Zotero.Retractions.init(); yield Zotero.Retractions.init();
yield Zotero.NoteBackups.init(); yield Zotero.NoteBackups.init();
yield Zotero.Dictionaries.init(); yield Zotero.Dictionaries.init();
Zotero.Reader.init();
// Migrate fields from Extra that can be moved to item fields after a schema update // Migrate fields from Extra that can be moved to item fields after a schema update
yield Zotero.Schema.migrateExtraFields(); yield Zotero.Schema.migrateExtraFields();

View file

@ -153,6 +153,7 @@ var ZoteroPane = new function()
Zotero.hiDPISuffix = Zotero.hiDPI ? "@2x" : ""; Zotero.hiDPISuffix = Zotero.hiDPI ? "@2x" : "";
Zotero_Tabs.init(); Zotero_Tabs.init();
ZoteroContextPane.init();
await ZoteroPane.initCollectionsTree(); await ZoteroPane.initCollectionsTree();
await ZoteroPane.initItemsTree(); await ZoteroPane.initItemsTree();
@ -248,6 +249,17 @@ var ZoteroPane = new function()
var importer = new Zotero_Import_Mendeley(); var importer = new Zotero_Import_Mendeley();
importer.deleteNonPrimaryFiles(); importer.deleteNonPrimaryFiles();
}, 10000) }, 10000)
// Restore pane state
try {
let state = Zotero.Session.state.windows.find(x => x.type == 'pane');
if (state) {
Zotero_Tabs.restoreState(state.tabs);
}
}
catch (e) {
Zotero.logError(e);
}
} }
@ -355,6 +367,12 @@ var ZoteroPane = new function()
observerService.removeObserver(_reloadObserver, "zotero-reloaded"); observerService.removeObserver(_reloadObserver, "zotero-reloaded");
ZoteroContextPane.destroy();
if (!Zotero.getZoteroPanes().length) {
Zotero.Session.setLastClosedZoteroPaneState(this.getState());
}
Zotero_Tabs.closeAll(); Zotero_Tabs.closeAll();
} }
@ -3801,7 +3819,7 @@ var ZoteroPane = new function()
await Zotero.Reader.open( await Zotero.Reader.open(
itemID, itemID,
extraData && extraData.location, extraData && extraData.location,
originalEvent && originalEvent.shiftKey { openInWindow: originalEvent && originalEvent.shiftKey }
); );
return; return;
} }
@ -4855,6 +4873,13 @@ var ZoteroPane = new function()
} }
this.getState = function () {
return {
type: 'pane',
tabs: Zotero_Tabs.getState()
};
};
/** /**
* Unserializes zotero-persist elements from preferences * Unserializes zotero-persist elements from preferences
*/ */

View file

@ -120,6 +120,7 @@ const xpcomFilesLocal = [
'router', 'router',
'schema', 'schema',
'server', 'server',
'session',
'streamer', 'streamer',
'style', 'style',
'sync', 'sync',