From 694e52f742436827556a40c345143701085fa8eb Mon Sep 17 00:00:00 2001 From: Jacob Robinson <39199632+jakethecoder95@users.noreply.github.com> Date: Fri, 13 Mar 2020 06:20:14 -0700 Subject: [PATCH] feat: Code saveing to localStorage on Cmd/Ctrl + S (#38324) Co-authored-by: Oliver Eyton-Williams Co-authored-by: mrugesh <1884376+raisedadead@users.noreply.github.com> --- .../templates/Challenges/classic/Editor.js | 15 ++++++-- .../Challenges/redux/code-storage-epic.js | 34 ++++++++++++++++--- .../src/templates/Challenges/redux/index.js | 2 ++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/client/src/templates/Challenges/classic/Editor.js b/client/src/templates/Challenges/classic/Editor.js index cd2b3f761a..be5219cd03 100644 --- a/client/src/templates/Challenges/classic/Editor.js +++ b/client/src/templates/Challenges/classic/Editor.js @@ -7,6 +7,7 @@ import { canFocusEditorSelector, executeChallenge, inAccessibilityModeSelector, + saveEditorContent, setEditorFocusability, setAccessibilityMode, updateFile @@ -25,6 +26,7 @@ const propTypes = { ext: PropTypes.string, fileKey: PropTypes.string, inAccessibilityMode: PropTypes.bool.isRequired, + saveEditorContent: PropTypes.func.isRequired, setAccessibilityMode: PropTypes.func.isRequired, setEditorFocusability: PropTypes.func, theme: PropTypes.string, @@ -44,9 +46,10 @@ const mapStateToProps = createSelector( ); const mapDispatchToProps = { - setEditorFocusability, - setAccessibilityMode, executeChallenge, + saveEditorContent, + setAccessibilityMode, + setEditorFocusability, updateFile }; @@ -150,6 +153,14 @@ class Editor extends Component { this.props.setEditorFocusability(false); } }); + this._editor.addAction({ + id: 'save-editor-content', + label: 'Save editor content to localStorage', + keybindings: [ + monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S) + ], + run: this.props.saveEditorContent + }); this._editor.addAction({ id: 'toggle-accessibility', label: 'Toggle Accessibility Mode', diff --git a/client/src/templates/Challenges/redux/code-storage-epic.js b/client/src/templates/Challenges/redux/code-storage-epic.js index 80250da57f..5e74323f0b 100644 --- a/client/src/templates/Challenges/redux/code-storage-epic.js +++ b/client/src/templates/Challenges/redux/code-storage-epic.js @@ -1,5 +1,5 @@ import { of } from 'rxjs'; -import { filter, switchMap, tap, ignoreElements } from 'rxjs/operators'; +import { filter, switchMap, map, tap, ignoreElements } from 'rxjs/operators'; import { combineEpics, ofType } from 'redux-observable'; import store from 'store'; @@ -16,6 +16,8 @@ import { types as appTypes } from '../../../redux'; import { setContent, isPoly } from '../utils/polyvinyl'; +import { createFlashMessage } from '../../../components/Flash/redux'; + const legacyPrefixes = [ 'Bonfire: ', 'Waypoint: ', @@ -73,16 +75,38 @@ function clearCodeEpic(action$, state$) { function saveCodeEpic(action$, state$) { return action$.pipe( - ofType(types.executeChallenge), + ofType(types.executeChallenge, types.saveEditorContent), // do not save challenge if code is locked filter(() => !isCodeLockedSelector(state$.value)), - tap(() => { + map(action => { const state = state$.value; const { id } = challengeMetaSelector(state); const files = challengeFilesSelector(state); - store.set(id, files); + try { + store.set(id, files); + // Possible fileType values: indexhtml indexjs indexjsx + // The files Object always has one of these as the first/only attribute + const fileType = Object.keys(files)[0]; + if (store.get(id)[fileType].contents !== files[fileType].contents) { + throw Error('Failed to save to localStorage'); + } + return action; + } catch (e) { + return { ...action, error: true }; + } }), - ignoreElements() + ofType(types.saveEditorContent), + switchMap(({ error }) => + of( + createFlashMessage({ + type: error ? 'warning' : 'success', + message: error + ? // eslint-disable-next-line max-len + "Oops, your code did not save, your browser's local storage may be full." + : "Saved! Your code was saved to your browser's local storage." + }) + ) + ) ); } diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js index 8cb92f4a43..3de0fa585d 100644 --- a/client/src/templates/Challenges/redux/index.js +++ b/client/src/templates/Challenges/redux/index.js @@ -70,6 +70,7 @@ export const types = createTypes( 'disableBuildOnError', 'storedCodeFound', 'noStoredCodeFound', + 'saveEditorContent', 'closeModal', 'openModal', @@ -147,6 +148,7 @@ export const unlockCode = createAction(types.unlockCode); export const disableBuildOnError = createAction(types.disableBuildOnError); export const storedCodeFound = createAction(types.storedCodeFound); export const noStoredCodeFound = createAction(types.noStoredCodeFound); +export const saveEditorContent = createAction(types.saveEditorContent); export const closeModal = createAction(types.closeModal); export const openModal = createAction(types.openModal);