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
This commit is contained in:
Erik Michelson 2020-09-09 11:22:52 +02:00 committed by GitHub
parent a86d4cbc58
commit f636e5ec10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 90 additions and 29 deletions

View file

@ -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<EditorPaneProps & ScrollProps> = ({ onContentC
const maxLengthWarningAlreadyShown = useRef(false)
const [editor, setEditor] = useState<Editor>()
const [statusBarInfo, setStatusBarInfo] = useState<StatusBarInfo>(defaultState)
const [editorPreferences, setEditorPreferences] = useState<EditorConfiguration>({
theme: 'one-dark',
keyMap: 'sublime',
indentUnit: 4,
indentWithTabs: false
})
const editorPreferences = useSelector((state: ApplicationState) => state.editorConfig.preferences, equal)
const lastScrollPosition = useRef<number>()
const [editorScroll, setEditorScroll] = useState<ScrollInfo>()
@ -158,8 +154,6 @@ export const EditorPane: React.FC<EditorPaneProps & ScrollProps> = ({ onContentC
<MaxLengthWarningModal show={showMaxLengthWarning} onHide={() => setShowMaxLengthWarning(false)} maxLength={maxLength}/>
<ToolBar
editor={editor}
onPreferencesChange={config => setEditorPreferences(config)}
editorPreferences={editorPreferences}
/>
<ControlledCodeMirror
className="overflow-hidden w-100 flex-fill"

View file

@ -1,23 +1,23 @@
import { EditorConfiguration } from 'codemirror'
import equal from 'fast-deep-equal'
import React, { Fragment, useCallback, useState } from 'react'
import { Button, Form, ListGroup } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { ApplicationState } from '../../../../../redux'
import { setEditorPreferences } from '../../../../../redux/editor/methods'
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
import { CommonModal } from '../../../../common/modals/common-modal'
import { EditorPreferenceProperty, EditorPreferenceSelect } from './editor-preference-select'
export interface EditorSettingsButtonProps {
preferences: EditorConfiguration
onPreferencesChange: (config: EditorConfiguration) => void
}
export const EditorPreferences: React.FC<EditorSettingsButtonProps> = ({ 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 (
<Fragment>

View file

@ -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<ToolBarProps> = ({ editor, onPreferencesChange, editorPreferences }) => {
export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
const { t } = useTranslation()
const notImplemented = () => {
@ -109,7 +107,7 @@ export const ToolBar: React.FC<ToolBarProps> = ({ editor, onPreferencesChange, e
<EmojiPickerButton editor={editor}/>
</ButtonGroup>
<ButtonGroup className={'mx-1 flex-wrap'}>
<EditorPreferences onPreferencesChange={onPreferencesChange} preferences={editorPreferences}/>
<EditorPreferences/>
</ButtonGroup>
</ButtonToolbar>
)

View file

@ -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)
}

View file

@ -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<EditorConfig, EditorConfigActions> = (state: EditorConfig = initialState, action: EditorConfigActions) => {
const getInitialState = (): EditorConfig => {
return loadFromLocalStorage() ?? initialState
}
export const EditorConfigReducer: Reducer<EditorConfig, EditorConfigActions> = (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
}

View file

@ -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<EditorConfigActionType> {
@ -22,3 +25,7 @@ export interface SetEditorSyncScrollAction extends EditorConfigActions {
export interface SetEditorConfigAction extends EditorConfigActions {
mode: EditorMode
}
export interface SetEditorPreferencesAction extends EditorConfigActions {
preferences: EditorConfiguration
}