From b5f11ca25bd027eeda5f1147afa528f66fc41077 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Tue, 26 Nov 2024 13:07:29 +0100 Subject: [PATCH] feat: keep history when switching mode (#504) --- src/lib/components/JSONEditor.svelte | 5 +- .../components/modals/JSONEditorModal.svelte | 2 +- .../components/modals/TransformModal.svelte | 15 ++ .../components/modes/JSONEditorRoot.svelte | 91 +++++++- .../modes/tablemode/TableMode.svelte | 45 ++-- .../modes/tablemode/menu/TableMenu.svelte | 9 +- .../components/modes/textmode/TextMode.svelte | 211 +++++++++++++----- .../components/modes/treemode/TreeMode.svelte | 43 ++-- .../modes/treemode/menu/TreeMenu.svelte | 15 +- src/lib/logic/history.ts | 57 ++--- src/lib/typeguards.ts | 24 +- src/lib/types.ts | 48 +++- 12 files changed, 423 insertions(+), 142 deletions(-) diff --git a/src/lib/components/JSONEditor.svelte b/src/lib/components/JSONEditor.svelte index ff7e8315..3fb4d5a1 100644 --- a/src/lib/components/JSONEditor.svelte +++ b/src/lib/components/JSONEditor.svelte @@ -287,6 +287,9 @@ case 'content': content = props[name] ?? contentDefault break + case 'selection': + selection = props[name] ?? selectionDefault + break case 'readOnly': readOnly = props[name] ?? readOnlyDefault break @@ -527,7 +530,7 @@ {#key instanceId} ({ + onChange: (updatedHistory) => (history = updatedHistory) + }) + + let history: History = historyInstance.get() + let selectedJson: unknown | undefined $: selectedJson = readonlyProxy(getIn(json, rootPath)) let selectedContent: Content @@ -316,6 +325,7 @@ ({ + onChange: (updatedHistory) => (history = updatedHistory) + }) + + let history: History = historyInstance.get() + + let mode = externalMode + + function applyExternalMode(externalMode: Mode) { + if (externalMode === mode) { + return + } + + const item: ModeHistoryItem = { + type: 'mode', + undo: { mode, selection: undefined }, + redo: { mode: externalMode, selection: undefined } + } + + if (mode === 'text' && refTextMode) { + // flush pending changes before adding a new history item + refTextMode.flush() + } + + debug('add history item', item) + history.add(item) + + mode = externalMode + } + + $: applyExternalMode(externalMode) + + function handleUndo(item: HistoryItem | undefined) { + if (isModeHistoryItem(item)) { + mode = item.undo.mode // important to prevent a new history item from being created + + // find the selection of the previous history item (if any), and use that as initial selection + const items = history.items() + const index = items.findIndex((i) => i === item) + const prevItem = index !== -1 ? items[index - 1] : undefined + debug('handleUndo', { index, item, items, prevItem }) + if (prevItem) { + selection = prevItem.redo.selection + } + + onChangeMode(mode) + } + } + + function handleRedo(item: HistoryItem | undefined) { + if (isModeHistoryItem(item)) { + // prevent a new history item from being created + mode = item.redo.mode + + // find the selection of the next history item (if any), and use that as initial selection + const items = history.items() + const index = items.findIndex((i) => i === item) + const nextItem = index !== -1 ? items[index + 1] : undefined + debug('handleRedo', { index, item, items, nextItem }) + if (nextItem) { + selection = nextItem.undo.selection + } + + onChangeMode(mode) + } + } + let modeMenuItems: MenuItem[] $: modeMenuItems = [ { @@ -268,6 +342,7 @@ bind:this={refTextMode} externalContent={content} externalSelection={selection} + {history} {readOnly} {indentation} {tabSize} @@ -279,8 +354,10 @@ {validator} {validationParser} {onChange} - {onSelect} {onChangeMode} + {onSelect} + onUndo={handleUndo} + onRedo={handleRedo} {onError} {onFocus} {onBlur} @@ -293,6 +370,7 @@ bind:this={refTableMode} externalContent={content} externalSelection={selection} + {history} {readOnly} {mainMenuBar} {escapeControlCharacters} @@ -306,6 +384,8 @@ {onChange} {onChangeMode} {onSelect} + onUndo={handleUndo} + onRedo={handleRedo} {onRenderValue} {onFocus} {onBlur} @@ -321,6 +401,7 @@ bind:this={refTreeMode} externalContent={content} externalSelection={selection} + {history} {readOnly} {indentation} {mainMenuBar} @@ -336,6 +417,8 @@ {onChange} {onChangeMode} {onSelect} + onUndo={handleUndo} + onRedo={handleRedo} {onRenderValue} {onClassName} {onFocus} diff --git a/src/lib/components/modes/tablemode/TableMode.svelte b/src/lib/components/modes/tablemode/TableMode.svelte index b139776c..405ee182 100644 --- a/src/lib/components/modes/tablemode/TableMode.svelte +++ b/src/lib/components/modes/tablemode/TableMode.svelte @@ -9,33 +9,36 @@ ContentErrors, ContextMenuItem, DocumentState, + History, HistoryItem, JSONEditorContext, JSONEditorSelection, JSONParser, JSONPatchResult, + JSONRepairModalProps, JSONSelection, OnBlur, OnChange, OnChangeMode, OnFocus, OnJSONEditorModal, + OnRedo, OnRenderContextMenuInternal, OnRenderMenuInternal, OnRenderValue, OnSelect, OnSortModal, OnTransformModal, + OnUndo, ParseError, PastedJson, - SearchResults, SearchResultDetails, + SearchResults, SortedColumn, TransformModalOptions, ValidationError, Validator, - ValueNormalization, - JSONRepairModalProps + ValueNormalization } from '$lib/types' import { Mode, SortDirection, ValidationSeverity } from '$lib/types.js' import TableMenu from './menu/TableMenu.svelte' @@ -105,7 +108,6 @@ pathStartsWith, removeEditModeFromSelection } from '$lib/logic/selection.js' - import { createHistory } from '$lib/logic/history.js' import ColumnHeader from './ColumnHeader.svelte' import { sortJson } from '$lib/logic/sort.js' import { keyComboFromEvent } from '$lib/utils/keyBindings.js' @@ -150,6 +152,7 @@ import ContextMenu from '../../controls/contextmenu/ContextMenu.svelte' import { flattenSearchResults, toRecursiveSearchResults } from '$lib/logic/search.js' import JSONValue from '../treemode/JSONValue.svelte' + import { isTreeHistoryItem } from 'svelte-jsoneditor' const debug = createDebug('jsoneditor:TableMode') const { openAbsolutePopup, closeAbsolutePopup } = @@ -164,6 +167,7 @@ export let readOnly: boolean export let externalContent: Content export let externalSelection: JSONEditorSelection | undefined + export let history: History export let mainMenuBar: boolean export let escapeControlCharacters: boolean export let escapeUnicodeCharacters: boolean @@ -176,6 +180,8 @@ export let onChange: OnChange export let onChangeMode: OnChangeMode export let onSelect: OnSelect + export let onUndo: OnUndo + export let onRedo: OnRedo export let onRenderValue: OnRenderValue export let onRenderMenu: OnRenderMenuInternal export let onRenderContextMenu: OnRenderContextMenuInternal @@ -338,10 +344,18 @@ let documentState: DocumentState | undefined = json !== undefined ? createDocumentState({ json }) : undefined - let selection: JSONSelection | undefined + let selection: JSONSelection | undefined = isJSONSelection(externalSelection) + ? externalSelection + : undefined let sortedColumn: SortedColumn | undefined let textIsRepaired = false + onMount(() => { + if (selection) { + scrollIntoView(getFocusPath(selection)) + } + }) + function onSortByHeader(newSortedColumn: SortedColumn) { if (readOnly) { return @@ -360,13 +374,6 @@ }) } - const history = createHistory({ - onChange: (state) => { - historyState = state - } - }) - let historyState = history.getState() - let context: JSONEditorContext $: context = { // eslint-disable-next-line svelte/no-unused-svelte-ignore @@ -481,6 +488,7 @@ const canPatch = json !== undefined && previous.json !== undefined history.add({ + type: 'tree', undo: { patch: canPatch ? [{ op: 'replace', path: '', value: previous.json }] : undefined, json: previous.json, @@ -608,6 +616,7 @@ parseError = undefined history.add({ + type: 'tree', undo: { patch: undo, ...previousState @@ -1586,12 +1595,13 @@ return } - if (!history.getState().canUndo) { + if (!history.canUndo) { return } const item = history.undo() - if (!item) { + if (!isTreeHistoryItem(item)) { + onUndo(item) return } @@ -1630,12 +1640,13 @@ return } - if (!history.getState().canRedo) { + if (!history.canRedo) { return } const item = history.redo() - if (!item) { + if (!isTreeHistoryItem(item)) { + onRedo(item) return } @@ -1695,7 +1706,7 @@ {containsValidArray} {readOnly} bind:showSearch - {historyState} + {history} onSort={handleSortAll} onTransform={handleTransformAll} onUndo={handleUndo} diff --git a/src/lib/components/modes/tablemode/menu/TableMenu.svelte b/src/lib/components/modes/tablemode/menu/TableMenu.svelte index 2143e024..c3c31fa9 100644 --- a/src/lib/components/modes/tablemode/menu/TableMenu.svelte +++ b/src/lib/components/modes/tablemode/menu/TableMenu.svelte @@ -1,7 +1,7 @@