mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
Chore: Moved desktop app reducer to separate file so that it can be unit tested
This commit is contained in:
parent
1bc674a1f9
commit
e6bff3f2e0
@ -117,6 +117,12 @@ packages/app-desktop/InteropServiceHelper.js.map
|
|||||||
packages/app-desktop/app.d.ts
|
packages/app-desktop/app.d.ts
|
||||||
packages/app-desktop/app.js
|
packages/app-desktop/app.js
|
||||||
packages/app-desktop/app.js.map
|
packages/app-desktop/app.js.map
|
||||||
|
packages/app-desktop/app.reducer.d.ts
|
||||||
|
packages/app-desktop/app.reducer.js
|
||||||
|
packages/app-desktop/app.reducer.js.map
|
||||||
|
packages/app-desktop/app.reducer.test.d.ts
|
||||||
|
packages/app-desktop/app.reducer.test.js
|
||||||
|
packages/app-desktop/app.reducer.test.js.map
|
||||||
packages/app-desktop/bridge.d.ts
|
packages/app-desktop/bridge.d.ts
|
||||||
packages/app-desktop/bridge.js
|
packages/app-desktop/bridge.js
|
||||||
packages/app-desktop/bridge.js.map
|
packages/app-desktop/bridge.js.map
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -103,6 +103,12 @@ packages/app-desktop/InteropServiceHelper.js.map
|
|||||||
packages/app-desktop/app.d.ts
|
packages/app-desktop/app.d.ts
|
||||||
packages/app-desktop/app.js
|
packages/app-desktop/app.js
|
||||||
packages/app-desktop/app.js.map
|
packages/app-desktop/app.js.map
|
||||||
|
packages/app-desktop/app.reducer.d.ts
|
||||||
|
packages/app-desktop/app.reducer.js
|
||||||
|
packages/app-desktop/app.reducer.js.map
|
||||||
|
packages/app-desktop/app.reducer.test.d.ts
|
||||||
|
packages/app-desktop/app.reducer.test.js
|
||||||
|
packages/app-desktop/app.reducer.test.js.map
|
||||||
packages/app-desktop/bridge.d.ts
|
packages/app-desktop/bridge.d.ts
|
||||||
packages/app-desktop/bridge.js
|
packages/app-desktop/bridge.js
|
||||||
packages/app-desktop/bridge.js.map
|
packages/app-desktop/bridge.js.map
|
||||||
|
47
packages/app-desktop/app.reducer.test.ts
Normal file
47
packages/app-desktop/app.reducer.test.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { AppState } from './app';
|
||||||
|
import appReducer, { createAppDefaultState } from './app.reducer';
|
||||||
|
|
||||||
|
describe('app.reducer', function() {
|
||||||
|
|
||||||
|
it('DIALOG_OPEN', async () => {
|
||||||
|
const state: AppState = createAppDefaultState({}, {});
|
||||||
|
|
||||||
|
let newState = appReducer(state, {
|
||||||
|
type: 'DIALOG_OPEN',
|
||||||
|
name: 'syncWizard',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(newState.dialogs.length).toBe(1);
|
||||||
|
expect(newState.dialogs[0].name).toBe('syncWizard');
|
||||||
|
|
||||||
|
expect(() => appReducer(newState, {
|
||||||
|
type: 'DIALOG_OPEN',
|
||||||
|
name: 'syncWizard',
|
||||||
|
})).toThrow();
|
||||||
|
|
||||||
|
newState = appReducer(newState, {
|
||||||
|
type: 'DIALOG_CLOSE',
|
||||||
|
name: 'syncWizard',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(newState.dialogs.length).toBe(0);
|
||||||
|
|
||||||
|
expect(() => appReducer(newState, {
|
||||||
|
type: 'DIALOG_CLOSE',
|
||||||
|
name: 'syncWizard',
|
||||||
|
})).toThrow();
|
||||||
|
|
||||||
|
newState = appReducer(newState, {
|
||||||
|
type: 'DIALOG_OPEN',
|
||||||
|
name: 'syncWizard',
|
||||||
|
});
|
||||||
|
|
||||||
|
newState = appReducer(newState, {
|
||||||
|
type: 'DIALOG_OPEN',
|
||||||
|
name: 'setPassword',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(newState.dialogs).toEqual([{ name: 'syncWizard' }, { name: 'setPassword' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
283
packages/app-desktop/app.reducer.ts
Normal file
283
packages/app-desktop/app.reducer.ts
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
import produce from 'immer';
|
||||||
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
import { defaultState } from '@joplin/lib/reducer';
|
||||||
|
import { AppState } from './app';
|
||||||
|
import iterateItems from './gui/ResizableLayout/utils/iterateItems';
|
||||||
|
import { LayoutItem } from './gui/ResizableLayout/utils/types';
|
||||||
|
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
|
||||||
|
|
||||||
|
export function createAppDefaultState(windowContentSize: any, resourceEditWatcherDefaultState: any): AppState {
|
||||||
|
return {
|
||||||
|
...defaultState,
|
||||||
|
route: {
|
||||||
|
type: 'NAV_GO',
|
||||||
|
routeName: 'Main',
|
||||||
|
props: {},
|
||||||
|
},
|
||||||
|
navHistory: [],
|
||||||
|
noteVisiblePanes: ['editor', 'viewer'],
|
||||||
|
windowContentSize, // bridge().windowContentSize(),
|
||||||
|
watchedNoteFiles: [],
|
||||||
|
lastEditorScrollPercents: {},
|
||||||
|
devToolsVisible: false,
|
||||||
|
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
||||||
|
focusedField: null,
|
||||||
|
layoutMoveMode: false,
|
||||||
|
mainLayout: null,
|
||||||
|
startupPluginsLoaded: false,
|
||||||
|
dialogs: [],
|
||||||
|
...resourceEditWatcherDefaultState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(state: AppState, action: any) {
|
||||||
|
let newState = state;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case 'NAV_BACK':
|
||||||
|
case 'NAV_GO':
|
||||||
|
|
||||||
|
{
|
||||||
|
const goingBack = action.type === 'NAV_BACK';
|
||||||
|
|
||||||
|
if (goingBack && !state.navHistory.length) break;
|
||||||
|
|
||||||
|
const currentRoute = state.route;
|
||||||
|
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
const newNavHistory = state.navHistory.slice();
|
||||||
|
|
||||||
|
if (goingBack) {
|
||||||
|
let newAction = null;
|
||||||
|
while (newNavHistory.length) {
|
||||||
|
newAction = newNavHistory.pop();
|
||||||
|
if (newAction.routeName !== state.route.routeName) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newAction) break;
|
||||||
|
|
||||||
|
action = newAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!goingBack) newNavHistory.push(currentRoute);
|
||||||
|
newState.navHistory = newNavHistory;
|
||||||
|
newState.route = action;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'STARTUP_PLUGINS_LOADED':
|
||||||
|
|
||||||
|
// When all startup plugins have loaded, we also recreate the
|
||||||
|
// main layout to ensure that it is updated in the UI. There's
|
||||||
|
// probably a cleaner way to do this, but for now that will do.
|
||||||
|
if (state.startupPluginsLoaded !== action.value) {
|
||||||
|
newState = {
|
||||||
|
...newState,
|
||||||
|
startupPluginsLoaded: action.value,
|
||||||
|
mainLayout: JSON.parse(JSON.stringify(newState.mainLayout)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'WINDOW_CONTENT_SIZE_SET':
|
||||||
|
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.windowContentSize = action.size;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NOTE_VISIBLE_PANES_TOGGLE':
|
||||||
|
|
||||||
|
{
|
||||||
|
const getNextLayout = (currentLayout: any) => {
|
||||||
|
currentLayout = panes.length === 2 ? 'both' : currentLayout[0];
|
||||||
|
|
||||||
|
let paneOptions;
|
||||||
|
if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_VIEWER) {
|
||||||
|
paneOptions = ['editor', 'viewer'];
|
||||||
|
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_SPLIT) {
|
||||||
|
paneOptions = ['editor', 'both'];
|
||||||
|
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_VIEWER_SPLIT) {
|
||||||
|
paneOptions = ['viewer', 'both'];
|
||||||
|
} else {
|
||||||
|
paneOptions = ['editor', 'viewer', 'both'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLayoutIndex = paneOptions.indexOf(currentLayout);
|
||||||
|
const nextLayoutIndex = currentLayoutIndex === paneOptions.length - 1 ? 0 : currentLayoutIndex + 1;
|
||||||
|
|
||||||
|
const nextLayout = paneOptions[nextLayoutIndex];
|
||||||
|
return nextLayout === 'both' ? ['editor', 'viewer'] : [nextLayout];
|
||||||
|
};
|
||||||
|
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
|
||||||
|
const panes = state.noteVisiblePanes.slice();
|
||||||
|
newState.noteVisiblePanes = getNextLayout(panes);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NOTE_VISIBLE_PANES_SET':
|
||||||
|
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.noteVisiblePanes = action.panes;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'MAIN_LAYOUT_SET':
|
||||||
|
|
||||||
|
newState = {
|
||||||
|
...state,
|
||||||
|
mainLayout: action.value,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'MAIN_LAYOUT_SET_ITEM_PROP':
|
||||||
|
|
||||||
|
{
|
||||||
|
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
|
||||||
|
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
|
||||||
|
if (item.key === action.itemKey) {
|
||||||
|
(item as any)[action.propName] = action.propValue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
|
||||||
|
|
||||||
|
newState = {
|
||||||
|
...state,
|
||||||
|
mainLayout: newLayout,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NOTE_FILE_WATCHER_ADD':
|
||||||
|
|
||||||
|
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
const watchedNoteFiles = newState.watchedNoteFiles.slice();
|
||||||
|
watchedNoteFiles.push(action.id);
|
||||||
|
newState.watchedNoteFiles = watchedNoteFiles;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NOTE_FILE_WATCHER_REMOVE':
|
||||||
|
|
||||||
|
{
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
const idx = newState.watchedNoteFiles.indexOf(action.id);
|
||||||
|
if (idx >= 0) {
|
||||||
|
const watchedNoteFiles = newState.watchedNoteFiles.slice();
|
||||||
|
watchedNoteFiles.splice(idx, 1);
|
||||||
|
newState.watchedNoteFiles = watchedNoteFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NOTE_FILE_WATCHER_CLEAR':
|
||||||
|
|
||||||
|
if (state.watchedNoteFiles.length) {
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.watchedNoteFiles = [];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'EDITOR_SCROLL_PERCENT_SET':
|
||||||
|
|
||||||
|
{
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
const newPercents = Object.assign({}, newState.lastEditorScrollPercents);
|
||||||
|
newPercents[action.noteId] = action.percent;
|
||||||
|
newState.lastEditorScrollPercents = newPercents;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NOTE_DEVTOOLS_TOGGLE':
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.devToolsVisible = !newState.devToolsVisible;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NOTE_DEVTOOLS_SET':
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.devToolsVisible = action.value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'VISIBLE_DIALOGS_ADD':
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
|
||||||
|
newState.visibleDialogs[action.name] = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'VISIBLE_DIALOGS_REMOVE':
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
|
||||||
|
delete newState.visibleDialogs[action.name];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'FOCUS_SET':
|
||||||
|
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.focusedField = action.field;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'FOCUS_CLEAR':
|
||||||
|
|
||||||
|
// A field can only clear its own state
|
||||||
|
if (action.field === state.focusedField) {
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.focusedField = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'DIALOG_OPEN':
|
||||||
|
case 'DIALOG_CLOSE':
|
||||||
|
|
||||||
|
{
|
||||||
|
let isOpen = true;
|
||||||
|
|
||||||
|
if (action.type === 'DIALOG_CLOSE') {
|
||||||
|
isOpen = false;
|
||||||
|
} else { // DIALOG_OPEN
|
||||||
|
isOpen = action.isOpen !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
const newDialogs = newState.dialogs.slice();
|
||||||
|
|
||||||
|
if (newDialogs.find(d => d.name === action.name)) throw new Error(`Trying to open a dialog is already open: ${action.name}`);
|
||||||
|
|
||||||
|
newDialogs.push({
|
||||||
|
name: action.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
newState.dialogs = newDialogs;
|
||||||
|
} else {
|
||||||
|
if (!newState.dialogs.find(d => d.name === action.name)) throw new Error(`Trying to close a dialog that is not open: ${action.name}`);
|
||||||
|
const newDialogs = newState.dialogs.slice().filter(d => d.name !== action.name);
|
||||||
|
newState.dialogs = newDialogs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'LAYOUT_MOVE_MODE_SET':
|
||||||
|
|
||||||
|
newState = {
|
||||||
|
...state,
|
||||||
|
layoutMoveMode: action.value,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newState;
|
||||||
|
}
|
@ -3,7 +3,7 @@ import CommandService from '@joplin/lib/services/CommandService';
|
|||||||
import KeymapService from '@joplin/lib/services/KeymapService';
|
import KeymapService from '@joplin/lib/services/KeymapService';
|
||||||
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||||
import resourceEditWatcherReducer, { defaultState as resourceEditWatcherDefaultState } from '@joplin/lib/services/ResourceEditWatcher/reducer';
|
import resourceEditWatcherReducer, { defaultState as resourceEditWatcherDefaultState } from '@joplin/lib/services/ResourceEditWatcher/reducer';
|
||||||
import { defaultState, State } from '@joplin/lib/reducer';
|
import { State } from '@joplin/lib/reducer';
|
||||||
import PluginRunner from './services/plugins/PluginRunner';
|
import PluginRunner from './services/plugins/PluginRunner';
|
||||||
import PlatformImplementation from './services/plugins/PlatformImplementation';
|
import PlatformImplementation from './services/plugins/PlatformImplementation';
|
||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
@ -23,9 +23,7 @@ import { LayoutItem } from './gui/ResizableLayout/utils/types';
|
|||||||
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
|
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
|
||||||
import ResourceService from '@joplin/lib/services/ResourceService';
|
import ResourceService from '@joplin/lib/services/ResourceService';
|
||||||
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
|
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
|
||||||
import produce from 'immer';
|
import appReducer, { createAppDefaultState } from './app.reducer';
|
||||||
import iterateItems from './gui/ResizableLayout/utils/iterateItems';
|
|
||||||
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
|
|
||||||
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
||||||
import Folder from '@joplin/lib/models/Folder';
|
import Folder from '@joplin/lib/models/Folder';
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
@ -138,27 +136,10 @@ export interface AppState extends State {
|
|||||||
dialogs: AppStateDialog[];
|
dialogs: AppStateDialog[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const appDefaultState: AppState = {
|
const appDefaultState = createAppDefaultState(
|
||||||
...defaultState,
|
bridge().windowContentSize(),
|
||||||
route: {
|
resourceEditWatcherDefaultState
|
||||||
type: 'NAV_GO',
|
);
|
||||||
routeName: 'Main',
|
|
||||||
props: {},
|
|
||||||
},
|
|
||||||
navHistory: [],
|
|
||||||
noteVisiblePanes: ['editor', 'viewer'],
|
|
||||||
windowContentSize: bridge().windowContentSize(),
|
|
||||||
watchedNoteFiles: [],
|
|
||||||
lastEditorScrollPercents: {},
|
|
||||||
devToolsVisible: false,
|
|
||||||
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
|
||||||
focusedField: null,
|
|
||||||
layoutMoveMode: false,
|
|
||||||
mainLayout: null,
|
|
||||||
startupPluginsLoaded: false,
|
|
||||||
dialogs: [],
|
|
||||||
...resourceEditWatcherDefaultState,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Application extends BaseApplication {
|
class Application extends BaseApplication {
|
||||||
|
|
||||||
@ -175,249 +156,9 @@ class Application extends BaseApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reducer(state: AppState = appDefaultState, action: any) {
|
reducer(state: AppState = appDefaultState, action: any) {
|
||||||
let newState = state;
|
let newState = appReducer(state, action);
|
||||||
|
|
||||||
try {
|
|
||||||
switch (action.type) {
|
|
||||||
|
|
||||||
case 'NAV_BACK':
|
|
||||||
case 'NAV_GO':
|
|
||||||
|
|
||||||
{
|
|
||||||
const goingBack = action.type === 'NAV_BACK';
|
|
||||||
|
|
||||||
if (goingBack && !state.navHistory.length) break;
|
|
||||||
|
|
||||||
const currentRoute = state.route;
|
|
||||||
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
const newNavHistory = state.navHistory.slice();
|
|
||||||
|
|
||||||
if (goingBack) {
|
|
||||||
let newAction = null;
|
|
||||||
while (newNavHistory.length) {
|
|
||||||
newAction = newNavHistory.pop();
|
|
||||||
if (newAction.routeName !== state.route.routeName) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newAction) break;
|
|
||||||
|
|
||||||
action = newAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!goingBack) newNavHistory.push(currentRoute);
|
|
||||||
newState.navHistory = newNavHistory;
|
|
||||||
newState.route = action;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'STARTUP_PLUGINS_LOADED':
|
|
||||||
|
|
||||||
// When all startup plugins have loaded, we also recreate the
|
|
||||||
// main layout to ensure that it is updated in the UI. There's
|
|
||||||
// probably a cleaner way to do this, but for now that will do.
|
|
||||||
if (state.startupPluginsLoaded !== action.value) {
|
|
||||||
newState = {
|
|
||||||
...newState,
|
|
||||||
startupPluginsLoaded: action.value,
|
|
||||||
mainLayout: JSON.parse(JSON.stringify(newState.mainLayout)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'WINDOW_CONTENT_SIZE_SET':
|
|
||||||
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
newState.windowContentSize = action.size;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'NOTE_VISIBLE_PANES_TOGGLE':
|
|
||||||
|
|
||||||
{
|
|
||||||
const getNextLayout = (currentLayout: any) => {
|
|
||||||
currentLayout = panes.length === 2 ? 'both' : currentLayout[0];
|
|
||||||
|
|
||||||
let paneOptions;
|
|
||||||
if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_VIEWER) {
|
|
||||||
paneOptions = ['editor', 'viewer'];
|
|
||||||
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_EDITOR_SPLIT) {
|
|
||||||
paneOptions = ['editor', 'both'];
|
|
||||||
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_VIEWER_SPLIT) {
|
|
||||||
paneOptions = ['viewer', 'both'];
|
|
||||||
} else {
|
|
||||||
paneOptions = ['editor', 'viewer', 'both'];
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentLayoutIndex = paneOptions.indexOf(currentLayout);
|
|
||||||
const nextLayoutIndex = currentLayoutIndex === paneOptions.length - 1 ? 0 : currentLayoutIndex + 1;
|
|
||||||
|
|
||||||
const nextLayout = paneOptions[nextLayoutIndex];
|
|
||||||
return nextLayout === 'both' ? ['editor', 'viewer'] : [nextLayout];
|
|
||||||
};
|
|
||||||
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
|
|
||||||
const panes = state.noteVisiblePanes.slice();
|
|
||||||
newState.noteVisiblePanes = getNextLayout(panes);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'NOTE_VISIBLE_PANES_SET':
|
|
||||||
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
newState.noteVisiblePanes = action.panes;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'MAIN_LAYOUT_SET':
|
|
||||||
|
|
||||||
newState = {
|
|
||||||
...state,
|
|
||||||
mainLayout: action.value,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'MAIN_LAYOUT_SET_ITEM_PROP':
|
|
||||||
|
|
||||||
{
|
|
||||||
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
|
|
||||||
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
|
|
||||||
if (item.key === action.itemKey) {
|
|
||||||
(item as any)[action.propName] = action.propValue;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
|
|
||||||
|
|
||||||
newState = {
|
|
||||||
...state,
|
|
||||||
mainLayout: newLayout,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'NOTE_FILE_WATCHER_ADD':
|
|
||||||
|
|
||||||
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
const watchedNoteFiles = newState.watchedNoteFiles.slice();
|
|
||||||
watchedNoteFiles.push(action.id);
|
|
||||||
newState.watchedNoteFiles = watchedNoteFiles;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'NOTE_FILE_WATCHER_REMOVE':
|
|
||||||
|
|
||||||
{
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
const idx = newState.watchedNoteFiles.indexOf(action.id);
|
|
||||||
if (idx >= 0) {
|
|
||||||
const watchedNoteFiles = newState.watchedNoteFiles.slice();
|
|
||||||
watchedNoteFiles.splice(idx, 1);
|
|
||||||
newState.watchedNoteFiles = watchedNoteFiles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'NOTE_FILE_WATCHER_CLEAR':
|
|
||||||
|
|
||||||
if (state.watchedNoteFiles.length) {
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
newState.watchedNoteFiles = [];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'EDITOR_SCROLL_PERCENT_SET':
|
|
||||||
|
|
||||||
{
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
const newPercents = Object.assign({}, newState.lastEditorScrollPercents);
|
|
||||||
newPercents[action.noteId] = action.percent;
|
|
||||||
newState.lastEditorScrollPercents = newPercents;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'NOTE_DEVTOOLS_TOGGLE':
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
newState.devToolsVisible = !newState.devToolsVisible;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'NOTE_DEVTOOLS_SET':
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
newState.devToolsVisible = action.value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'VISIBLE_DIALOGS_ADD':
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
|
|
||||||
newState.visibleDialogs[action.name] = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'VISIBLE_DIALOGS_REMOVE':
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
newState.visibleDialogs = Object.assign({}, newState.visibleDialogs);
|
|
||||||
delete newState.visibleDialogs[action.name];
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'FOCUS_SET':
|
|
||||||
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
newState.focusedField = action.field;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'FOCUS_CLEAR':
|
|
||||||
|
|
||||||
// A field can only clear its own state
|
|
||||||
if (action.field === state.focusedField) {
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
newState.focusedField = null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'DIALOG_OPEN':
|
|
||||||
|
|
||||||
{
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
const newDialogs = newState.dialogs.slice();
|
|
||||||
|
|
||||||
if (newDialogs.find(d => d.name === action.name)) throw new Error(`This dialog is already opened: ${action.name}`);
|
|
||||||
|
|
||||||
newDialogs.push({
|
|
||||||
name: action.name,
|
|
||||||
});
|
|
||||||
newState.dialogs = newDialogs;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'DIALOG_CLOSE':
|
|
||||||
|
|
||||||
{
|
|
||||||
newState = Object.assign({}, state);
|
|
||||||
const newDialogs = newState.dialogs.slice().filter(d => d.name !== action.name);
|
|
||||||
newState.dialogs = newDialogs;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'LAYOUT_MOVE_MODE_SET':
|
|
||||||
|
|
||||||
newState = {
|
|
||||||
...state,
|
|
||||||
layoutMoveMode: action.value,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
newState = resourceEditWatcherReducer(newState, action);
|
newState = resourceEditWatcherReducer(newState, action);
|
||||||
newState = super.reducer(newState, action);
|
newState = super.reducer(newState, action);
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user