Add split view for PDF reader (#2832)

This commit is contained in:
Martynas Bagdonas 2022-09-29 13:25:58 +03:00 committed by GitHub
parent a7e6fc330c
commit 6aad0cbb9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 158 additions and 26 deletions

View file

@ -163,6 +163,19 @@
label="&zotero.pdfReader.zoomPageHeight;" label="&zotero.pdfReader.zoomPageHeight;"
oncommand="menuCmd('zoomPageHeight')" oncommand="menuCmd('zoomPageHeight')"
/> />
<menuseparator class="menu-type-reader"/>
<menuitem
id="view-menuitem-split-vertically"
type="checkbox"
label="&zotero.pdfReader.splitVertically;"
oncommand="menuCmd('splitVertically')"
/>
<menuitem
id="view-menuitem-split-horizontally"
type="checkbox"
label="&zotero.pdfReader.splitHorizontally;"
oncommand="menuCmd('splitHorizontally')"
/>
</menupopup> </menupopup>
</menu> </menu>
<menu <menu

View file

@ -405,6 +405,8 @@ const ZoteroStandalone = new function() {
this.updateMenuItemCheckmark('view-menuitem-zoom-auto', reader.isZoomAutoActive()); this.updateMenuItemCheckmark('view-menuitem-zoom-auto', reader.isZoomAutoActive());
this.updateMenuItemCheckmark('view-menuitem-zoom-page-width', reader.isZoomPageWidthActive()); this.updateMenuItemCheckmark('view-menuitem-zoom-page-width', reader.isZoomPageWidthActive());
this.updateMenuItemCheckmark('view-menuitem-zoom-page-height', reader.isZoomPageHeightActive()); this.updateMenuItemCheckmark('view-menuitem-zoom-page-height', reader.isZoomPageHeightActive());
this.updateMenuItemCheckmark('view-menuitem-split-vertically', reader.isSplitVerticallyActive());
this.updateMenuItemCheckmark('view-menuitem-split-horizontally', reader.isSplitHorizontallyActive());
} }
// Layout mode // Layout mode

View file

@ -337,6 +337,21 @@
oncommand="ZoteroStandalone.onReaderCmd('zoomPageHeight')" oncommand="ZoteroStandalone.onReaderCmd('zoomPageHeight')"
/> />
<menuseparator class="menu-type-reader"/> <menuseparator class="menu-type-reader"/>
<menuitem
id="view-menuitem-split-vertically"
class="menu-type-reader"
type="checkbox"
label="&zotero.pdfReader.splitVertically;"
oncommand="ZoteroStandalone.onReaderCmd('splitVertically')"
/>
<menuitem
id="view-menuitem-split-horizontally"
class="menu-type-reader"
type="checkbox"
label="&zotero.pdfReader.splitHorizontally;"
oncommand="ZoteroStandalone.onReaderCmd('splitHorizontally')"
/>
<menuseparator class="menu-type-reader"/>
<menu id="layout-menu" label="&layout.label;"> <menu id="layout-menu" label="&layout.label;">
<menupopup oncommand="ZoteroStandalone.onViewMenuItemClick(event)"> <menupopup oncommand="ZoteroStandalone.onViewMenuItemClick(event)">

View file

@ -89,6 +89,12 @@ var Zotero_Tabs = new function () {
return tab && tab.id; return tab && tab.id;
}; };
this.setSecondViewState = function (tabID, state) {
let { tab } = this._getTab(tabID);
tab.data.secondViewState = state;
Zotero.Session.debounceSave();
};
this.init = function () { this.init = function () {
ReactDOM.render( ReactDOM.render(
<TabBar <TabBar
@ -138,7 +144,8 @@ var Zotero_Tabs = new function () {
null, null,
{ {
title: tab.title, title: tab.title,
openInBackground: !tab.selected openInBackground: !tab.selected,
secondViewState: tab.data.secondViewState
} }
); );
} }
@ -352,7 +359,8 @@ var Zotero_Tabs = new function () {
tabID: tab.id, tabID: tab.id,
title: tab.title, title: tab.title,
tabIndex, tabIndex,
allowDuplicate: true allowDuplicate: true,
secondViewState: tab.data.secondViewState
}); });
return; return;
} }
@ -458,7 +466,7 @@ var Zotero_Tabs = new function () {
}; };
this._openMenu = function (x, y, id) { this._openMenu = function (x, y, id) {
var { tabIndex } = this._getTab(id); var { tab, tabIndex } = this._getTab(id);
window.Zotero_Tooltip.stop(); window.Zotero_Tooltip.stop();
let menuitem; let menuitem;
let popup = document.createElement('menupopup'); let popup = document.createElement('menupopup');
@ -515,7 +523,8 @@ var Zotero_Tabs = new function () {
var reader = Zotero.Reader.getByTabID(id); var reader = Zotero.Reader.getByTabID(id);
if (reader) { if (reader) {
this.close(id); this.close(id);
Zotero.Reader.open(reader.itemID, null, { openInWindow: true }); let { secondViewState } = tab.data;
Zotero.Reader.open(reader.itemID, null, { openInWindow: true, secondViewState });
} }
}); });
menupopup.appendChild(menuitem); menupopup.appendChild(menuitem);
@ -523,11 +532,10 @@ var Zotero_Tabs = new function () {
menuitem = document.createElement('menuitem'); menuitem = document.createElement('menuitem');
menuitem.setAttribute('label', Zotero.getString('tabs.duplicate')); menuitem.setAttribute('label', Zotero.getString('tabs.duplicate'));
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
var { tab, tabIndex } = this._getTab(id);
if (tab.data.itemID) { if (tab.data.itemID) {
tabIndex++; tabIndex++;
Zotero.Reader.open(tab.data.itemID, null, { tabIndex, allowDuplicate: true }); let { secondViewState } = tab.data;
Zotero.Reader.open(tab.data.itemID, null, { tabIndex, allowDuplicate: true, secondViewState });
} }
}); });
popup.appendChild(menuitem); popup.appendChild(menuitem);

View file

@ -52,6 +52,11 @@ class ReaderInstance {
} }
} }
getSecondViewState() {
let state = this._iframeWindow.wrappedJSObject.getSecondViewState();
return state ? JSON.parse(JSON.stringify(state)) : undefined;
}
async migrateMendeleyColors(libraryID, annotations) { async migrateMendeleyColors(libraryID, annotations) {
let colorMap = new Map(); let colorMap = new Map();
colorMap.set('#fff5ad', '#ffd400'); colorMap.set('#fff5ad', '#ffd400');
@ -88,7 +93,7 @@ class ReaderInstance {
return true; return true;
} }
async open({ itemID, state, location }) { async open({ itemID, state, location, secondViewState }) {
let { libraryID } = Zotero.Items.getLibraryAndKeyFromID(itemID); let { libraryID } = Zotero.Items.getLibraryAndKeyFromID(itemID);
let library = Zotero.Libraries.get(libraryID); let library = Zotero.Libraries.get(libraryID);
await library.waitForDataLoad('item'); await library.waitForDataLoad('item');
@ -122,6 +127,7 @@ class ReaderInstance {
buf, buf,
annotations, annotations,
state, state,
secondViewState,
location, location,
readOnly: this._isReadOnly(), readOnly: this._isReadOnly(),
authorName: item.library.libraryType === 'group' ? Zotero.Users.getCurrentName() : '', authorName: item.library.libraryType === 'group' ? Zotero.Users.getCurrentName() : '',
@ -258,6 +264,14 @@ class ReaderInstance {
isZoomPageHeightActive() { isZoomPageHeightActive() {
return this._iframeWindow.eval('PDFViewerApplication.pdfViewer.currentScaleValue === "page-fit"'); return this._iframeWindow.eval('PDFViewerApplication.pdfViewer.currentScaleValue === "page-fit"');
} }
isSplitVerticallyActive() {
return this._iframeWindow.wrappedJSObject.getSplitType() === 'vertical';
}
isSplitHorizontallyActive() {
return this._iframeWindow.wrappedJSObject.getSplitType() === 'horizontal';
}
allowNavigateFirstPage() { allowNavigateFirstPage() {
return this._iframeWindow.eval('PDFViewerApplication.pdfViewer.currentPageNumber > 1'); return this._iframeWindow.eval('PDFViewerApplication.pdfViewer.currentPageNumber > 1');
@ -365,6 +379,12 @@ class ReaderInstance {
} }
return; return;
} }
else if (cmd === 'splitVertically') {
this._splitVertically();
}
else if (cmd === 'splitHorizontally') {
this._splitHorizontally();
}
let data = { let data = {
action: 'menuCmd', action: 'menuCmd',
@ -580,6 +600,26 @@ class ReaderInstance {
return `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect shape-rendering="geometricPrecision" fill="${fill}" stroke-width="2" x="2" y="2" stroke="${stroke}" width="12" height="12" rx="3"/></svg>`; return `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect shape-rendering="geometricPrecision" fill="${fill}" stroke-width="2" x="2" y="2" stroke="${stroke}" width="12" height="12" rx="3"/></svg>`;
} }
_splitVertically() {
if (this.isSplitVerticallyActive()) {
this._iframeWindow.wrappedJSObject.unsplitView();
}
else {
this._iframeWindow.wrappedJSObject.splitView();
}
setTimeout(() => this._updateSecondViewState(), 500);
}
_splitHorizontally() {
if (this.isSplitHorizontallyActive()) {
this._iframeWindow.wrappedJSObject.unsplitView();
}
else {
this._iframeWindow.wrappedJSObject.splitView(true);
}
setTimeout(() => this._updateSecondViewState(), 500);
}
_openTagsPopup(item, selector) { _openTagsPopup(item, selector) {
let menupopup = this._window.document.createElement('menupopup'); let menupopup = this._window.document.createElement('menupopup');
menupopup.className = 'tags-popup'; menupopup.className = 'tags-popup';
@ -598,7 +638,7 @@ class ReaderInstance {
} }
} }
_openPagePopup(data) { _openPagePopup(data, secondView) {
let popup = this._window.document.createElement('menupopup'); let popup = this._window.document.createElement('menupopup');
this._popupset.appendChild(popup); this._popupset.appendChild(popup);
popup.addEventListener('popuphidden', function () { popup.addEventListener('popuphidden', function () {
@ -619,14 +659,14 @@ class ReaderInstance {
menuitem = this._window.document.createElement('menuitem'); menuitem = this._window.document.createElement('menuitem');
menuitem.setAttribute('label', Zotero.getString('pdfReader.zoomIn')); menuitem.setAttribute('label', Zotero.getString('pdfReader.zoomIn'));
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
this._postMessage({ action: 'popupCmd', cmd: 'zoomIn' }); this._postMessage({ action: 'popupCmd', cmd: 'zoomIn' }, [], secondView);
}); });
popup.appendChild(menuitem); popup.appendChild(menuitem);
// Zoom out // Zoom out
menuitem = this._window.document.createElement('menuitem'); menuitem = this._window.document.createElement('menuitem');
menuitem.setAttribute('label', Zotero.getString('pdfReader.zoomOut')); menuitem.setAttribute('label', Zotero.getString('pdfReader.zoomOut'));
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
this._postMessage({ action: 'popupCmd', cmd: 'zoomOut' }); this._postMessage({ action: 'popupCmd', cmd: 'zoomOut' }, [], secondView);
}); });
popup.appendChild(menuitem); popup.appendChild(menuitem);
// Zoom 'Auto' // Zoom 'Auto'
@ -635,7 +675,7 @@ class ReaderInstance {
menuitem.setAttribute('type', 'checkbox'); menuitem.setAttribute('type', 'checkbox');
menuitem.setAttribute('checked', data.isZoomAuto); menuitem.setAttribute('checked', data.isZoomAuto);
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
this._postMessage({ action: 'popupCmd', cmd: 'zoomAuto' }); this._postMessage({ action: 'popupCmd', cmd: 'zoomAuto' }, [], secondView);
}); });
popup.appendChild(menuitem); popup.appendChild(menuitem);
// Zoom 'Page Width' // Zoom 'Page Width'
@ -644,7 +684,7 @@ class ReaderInstance {
menuitem.setAttribute('type', 'checkbox'); menuitem.setAttribute('type', 'checkbox');
menuitem.setAttribute('checked', data.isZoomPageWidth); menuitem.setAttribute('checked', data.isZoomPageWidth);
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
this._postMessage({ action: 'popupCmd', cmd: 'zoomPageWidth' }); this._postMessage({ action: 'popupCmd', cmd: 'zoomPageWidth' }, [], secondView);
}); });
popup.appendChild(menuitem); popup.appendChild(menuitem);
// Zoom 'Page Height' // Zoom 'Page Height'
@ -653,17 +693,33 @@ class ReaderInstance {
menuitem.setAttribute('type', 'checkbox'); menuitem.setAttribute('type', 'checkbox');
menuitem.setAttribute('checked', data.isZoomPageHeight); menuitem.setAttribute('checked', data.isZoomPageHeight);
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
this._postMessage({ action: 'popupCmd', cmd: 'zoomPageHeight' }); this._postMessage({ action: 'popupCmd', cmd: 'zoomPageHeight' }, [], secondView);
}); });
popup.appendChild(menuitem); popup.appendChild(menuitem);
// Separator // Separator
popup.appendChild(this._window.document.createElement('menuseparator')); popup.appendChild(this._window.document.createElement('menuseparator'));
// Split Vertically
menuitem = this._window.document.createElement('menuitem');
menuitem.setAttribute('label', Zotero.getString('pdfReader.splitVertically'));
menuitem.setAttribute('type', 'checkbox');
menuitem.setAttribute('checked', this.isSplitVerticallyActive());
menuitem.addEventListener('command', () => this._splitVertically());
popup.appendChild(menuitem);
// Split Horizontally
menuitem = this._window.document.createElement('menuitem');
menuitem.setAttribute('label', Zotero.getString('pdfReader.splitHorizontally'));
menuitem.setAttribute('type', 'checkbox');
menuitem.setAttribute('checked', this.isSplitHorizontallyActive());
menuitem.addEventListener('command', () => this._splitHorizontally());
popup.appendChild(menuitem);
// Separator
popup.appendChild(this._window.document.createElement('menuseparator'));
// Next page // Next page
menuitem = this._window.document.createElement('menuitem'); menuitem = this._window.document.createElement('menuitem');
menuitem.setAttribute('label', Zotero.getString('pdfReader.nextPage')); menuitem.setAttribute('label', Zotero.getString('pdfReader.nextPage'));
menuitem.setAttribute('disabled', !data.enableNextPage); menuitem.setAttribute('disabled', !data.enableNextPage);
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
this._postMessage({ action: 'popupCmd', cmd: 'nextPage' }); this._postMessage({ action: 'popupCmd', cmd: 'nextPage' }, [], secondView);
}); });
popup.appendChild(menuitem); popup.appendChild(menuitem);
// Previous page // Previous page
@ -671,7 +727,7 @@ class ReaderInstance {
menuitem.setAttribute('label', Zotero.getString('pdfReader.previousPage')); menuitem.setAttribute('label', Zotero.getString('pdfReader.previousPage'));
menuitem.setAttribute('disabled', !data.enablePrevPage); menuitem.setAttribute('disabled', !data.enablePrevPage);
menuitem.addEventListener('command', () => { menuitem.addEventListener('command', () => {
this._postMessage({ action: 'popupCmd', cmd: 'prevPage' }); this._postMessage({ action: 'popupCmd', cmd: 'prevPage' }, [], secondView);
}); });
popup.appendChild(menuitem); popup.appendChild(menuitem);
popup.openPopupAtScreen(data.x, data.y, true); popup.openPopupAtScreen(data.x, data.y, true);
@ -893,25 +949,52 @@ class ReaderInstance {
popup.openPopupAtScreen(data.x, data.y, true); popup.openPopupAtScreen(data.x, data.y, true);
} }
async _postMessage(message, transfer) { async _postMessage(message, transfer, secondView) {
await this._waitForReader(); await this._waitForReader();
this._iframeWindow.postMessage({ itemID: this._itemID, message }, this._iframeWindow.origin, transfer); this._iframeWindow.postMessage({ itemID: this._itemID, message, secondView }, this._iframeWindow.origin, transfer);
}
_updateSecondViewState() {
if (this.tabID) {
let win = Zotero.getMainWindow();
if (win) {
win.Zotero_Tabs.setSecondViewState(this.tabID, this.getSecondViewState());
}
}
} }
_handleMessage = async (event) => { _handleMessage = async (event) => {
let message; let message;
let secondViewIframeWindow = this._iframeWindow.document.getElementById('secondViewIframe');
if (secondViewIframeWindow) {
secondViewIframeWindow = secondViewIframeWindow.contentWindow;
}
try { try {
if (event.source !== this._iframeWindow) { if (event.source !== this._iframeWindow
&& event.source !== secondViewIframeWindow) {
return; return;
} }
// Clone data to avoid the dead object error when the window is closed // Clone data to avoid the dead object error when the window is closed
let data = JSON.parse(JSON.stringify(event.data)); let data = JSON.parse(JSON.stringify(event.data));
let { secondView } = data;
// Filter messages coming from previous reader instances, // Filter messages coming from previous reader instances,
// except for `setAnnotation` to still allow saving it // except for `setAnnotation` to still allow saving it
if (data.itemID !== this._itemID && data.message.action !== 'setAnnotation') { if (data.itemID !== this._itemID && data.message.action !== 'setAnnotation') {
return; return;
} }
message = data.message; message = data.message;
if (secondView) {
switch (message.action) {
case 'openPagePopup': break;
case 'setState': {
this._updateSecondViewState();
return;
}
default: return;
}
}
switch (message.action) { switch (message.action) {
case 'initialized': { case 'initialized': {
this._resolveInitPromise(); this._resolveInitPromise();
@ -998,7 +1081,7 @@ class ReaderInstance {
return; return;
} }
case 'openPagePopup': { case 'openPagePopup': {
this._openPagePopup(message.data); this._openPagePopup(message.data, secondView);
return; return;
} }
case 'openAnnotationPopup': { case 'openAnnotationPopup': {
@ -1302,6 +1385,8 @@ class ReaderWindow extends ReaderInstance {
this._window.document.getElementById('view-menuitem-zoom-auto').setAttribute('checked', this.isZoomAutoActive()); this._window.document.getElementById('view-menuitem-zoom-auto').setAttribute('checked', this.isZoomAutoActive());
this._window.document.getElementById('view-menuitem-zoom-page-width').setAttribute('checked', this.isZoomPageWidthActive()); this._window.document.getElementById('view-menuitem-zoom-page-width').setAttribute('checked', this.isZoomPageWidthActive());
this._window.document.getElementById('view-menuitem-zoom-page-height').setAttribute('checked', this.isZoomPageHeightActive()); this._window.document.getElementById('view-menuitem-zoom-page-height').setAttribute('checked', this.isZoomPageHeightActive());
this._window.document.getElementById('view-menuitem-split-vertically').setAttribute('checked', this.isSplitVerticallyActive());
this._window.document.getElementById('view-menuitem-split-horizontally').setAttribute('checked', this.isSplitHorizontallyActive());
} }
_onGoMenuOpen() { _onGoMenuOpen() {
@ -1361,7 +1446,7 @@ class Reader {
await Zotero.uiReadyPromise; await Zotero.uiReadyPromise;
Zotero.Session.state.windows Zotero.Session.state.windows
.filter(x => x.type == 'reader' && Zotero.Items.exists(x.itemID)) .filter(x => x.type == 'reader' && Zotero.Items.exists(x.itemID))
.forEach(x => this.open(x.itemID, null, { title: x.title, openInWindow: true })); .forEach(x => this.open(x.itemID, null, { title: x.title, openInWindow: true, secondViewState: x.secondViewState }));
} }
_loadSidebarState() { _loadSidebarState() {
@ -1484,7 +1569,12 @@ class Reader {
getWindowStates() { getWindowStates() {
return this._readers return this._readers
.filter(r => r instanceof ReaderWindow) .filter(r => r instanceof ReaderWindow)
.map(r => ({ type: 'reader', itemID: r._itemID, title: r._title })); .map(r => ({
type: 'reader',
itemID: r._itemID,
title: r._title,
secondViewState: r.getSecondViewState()
}));
} }
async openURI(itemURI, location, options) { async openURI(itemURI, location, options) {
@ -1493,7 +1583,7 @@ class Reader {
await this.open(item.id, location, options); await this.open(item.id, location, options);
} }
async open(itemID, location, { title, tabIndex, tabID, openInBackground, openInWindow, allowDuplicate } = {}) { async open(itemID, location, { title, tabIndex, tabID, openInBackground, openInWindow, allowDuplicate, secondViewState } = {}) {
this._loadSidebarState(); this._loadSidebarState();
this.triggerAnnotationsImportCheck(itemID); this.triggerAnnotationsImportCheck(itemID);
let reader; let reader;
@ -1534,7 +1624,7 @@ class Reader {
bottomPlaceholderHeight: this._bottomPlaceholderHeight bottomPlaceholderHeight: this._bottomPlaceholderHeight
}); });
this._readers.push(reader); this._readers.push(reader);
if (!(await reader.open({ itemID, location }))) { if (!(await reader.open({ itemID, location, secondViewState }))) {
return; return;
} }
Zotero.Session.debounceSave(); Zotero.Session.debounceSave();
@ -1555,7 +1645,7 @@ class Reader {
bottomPlaceholderHeight: this._bottomPlaceholderHeight bottomPlaceholderHeight: this._bottomPlaceholderHeight
}); });
this._readers.push(reader); this._readers.push(reader);
if (!(await reader.open({ itemID, location }))) { if (!(await reader.open({ itemID, location, secondViewState }))) {
return; return;
} }
reader.onChangeSidebarWidth = (width) => { reader.onChangeSidebarWidth = (width) => {

View file

@ -325,4 +325,6 @@
<!ENTITY zotero.pdfReader.zoomAuto "Automatically Resize"> <!ENTITY zotero.pdfReader.zoomAuto "Automatically Resize">
<!ENTITY zotero.pdfReader.zoomPageWidth "Zoom to Page Width"> <!ENTITY zotero.pdfReader.zoomPageWidth "Zoom to Page Width">
<!ENTITY zotero.pdfReader.zoomPageHeight "Zoom to Page Height"> <!ENTITY zotero.pdfReader.zoomPageHeight "Zoom to Page Height">
<!ENTITY zotero.pdfReader.splitVertically "Split Vertically">
<!ENTITY zotero.pdfReader.splitHorizontally "Split Horizontally">
<!ENTITY zotero.pdfReader.transferFromPDF "Import Annotations…"> <!ENTITY zotero.pdfReader.transferFromPDF "Import Annotations…">

View file

@ -1372,6 +1372,8 @@ pdfReader.zoomOut = Zoom Out
pdfReader.zoomAuto = Automatically Resize pdfReader.zoomAuto = Automatically Resize
pdfReader.zoomPageWidth = Zoom to Page Width pdfReader.zoomPageWidth = Zoom to Page Width
pdfReader.zoomPageHeight = Zoom to Page Height pdfReader.zoomPageHeight = Zoom to Page Height
pdfReader.splitVertically = Split Vertically
pdfReader.splitHorizontally = Split Horizontally
pdfReader.nextPage = Next Page pdfReader.nextPage = Next Page
pdfReader.previousPage = Previous Page pdfReader.previousPage = Previous Page
pdfReader.page = Page pdfReader.page = Page

@ -1 +1 @@
Subproject commit 40f5d26b7cc754fb72ba10eff979560721d4fe0e Subproject commit f8e560210017bc621056fcf654cf3dd7a657f55a