From f636e5ec109e54751b8f680efe06d665aba5f44d Mon Sep 17 00:00:00 2001 From: Erik Michelson Date: Wed, 9 Sep 2020 11:22:52 +0200 Subject: [PATCH] Save editor preferences to localStorage (#541 / #553) * Added editor-preferences to redux store * Add local-storage saving and retrieval of EditorConfig * Change import to be in a single line * Add equality check to redux-selector (as suggested by @mrdrogdrog) * Save and load editor-config to/from localStorage --- .../editor/editor-pane/editor-pane.tsx | 10 +---- .../editor-preferences/editor-preferences.tsx | 16 ++++---- .../editor/editor-pane/tool-bar/tool-bar.tsx | 8 ++-- src/redux/editor/methods.ts | 40 ++++++++++++++++++- src/redux/editor/reducers.ts | 36 ++++++++++++++--- src/redux/editor/types.ts | 9 ++++- 6 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/components/editor/editor-pane/editor-pane.tsx b/src/components/editor/editor-pane/editor-pane.tsx index 2babbd846..bec478c5e 100644 --- a/src/components/editor/editor-pane/editor-pane.tsx +++ b/src/components/editor/editor-pane/editor-pane.tsx @@ -20,6 +20,7 @@ import 'codemirror/keymap/emacs' import 'codemirror/keymap/sublime' import 'codemirror/keymap/vim' import 'codemirror/mode/gfm/gfm' +import equal from 'fast-deep-equal' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Controlled as ControlledCodeMirror } from 'react-codemirror2' import { useTranslation } from 'react-i18next' @@ -60,12 +61,7 @@ export const EditorPane: React.FC = ({ onContentC const maxLengthWarningAlreadyShown = useRef(false) const [editor, setEditor] = useState() const [statusBarInfo, setStatusBarInfo] = useState(defaultState) - const [editorPreferences, setEditorPreferences] = useState({ - theme: 'one-dark', - keyMap: 'sublime', - indentUnit: 4, - indentWithTabs: false - }) + const editorPreferences = useSelector((state: ApplicationState) => state.editorConfig.preferences, equal) const lastScrollPosition = useRef() const [editorScroll, setEditorScroll] = useState() @@ -158,8 +154,6 @@ export const EditorPane: React.FC = ({ onContentC setShowMaxLengthWarning(false)} maxLength={maxLength}/> setEditorPreferences(config)} - editorPreferences={editorPreferences} /> void -} - -export const EditorPreferences: React.FC = ({ onPreferencesChange, preferences }) => { +export const EditorPreferences: React.FC = () => { const { t } = useTranslation() const [showModal, setShowModal] = useState(false) + const preferences = useSelector((state: ApplicationState) => state.editorConfig.preferences, equal) const sendPreferences = useCallback((newPreferences: EditorConfiguration) => { - onPreferencesChange(newPreferences) - }, [onPreferencesChange]) + setEditorPreferences(newPreferences) + }, []) return ( diff --git a/src/components/editor/editor-pane/tool-bar/tool-bar.tsx b/src/components/editor/editor-pane/tool-bar/tool-bar.tsx index a0c062573..880b6a4f0 100644 --- a/src/components/editor/editor-pane/tool-bar/tool-bar.tsx +++ b/src/components/editor/editor-pane/tool-bar/tool-bar.tsx @@ -1,4 +1,4 @@ -import { Editor, EditorConfiguration } from 'codemirror' +import { Editor } from 'codemirror' import React from 'react' import { Button, ButtonGroup, ButtonToolbar } from 'react-bootstrap' import { useTranslation } from 'react-i18next' @@ -28,11 +28,9 @@ import { export interface ToolBarProps { editor: Editor | undefined - onPreferencesChange: (config: EditorConfiguration) => void - editorPreferences: EditorConfiguration } -export const ToolBar: React.FC = ({ editor, onPreferencesChange, editorPreferences }) => { +export const ToolBar: React.FC = ({ editor }) => { const { t } = useTranslation() const notImplemented = () => { @@ -109,7 +107,7 @@ export const ToolBar: React.FC = ({ editor, onPreferencesChange, e - + ) diff --git a/src/redux/editor/methods.ts b/src/redux/editor/methods.ts index 4b94f912d..9c06c77d6 100644 --- a/src/redux/editor/methods.ts +++ b/src/redux/editor/methods.ts @@ -1,6 +1,34 @@ +import { EditorConfiguration } from 'codemirror' import { store } from '..' import { EditorMode } from '../../components/editor/app-bar/editor-view-mode' -import { EditorConfigActionType, SetEditorConfigAction, SetEditorSyncScrollAction } from './types' +import { + EditorConfig, + EditorConfigActionType, + SetEditorConfigAction, + SetEditorPreferencesAction, + SetEditorSyncScrollAction +} from './types' + +export const loadFromLocalStorage = (): EditorConfig | undefined => { + try { + const stored = window.localStorage.getItem('editorConfig') + if (!stored) { + return undefined + } + return JSON.parse(stored) as EditorConfig + } catch (_) { + return undefined + } +} + +export const saveToLocalStorage = (editorConfig: EditorConfig): void => { + try { + const json = JSON.stringify(editorConfig) + localStorage.setItem('editorConfig', json) + } catch (e) { + console.error('Can not persist editor config in local storage: ', e) + } +} export const setEditorMode = (editorMode: EditorMode): void => { const action: SetEditorConfigAction = { @@ -17,3 +45,13 @@ export const setEditorSyncScroll = (syncScroll: boolean): void => { } store.dispatch(action) } + +export const setEditorPreferences = (preferences: EditorConfiguration): void => { + const action: SetEditorPreferencesAction = { + type: EditorConfigActionType.SET_EDITOR_PREFERENCES, + preferences: { + ...preferences + } + } + store.dispatch(action) +} diff --git a/src/redux/editor/reducers.ts b/src/redux/editor/reducers.ts index 0a837ce1c..9132efbb1 100644 --- a/src/redux/editor/reducers.ts +++ b/src/redux/editor/reducers.ts @@ -1,30 +1,54 @@ import { Reducer } from 'redux' +import { EditorMode } from '../../components/editor/app-bar/editor-view-mode' +import { loadFromLocalStorage, saveToLocalStorage } from './methods' import { EditorConfig, EditorConfigActions, EditorConfigActionType, SetEditorConfigAction, + SetEditorPreferencesAction, SetEditorSyncScrollAction } from './types' -import { EditorMode } from '../../components/editor/app-bar/editor-view-mode' -export const initialState: EditorConfig = { +const initialState: EditorConfig = { editorMode: EditorMode.BOTH, - syncScroll: true + syncScroll: true, + preferences: { + theme: 'one-dark', + keyMap: 'sublime', + indentUnit: 4, + indentWithTabs: false + } } -export const EditorConfigReducer: Reducer = (state: EditorConfig = initialState, action: EditorConfigActions) => { +const getInitialState = (): EditorConfig => { + return loadFromLocalStorage() ?? initialState +} + +export const EditorConfigReducer: Reducer = (state: EditorConfig = getInitialState(), action: EditorConfigActions) => { + let newState: EditorConfig switch (action.type) { case EditorConfigActionType.SET_EDITOR_VIEW_MODE: - return { + newState = { ...state, editorMode: (action as SetEditorConfigAction).mode } + saveToLocalStorage(newState) + return newState case EditorConfigActionType.SET_SYNC_SCROLL: - return { + newState = { ...state, syncScroll: (action as SetEditorSyncScrollAction).syncScroll } + saveToLocalStorage(newState) + return newState + case EditorConfigActionType.SET_EDITOR_PREFERENCES: + newState = { + ...state, + preferences: (action as SetEditorPreferencesAction).preferences + } + saveToLocalStorage(newState) + return newState default: return state } diff --git a/src/redux/editor/types.ts b/src/redux/editor/types.ts index 2112ed1cd..6068696be 100644 --- a/src/redux/editor/types.ts +++ b/src/redux/editor/types.ts @@ -1,14 +1,17 @@ +import { EditorConfiguration } from 'codemirror' import { Action } from 'redux' import { EditorMode } from '../../components/editor/app-bar/editor-view-mode' export enum EditorConfigActionType { SET_EDITOR_VIEW_MODE = 'editor/mode/set', - SET_SYNC_SCROLL = 'editor/syncScroll/set' + SET_SYNC_SCROLL = 'editor/syncScroll/set', + SET_EDITOR_PREFERENCES = 'editor/preferences/set' } export interface EditorConfig { editorMode: EditorMode; syncScroll: boolean; + preferences: EditorConfiguration } export interface EditorConfigActions extends Action { @@ -22,3 +25,7 @@ export interface SetEditorSyncScrollAction extends EditorConfigActions { export interface SetEditorConfigAction extends EditorConfigActions { mode: EditorMode } + +export interface SetEditorPreferencesAction extends EditorConfigActions { + preferences: EditorConfiguration +}