Merge pull request #9026 from Snuffleupagus/sidebar-resize
Implement sidebar resizing for modern browsers, by utilizing CSS variables (issue 2072)
This commit is contained in:
commit
04b93cf57e
@ -38,6 +38,7 @@ import { PDFHistory } from './pdf_history';
|
|||||||
import { PDFLinkService } from './pdf_link_service';
|
import { PDFLinkService } from './pdf_link_service';
|
||||||
import { PDFOutlineViewer } from './pdf_outline_viewer';
|
import { PDFOutlineViewer } from './pdf_outline_viewer';
|
||||||
import { PDFPresentationMode } from './pdf_presentation_mode';
|
import { PDFPresentationMode } from './pdf_presentation_mode';
|
||||||
|
import { PDFSidebarResizer } from './pdf_sidebar_resizer';
|
||||||
import { PDFThumbnailViewer } from './pdf_thumbnail_viewer';
|
import { PDFThumbnailViewer } from './pdf_thumbnail_viewer';
|
||||||
import { PDFViewer } from './pdf_viewer';
|
import { PDFViewer } from './pdf_viewer';
|
||||||
import { SecondaryToolbar } from './secondary_toolbar';
|
import { SecondaryToolbar } from './secondary_toolbar';
|
||||||
@ -109,6 +110,8 @@ let PDFViewerApplication = {
|
|||||||
pdfHistory: null,
|
pdfHistory: null,
|
||||||
/** @type {PDFSidebar} */
|
/** @type {PDFSidebar} */
|
||||||
pdfSidebar: null,
|
pdfSidebar: null,
|
||||||
|
/** @type {PDFSidebarResizer} */
|
||||||
|
pdfSidebarResizer: null,
|
||||||
/** @type {PDFOutlineViewer} */
|
/** @type {PDFOutlineViewer} */
|
||||||
pdfOutlineViewer: null,
|
pdfOutlineViewer: null,
|
||||||
/** @type {PDFAttachmentViewer} */
|
/** @type {PDFAttachmentViewer} */
|
||||||
@ -419,6 +422,8 @@ let PDFViewerApplication = {
|
|||||||
this.pdfSidebar = new PDFSidebar(sidebarConfig, this.l10n);
|
this.pdfSidebar = new PDFSidebar(sidebarConfig, this.l10n);
|
||||||
this.pdfSidebar.onToggled = this.forceRendering.bind(this);
|
this.pdfSidebar.onToggled = this.forceRendering.bind(this);
|
||||||
|
|
||||||
|
this.pdfSidebarResizer = new PDFSidebarResizer(appConfig.sidebarResizer,
|
||||||
|
eventBus, this.l10n);
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -1305,7 +1310,7 @@ let PDFViewerApplication = {
|
|||||||
let { eventBus, _boundEvents, } = this;
|
let { eventBus, _boundEvents, } = this;
|
||||||
|
|
||||||
_boundEvents.windowResize = () => {
|
_boundEvents.windowResize = () => {
|
||||||
eventBus.dispatch('resize');
|
eventBus.dispatch('resize', { source: window, });
|
||||||
};
|
};
|
||||||
_boundEvents.windowHashChange = () => {
|
_boundEvents.windowHashChange = () => {
|
||||||
eventBus.dispatch('hashchange', {
|
eventBus.dispatch('hashchange', {
|
||||||
@ -1578,7 +1583,7 @@ function webViewerInitialized() {
|
|||||||
|
|
||||||
appConfig.mainContainer.addEventListener('transitionend', function(evt) {
|
appConfig.mainContainer.addEventListener('transitionend', function(evt) {
|
||||||
if (evt.target === /* mainContainer */ this) {
|
if (evt.target === /* mainContainer */ this) {
|
||||||
PDFViewerApplication.eventBus.dispatch('resize');
|
PDFViewerApplication.eventBus.dispatch('resize', { source: this, });
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
184
web/pdf_sidebar_resizer.js
Normal file
184
web/pdf_sidebar_resizer.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/* Copyright 2017 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NullL10n } from './ui_utils';
|
||||||
|
|
||||||
|
const SIDEBAR_WIDTH_VAR = '--sidebar-width';
|
||||||
|
const SIDEBAR_MIN_WIDTH = 200; // pixels
|
||||||
|
const SIDEBAR_RESIZING_CLASS = 'sidebarResizing';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PDFSidebarResizerOptions
|
||||||
|
* @property {HTMLDivElement} outerContainer - The outer container
|
||||||
|
* (encasing both the viewer and sidebar elements).
|
||||||
|
* @property {HTMLDivElement} resizer - The DOM element that can be dragged in
|
||||||
|
* order to adjust the width of the sidebar.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PDFSidebarResizer {
|
||||||
|
/**
|
||||||
|
* @param {PDFSidebarResizerOptions} options
|
||||||
|
* @param {EventBus} eventBus - The application event bus.
|
||||||
|
* @param {IL10n} l10n - Localization service.
|
||||||
|
*/
|
||||||
|
constructor(options, eventBus, l10n = NullL10n) {
|
||||||
|
this.enabled = false;
|
||||||
|
this.isRTL = false;
|
||||||
|
this.sidebarOpen = false;
|
||||||
|
this.doc = document.documentElement;
|
||||||
|
this._width = null;
|
||||||
|
this._outerContainerWidth = null;
|
||||||
|
this._boundEvents = Object.create(null);
|
||||||
|
|
||||||
|
this.outerContainer = options.outerContainer;
|
||||||
|
this.resizer = options.resizer;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.l10n = l10n;
|
||||||
|
|
||||||
|
if (typeof CSS === 'undefined' || typeof CSS.supports !== 'function' ||
|
||||||
|
!CSS.supports(SIDEBAR_WIDTH_VAR, `calc(-1 * ${SIDEBAR_MIN_WIDTH}px)`)) {
|
||||||
|
console.warn('PDFSidebarResizer: ' +
|
||||||
|
'The browser does not support resizing of the sidebar.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.enabled = true;
|
||||||
|
this.resizer.classList.remove('hidden'); // Show the resizer DOM element.
|
||||||
|
|
||||||
|
this.l10n.getDirection().then((dir) => {
|
||||||
|
this.isRTL = (dir === 'rtl');
|
||||||
|
});
|
||||||
|
this._addEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns {number}
|
||||||
|
*/
|
||||||
|
get outerContainerWidth() {
|
||||||
|
if (!this._outerContainerWidth) {
|
||||||
|
this._outerContainerWidth = this.outerContainer.clientWidth;
|
||||||
|
}
|
||||||
|
return this._outerContainerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* returns {boolean} Indicating if the sidebar width was updated.
|
||||||
|
*/
|
||||||
|
_updateWidth(width = 0) {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Prevent the sidebar from becoming too narrow, or from occupying more
|
||||||
|
// than half of the available viewer width.
|
||||||
|
const maxWidth = Math.floor(this.outerContainerWidth / 2);
|
||||||
|
if (width > maxWidth) {
|
||||||
|
width = maxWidth;
|
||||||
|
}
|
||||||
|
if (width < SIDEBAR_MIN_WIDTH) {
|
||||||
|
width = SIDEBAR_MIN_WIDTH;
|
||||||
|
}
|
||||||
|
// Only update the UI when the sidebar width did in fact change.
|
||||||
|
if (width === this._width) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this._width = width;
|
||||||
|
this.doc.style.setProperty(SIDEBAR_WIDTH_VAR, `${width}px`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_mouseMove(evt) {
|
||||||
|
let width = evt.clientX;
|
||||||
|
// For sidebar resizing to work correctly in RTL mode, invert the width.
|
||||||
|
if (this.isRTL) {
|
||||||
|
width = this.outerContainerWidth - width;
|
||||||
|
}
|
||||||
|
this._updateWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_mouseUp(evt) {
|
||||||
|
// Re-enable the `transition-duration` rules when sidebar resizing ends...
|
||||||
|
this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
|
||||||
|
// ... and ensure that rendering will always be triggered.
|
||||||
|
this.eventBus.dispatch('resize', { source: this, });
|
||||||
|
|
||||||
|
let _boundEvents = this._boundEvents;
|
||||||
|
window.removeEventListener('mousemove', _boundEvents.mouseMove);
|
||||||
|
window.removeEventListener('mouseup', _boundEvents.mouseUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_addEventListeners() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _boundEvents = this._boundEvents;
|
||||||
|
_boundEvents.mouseMove = this._mouseMove.bind(this);
|
||||||
|
_boundEvents.mouseUp = this._mouseUp.bind(this);
|
||||||
|
|
||||||
|
this.resizer.addEventListener('mousedown', (evt) => {
|
||||||
|
// Disable the `transition-duration` rules when sidebar resizing begins,
|
||||||
|
// in order to improve responsiveness and to avoid visual glitches.
|
||||||
|
this.outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', _boundEvents.mouseMove);
|
||||||
|
window.addEventListener('mouseup', _boundEvents.mouseUp);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventBus.on('sidebarviewchanged', (evt) => {
|
||||||
|
this.sidebarOpen = !!(evt && evt.view);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventBus.on('resize', (evt) => {
|
||||||
|
// When the *entire* viewer is resized, such that it becomes narrower,
|
||||||
|
// ensure that the sidebar doesn't end up being too wide.
|
||||||
|
if (evt && evt.source === window) {
|
||||||
|
// Always reset the cached width when the viewer is resized.
|
||||||
|
this._outerContainerWidth = null;
|
||||||
|
|
||||||
|
if (this._width) {
|
||||||
|
// NOTE: If the sidebar is closed, we don't need to worry about
|
||||||
|
// visual glitches nor ensure that rendering is triggered.
|
||||||
|
if (this.sidebarOpen) {
|
||||||
|
this.outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
|
||||||
|
let updated = this._updateWidth(this._width);
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
|
||||||
|
// Trigger rendering if the sidebar width changed, to avoid
|
||||||
|
// depending on the order in which 'resize' events are handled.
|
||||||
|
if (updated) {
|
||||||
|
this.eventBus.dispatch('resize', { source: this, });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._updateWidth(this._width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
PDFSidebarResizer,
|
||||||
|
};
|
@ -52,6 +52,10 @@ function formatL10nValue(text, args) {
|
|||||||
* @implements {IL10n}
|
* @implements {IL10n}
|
||||||
*/
|
*/
|
||||||
let NullL10n = {
|
let NullL10n = {
|
||||||
|
getDirection() {
|
||||||
|
return Promise.resolve('ltr');
|
||||||
|
},
|
||||||
|
|
||||||
get(property, args, fallback) {
|
get(property, args, fallback) {
|
||||||
return Promise.resolve(formatL10nValue(fallback, args));
|
return Promise.resolve(formatL10nValue(fallback, args));
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
|
|
||||||
@import url(pdf_viewer.css);
|
@import url(pdf_viewer.css);
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--sidebar-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -145,7 +149,9 @@ select {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 32px;
|
top: 32px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 200px;
|
width: 200px; /* Here, and elsewhere below, keep the constant value for compatibility
|
||||||
|
with older browsers that lack support for CSS variables. */
|
||||||
|
width: var(--sidebar-width);
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
border-top: 1px solid #333;
|
border-top: 1px solid #333;
|
||||||
@ -159,17 +165,28 @@ html[dir='ltr'] #sidebarContainer {
|
|||||||
-webkit-transition-property: left;
|
-webkit-transition-property: left;
|
||||||
transition-property: left;
|
transition-property: left;
|
||||||
left: -200px;
|
left: -200px;
|
||||||
|
left: calc(-1 * var(--sidebar-width));
|
||||||
}
|
}
|
||||||
html[dir='rtl'] #sidebarContainer {
|
html[dir='rtl'] #sidebarContainer {
|
||||||
-webkit-transition-property: right;
|
-webkit-transition-property: right;
|
||||||
transition-property: right;
|
transition-property: right;
|
||||||
right: -200px;
|
right: -200px;
|
||||||
|
right: calc(-1 * var(--sidebar-width));
|
||||||
}
|
}
|
||||||
|
|
||||||
.loadingInProgress #sidebarContainer {
|
.loadingInProgress #sidebarContainer {
|
||||||
top: 36px;
|
top: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#outerContainer.sidebarResizing #sidebarContainer {
|
||||||
|
/* Improve responsiveness and avoid visual glitches when the sidebar is resized. */
|
||||||
|
-webkit-transition-duration: 0s;
|
||||||
|
transition-duration: 0s;
|
||||||
|
/* Prevent e.g. the thumbnails being selected when the sidebar is resized. */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
#outerContainer.sidebarMoving #sidebarContainer,
|
#outerContainer.sidebarMoving #sidebarContainer,
|
||||||
#outerContainer.sidebarOpen #sidebarContainer {
|
#outerContainer.sidebarOpen #sidebarContainer {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
@ -230,15 +247,23 @@ html[dir='rtl'] #viewerContainer {
|
|||||||
box-shadow: inset -1px 0 0 hsla(0,0%,100%,.05);
|
box-shadow: inset -1px 0 0 hsla(0,0%,100%,.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#outerContainer.sidebarResizing #viewerContainer {
|
||||||
|
/* Improve responsiveness and avoid visual glitches when the sidebar is resized. */
|
||||||
|
-webkit-transition-duration: 0s;
|
||||||
|
transition-duration: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
html[dir='ltr'] #outerContainer.sidebarOpen #viewerContainer {
|
html[dir='ltr'] #outerContainer.sidebarOpen #viewerContainer {
|
||||||
-webkit-transition-property: left;
|
-webkit-transition-property: left;
|
||||||
transition-property: left;
|
transition-property: left;
|
||||||
left: 200px;
|
left: 200px;
|
||||||
|
left: var(--sidebar-width);
|
||||||
}
|
}
|
||||||
html[dir='rtl'] #outerContainer.sidebarOpen #viewerContainer {
|
html[dir='rtl'] #outerContainer.sidebarOpen #viewerContainer {
|
||||||
-webkit-transition-property: right;
|
-webkit-transition-property: right;
|
||||||
transition-property: right;
|
transition-property: right;
|
||||||
right: 200px;
|
right: 200px;
|
||||||
|
right: var(--sidebar-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
@ -273,6 +298,21 @@ html[dir='rtl'] #toolbarSidebar {
|
|||||||
0 0 1px hsla(0,0%,0%,.1);
|
0 0 1px hsla(0,0%,0%,.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sidebarResizer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 6px;
|
||||||
|
z-index: 200;
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
html[dir='ltr'] #sidebarResizer {
|
||||||
|
right: -6px;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] #sidebarResizer {
|
||||||
|
left: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
#toolbarContainer, .findbar, .secondaryToolbar {
|
#toolbarContainer, .findbar, .secondaryToolbar {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@ -1133,9 +1173,14 @@ html[dir='rtl'] .verticalToolbarSeparator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail {
|
.thumbnail {
|
||||||
float: left;
|
|
||||||
margin: 0 10px 5px 10px;
|
margin: 0 10px 5px 10px;
|
||||||
}
|
}
|
||||||
|
html[dir='ltr'] .thumbnail {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] .thumbnail {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
#thumbnailView > a:last-of-type > .thumbnail {
|
#thumbnailView > a:last-of-type > .thumbnail {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -86,6 +86,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
<div id="attachmentsView" class="hidden">
|
<div id="attachmentsView" class="hidden">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="sidebarResizer" class="hidden"></div>
|
||||||
</div> <!-- sidebarContainer -->
|
</div> <!-- sidebarContainer -->
|
||||||
|
|
||||||
<div id="mainContainer">
|
<div id="mainContainer">
|
||||||
|
@ -117,6 +117,10 @@ function getViewerConfiguration() {
|
|||||||
outlineView: document.getElementById('outlineView'),
|
outlineView: document.getElementById('outlineView'),
|
||||||
attachmentsView: document.getElementById('attachmentsView'),
|
attachmentsView: document.getElementById('attachmentsView'),
|
||||||
},
|
},
|
||||||
|
sidebarResizer: {
|
||||||
|
outerContainer: document.getElementById('outerContainer'),
|
||||||
|
resizer: document.getElementById('sidebarResizer'),
|
||||||
|
},
|
||||||
findBar: {
|
findBar: {
|
||||||
bar: document.getElementById('findbar'),
|
bar: document.getElementById('findbar'),
|
||||||
toggleButton: document.getElementById('viewFind'),
|
toggleButton: document.getElementById('viewFind'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user