Skip to content

Commit aebecab

Browse files
committed
V:: v.0.8.2
API to change view mode programmatically. Reset single page view mode state to not show previous pages. Escape hatch API. Refactoring.
1 parent 594341c commit aebecab

File tree

10 files changed

+96
-51
lines changed

10 files changed

+96
-51
lines changed

extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": 2,
33
"name": "DjVu.js Viewer",
44
"short_name": "DV",
5-
"version": "0.8.1.0",
5+
"version": "0.8.2.0",
66
"author": "RussCoder",
77
"homepage_url": "https://github.com/RussCoder/djvujs",
88
"description": "Opens links to .djvu files. Allows opening files from a local disk. Processes <object> & <embed> tags.",

viewer/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# DjVu.js Viewer's Changelog
22

3+
## v.0.8.2 (23.09.2021)
4+
5+
- Support for big images (up to 20K * 20K pixels) in the single page view mode.
6+
- Dynamic letter spacing in the text layer to make text fully fill its zone.
7+
- API to change view mode programmatically.
8+
- Fixed: a previous page was shown for a short while when one switched from
9+
continuous scroll to single page view mode.
10+
- Getters, constants and action types are exposed as an escape hatch API for
11+
temporary solutions.
12+
313
## v.0.8.1 (11.09.2021)
414

515
- Fixed a bug from the previous release (the text layer couldn't be selected).

viewer/src/DjVuViewer.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ const Events = constant({
1717

1818
export default class DjVuViewer extends EventEmitter {
1919

20-
static VERSION = '0.8.1';
20+
static VERSION = '0.8.2';
2121

2222
static Events = Events;
23+
static Constants = Constants;
24+
static ActionTypes = ActionTypes;
25+
static get = get;
2326

2427
static getAvailableLanguages() {
2528
return Object.keys(dictionaries);
@@ -117,6 +120,7 @@ export default class DjVuViewer extends EventEmitter {
117120
* The config object is destructed merely for the purpose of documentation
118121
* @param {number} pageNumber
119122
* @param {0|90|180|270} pageRotation
123+
* @param {'continuous'|'single'|'text'} viewMode
120124
* @param {number} pageScale
121125
* @param {string} language
122126
* @param {'dark'|'light'} theme
@@ -130,10 +134,18 @@ export default class DjVuViewer extends EventEmitter {
130134
}} uiOptions
131135
* @returns {DjVuViewer}
132136
*/
133-
configure({ pageNumber, pageRotation, pageScale, language, theme, uiOptions } = {}) {
137+
configure({
138+
pageNumber,
139+
pageRotation,
140+
viewMode,
141+
pageScale,
142+
language,
143+
theme,
144+
uiOptions,
145+
} = {}) {
134146
this.store.dispatch({
135147
type: ActionTypes.CONFIGURE,
136-
pageNumber, pageRotation, pageScale, language, theme, uiOptions,
148+
pageNumber, pageRotation, viewMode, pageScale, language, theme, uiOptions,
137149
});
138150

139151
return this;

viewer/src/components/Toolbar/ViewModeButtons.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
44
import { FaRegFileAlt, FaRegFileImage } from "react-icons/all";
55

66
import { get } from '../../reducers';
7-
import Constants from '../../constants';
7+
import Constants, { ActionTypes } from '../../constants';
88
import { TranslationContext } from '../Translation';
99
import styled from 'styled-components';
1010
import { controlButton } from "../cssMixins";
@@ -44,15 +44,15 @@ class ViewModeButtons extends React.Component {
4444
static contextType = TranslationContext;
4545

4646
enableContinuousScrollMode = () => {
47-
this.props.dispatch({ type: Constants.ENABLE_CONTINUOUS_SCROLL_MODE_ACTION });
47+
this.props.dispatch({ type: ActionTypes.SET_VIEW_MODE, payload: Constants.CONTINUOUS_SCROLL_MODE });
4848
};
4949

5050
enableSinglePageMode = () => {
51-
this.props.dispatch({ type: Constants.ENABLE_SINGLE_PAGE_MODE_ACTION });
51+
this.props.dispatch({ type: ActionTypes.SET_VIEW_MODE, payload: Constants.SINGLE_PAGE_MODE });
5252
};
5353

5454
enableTextMode = () => {
55-
this.props.dispatch({ type: Constants.ENABLE_TEXT_MODE_ACTION });
55+
this.props.dispatch({ type: ActionTypes.SET_VIEW_MODE, payload: Constants.TEXT_MODE });
5656
};
5757

5858
render() {

viewer/src/constants/Constants.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ const Constants = {
44
TEXT_CURSOR_MODE: null,
55
GRAB_CURSOR_MODE: null,
66

7-
TEXT_MODE: null,
8-
CONTINUOUS_SCROLL_MODE: null,
9-
SINGLE_PAGE_MODE: null,
7+
TEXT_MODE: 'text',
8+
CONTINUOUS_SCROLL_MODE: 'continuous',
9+
SINGLE_PAGE_MODE: 'single',
1010

1111
SET_CURSOR_MODE_ACTION: null,
1212
ERROR_ACTION: null,
@@ -32,9 +32,6 @@ const Constants = {
3232
PAGES_SIZES_ARE_GOTTEN: null,
3333
DROP_PAGE_ACTION: null,
3434
DROP_ALL_PAGES_ACTION: null,
35-
ENABLE_CONTINUOUS_SCROLL_MODE_ACTION: null,
36-
ENABLE_SINGLE_PAGE_MODE_ACTION: null,
37-
ENABLE_TEXT_MODE_ACTION: null,
3835

3936
SET_API_CALLBACK_ACTION: null, // A special action for interaction with sagas. Used for program API of the viewer, look at the DjVuViewer.js
4037
};

viewer/src/constants/actionTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ export const ActionTypes = constant({
2828
PIN_TOOLBAR: null,
2929
UNPIN_TOOLBAR: null,
3030
UPDATE_APP_CONTEXT: null,
31+
SET_VIEW_MODE: null,
3132
});

viewer/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ if (process.env.NODE_ENV !== 'production') {
2424
window.DjVuViewerInstance.render(document.getElementById('root'));
2525

2626
window.DjVuViewerInstance.loadDocumentByUrl("/DjVu3Spec.djvu");
27-
//window.DjVuViewerInstance.loadDocumentByUrl("/DjVu3Spec_indirect/index.djvu");
27+
//window.DjVuViewerInstance.loadDocumentByUrl("/DjVu3Spec_indirect/index.djvu", { viewMode: 'continuous' });
2828

2929
//window.DjVuViewerInstance.loadDocumentByUrl("/czech_indirect/index.djvu", { pageRotation: 0, djvuOptions: {baseUrl: '/czech_indirect/'} });
3030
//window.DjVuViewerInstance.loadDocumentByUrl("/tmp/DjVu3Spec.djvu").then(() => window.DjVuViewerInstance.configure({pageRotation: 270}));

viewer/src/reducers/commonReducer.js

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ const initialState = Object.freeze({
88
userScale: 1,
99
pageRotation: 0,
1010
isLoading: false,
11-
isTextMode: false,
11+
viewMode: Constants.SINGLE_PAGE_MODE,
1212
pagesQuantity: null,
1313
isFullPageView: false,
1414
error: null,
1515
contents: null,
1616
isHelpWindowShown: false,
1717
isOptionsWindowOpened: false,
18-
isContinuousScrollMode: false,
1918
isIndirect: false,
2019
isContentsOpened: false,
2120
options: { // all these options are saved in localStorage
@@ -56,14 +55,12 @@ export default (state = initialState, action) => {
5655
case ActionTypes.UPDATE_OPTIONS:
5756
return { ...state, options: { ...state.options, ...payload } };
5857

59-
case Constants.ENABLE_CONTINUOUS_SCROLL_MODE_ACTION:
60-
return { ...state, isContinuousScrollMode: true, isTextMode: false };
61-
62-
case Constants.ENABLE_SINGLE_PAGE_MODE_ACTION:
63-
return { ...state, isContinuousScrollMode: false, isTextMode: false };
64-
65-
case Constants.ENABLE_TEXT_MODE_ACTION:
66-
return { ...state, isContinuousScrollMode: false, isTextMode: true, isLoading: false };
58+
case ActionTypes.SET_VIEW_MODE:
59+
return {
60+
...state,
61+
viewMode: payload,
62+
isLoading: payload === Constants.TEXT_MODE ? false : state.isLoading
63+
};
6764

6865
case Constants.SET_PAGE_ROTATION_ACTION:
6966
return {
@@ -96,7 +93,7 @@ export default (state = initialState, action) => {
9693
...getInitialStateWithOptions(state),
9794
documentId: state.documentId + 1,
9895
isLoading: true,
99-
isContinuousScrollMode: state.options.preferContinuousScroll ? true : state.isContinuousScrollMode,
96+
viewMode: state.options.preferContinuousScroll ? Constants.CONTINUOUS_SCROLL_MODE : initialState.viewMode,
10097
pagesQuantity: action.pagesQuantity,
10198
fileName: action.fileName,
10299
isIndirect: action.isIndirect,
@@ -168,12 +165,9 @@ export const get = {
168165
isLoading: state => state.isLoading,
169166
isDocumentLoaded: state => !!state.pagesQuantity,
170167
viewMode: state => {
171-
if (!state.isIndirect && state.isContinuousScrollMode) {
172-
return Constants.CONTINUOUS_SCROLL_MODE;
173-
}
174-
if (state.isTextMode) {
175-
return Constants.TEXT_MODE;
168+
if (state.isIndirect && state.viewMode === Constants.CONTINUOUS_SCROLL_MODE) {
169+
return Constants.SINGLE_PAGE_MODE;
176170
}
177-
return Constants.SINGLE_PAGE_MODE;
171+
return state.viewMode;
178172
}
179173
};

viewer/src/reducers/pageReducer.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ export default function pageReducer(state = initialState, action) {
8383
currentPageNumber: action.pageNumber
8484
};
8585

86+
case ActionTypes.SET_VIEW_MODE:
87+
if (payload === Constants.CONTINUOUS_SCROLL_MODE) {
88+
return { ...state, ...singlePageInitialState };
89+
}
90+
break;
91+
8692
case Constants.PAGE_TEXT_FETCHED_ACTION:
8793
return {
8894
...state,
@@ -95,10 +101,9 @@ export default function pageReducer(state = initialState, action) {
95101

96102
case ActionTypes.SET_TEXT_PAGE_ERROR:
97103
return { ...state, textPageError: payload };
98-
99-
default:
100-
return state;
101104
}
105+
106+
return state;
102107
}
103108

104109
/** @returns {function} */

viewer/src/sagas/rootSaga.js

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ class RootSaga {
6464
const state = yield select();
6565
const viewMode = get.viewMode(state);
6666

67-
if (viewMode === Constants.CONTINUOUS_SCROLL_MODE) {
67+
// when an outer config is provided, and the continuous scroll isn't default,
68+
// the page reset can start this saga before the manager has been crated.
69+
// Must be fixed at the architectural level, maybe the manager should be created lazily.
70+
if (viewMode === Constants.CONTINUOUS_SCROLL_MODE && this.continuousScrollManager) {
6871
yield* this.continuousScrollManager.startDataFetching();
6972
} else {
7073
const pageNumber = get.currentPageNumber(state);
@@ -116,7 +119,8 @@ class RootSaga {
116119
yield put(Actions.pagesSizesAreGottenAction(pagesSizes));
117120
}
118121

119-
* configure({ pageNumber, pageRotation, pageScale, language, theme, uiOptions }) {
122+
* configure({ pageNumber, pageRotation, viewMode, pageScale, language, theme, uiOptions }) {
123+
if (viewMode) yield put({ type: ActionTypes.SET_VIEW_MODE, payload: viewMode, notSave: true });
120124
if (pageNumber) yield put(Actions.setNewPageNumberAction(pageNumber, true));
121125
if (pageRotation) yield put(Actions.setPageRotationAction(pageRotation));
122126
if (pageScale) yield put(Actions.setUserScaleAction(pageScale));
@@ -173,27 +177,32 @@ class RootSaga {
173177
isIndirect: !isBundled,
174178
});
175179

180+
yield* this.loadContents();
181+
182+
const state = yield select();
183+
if (get.viewMode(state) === Constants.CONTINUOUS_SCROLL_MODE) {
184+
yield* this.prepareForContinuousMode();
185+
}
186+
176187
// perhaps it's better to configure the viewer before DOCUMENT_CREATED_ACTION
177188
// (since the promise of the viewer.loadDocument is resolved on this action)
178189
// But currently DOCUMENT_CREATED_ACTION reset the state of the viewer, so the configuration is done after it.
179190
// The optimal variant is to resolve the promise on another action, but I'm not sure is it needed to anybody at all.
191+
// Also, configuration can start some heavy sagas (e.g. viewMode or pageNumber change) which cancel all worker tasks.
192+
// Thus, it's done after all other tasks (including loading of the contents) are done.
180193
if (config) {
181194
yield* this.configure(config);
182195
}
183196

184-
const state = yield select();
185-
this.continuousScrollManager = null; // we don't have to reset it, since the worker was recreated and all memory was release in any case
186-
if (get.viewMode(state) === Constants.CONTINUOUS_SCROLL_MODE) {
187-
yield* this.prepareForContinuousMode();
188-
}
197+
yield* this.resetCurrentPageNumber();
198+
}
189199

200+
* loadContents() {
190201
const contents = yield this.djvuWorker.doc.getContents().run();
191202
yield put({
192203
type: Constants.CONTENTS_IS_GOTTEN_ACTION,
193204
contents: contents
194205
});
195-
196-
yield* this.resetCurrentPageNumber();
197206
}
198207

199208
* resetCurrentPageNumber() {
@@ -269,6 +278,8 @@ class RootSaga {
269278
this.pageStorage.reset();
270279
this.pagesCache.resetPagesCache();
271280
this.djvuWorker.reset();
281+
// we don't have to reset it, since the worker was recreated and all memory was released in any case
282+
this.continuousScrollManager = null;
272283
this.isBundling = false;
273284
this.documentContructorData = null;
274285
}
@@ -277,23 +288,23 @@ class RootSaga {
277288
this.callbacks[action.callbackName] = action.callback;
278289
}
279290

280-
* switchToContinuousScrollMode() {
291+
* switchToContinuousScrollMode(notSave = false) {
281292
this.djvuWorker.cancelAllTasks();
282293
if (!this.continuousScrollManager) {
283294
yield* this.prepareForContinuousMode();
284295
}
285296
this.pagesCache.resetPagesCache();
286297
yield* this.resetCurrentPageNumber();
287-
yield put({ type: ActionTypes.UPDATE_OPTIONS, payload: { preferContinuousScroll: true } });
298+
if (!notSave) yield put({ type: ActionTypes.UPDATE_OPTIONS, payload: { preferContinuousScroll: true } });
288299
}
289300

290-
* switchToSinglePageMode() {
301+
* switchToSinglePageMode(notSave = false) {
291302
this.djvuWorker.cancelAllTasks();
292303
if (this.continuousScrollManager) {
293304
yield* this.continuousScrollManager.reset();
294305
}
295306
yield* this.resetCurrentPageNumber();
296-
yield put({ type: ActionTypes.UPDATE_OPTIONS, payload: { preferContinuousScroll: false } });
307+
if (!notSave) yield put({ type: ActionTypes.UPDATE_OPTIONS, payload: { preferContinuousScroll: false } });
297308
}
298309

299310
* switchToTextMode() {
@@ -304,6 +315,23 @@ class RootSaga {
304315
yield* this.fetchPageTextIfRequired();
305316
}
306317

318+
* handleViewModeSwitch({ notSave = false } = {}) {
319+
const isLoaded = yield select(get.isDocumentLoaded);
320+
const viewMode = yield select(get.viewMode);
321+
if (!isLoaded) return;
322+
323+
switch (viewMode) {
324+
case Constants.CONTINUOUS_SCROLL_MODE:
325+
return yield* this.switchToContinuousScrollMode(notSave);
326+
case Constants.SINGLE_PAGE_MODE:
327+
return yield* this.switchToSinglePageMode(notSave);
328+
case Constants.TEXT_MODE:
329+
return yield* this.switchToTextMode();
330+
default:
331+
throw new Error('Invalid view mode: ' + payload);
332+
}
333+
}
334+
307335
* updateOptions(action) {
308336
if (action.notSave) return;
309337

@@ -464,9 +492,7 @@ class RootSaga {
464492
yield takeLatest(ActionTypes.SAVE_DOCUMENT, this.withErrorHandler(this.saveDocument));
465493
yield takeLatest(Constants.CLOSE_DOCUMENT_ACTION, this.withErrorHandler(this.resetWorkerAndStorages));
466494
yield takeLatest(Constants.SET_API_CALLBACK_ACTION, this.withErrorHandler(this.setCallback));
467-
yield takeLatest(Constants.ENABLE_CONTINUOUS_SCROLL_MODE_ACTION, this.withErrorHandler(this.switchToContinuousScrollMode));
468-
yield takeLatest(Constants.ENABLE_SINGLE_PAGE_MODE_ACTION, this.withErrorHandler(this.switchToSinglePageMode));
469-
yield takeLatest(Constants.ENABLE_TEXT_MODE_ACTION, this.withErrorHandler(this.switchToTextMode));
495+
yield takeLatest(ActionTypes.SET_VIEW_MODE, this.withErrorHandler(this.handleViewModeSwitch));
470496
yield takeLatest(ActionTypes.UPDATE_OPTIONS, this.withErrorHandler(this.updateOptions));
471497
yield takeLatest(ActionTypes.CONFIGURE, this.withErrorHandler(this.configure));
472498
yield takeLatest(ActionTypes.LOAD_DOCUMENT_BY_URL, this.loadDocumentByUrl.bind(this));

0 commit comments

Comments
 (0)