Revert to preserving editor state with fromJson due to state callbacks not being preserved

This commit is contained in:
Gregory Schier
2025-01-01 08:19:41 -08:00
parent be938a81dc
commit add39bda6e
9 changed files with 76 additions and 69 deletions

View File

@@ -4,7 +4,7 @@ import { useCallback, useMemo, useState } from 'react';
import slugify from 'slugify';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { count } from '../lib/pluralize';
import { pluralizeCount } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { Button } from './core/Button';
import { Checkbox } from './core/Checkbox';
@@ -128,7 +128,7 @@ function ExportDataDialogContent({
disabled={noneSelected}
onClick={() => handleExport()}
>
Export {count('Workspace', numSelected, { omitSingle: true, noneWord: 'Nothing' })}
Export {pluralizeCount('Workspace', numSelected, { omitSingle: true, noneWord: 'Nothing' })}
</Button>
</HStack>
</VStack>

View File

@@ -14,7 +14,7 @@ import { useAlert } from '../hooks/useAlert';
import type { ReflectResponseService } from '../hooks/useGrpc';
import { tryFormatJson } from '../lib/formatters';
import type { GrpcRequest } from '@yaakapp-internal/models';
import { count } from '../lib/pluralize';
import { pluralizeCount } from '../lib/pluralize';
import { Button } from './core/Button';
import { FormattedError } from './core/FormattedError';
import { InlineCode } from './core/InlineCode';
@@ -164,7 +164,7 @@ export function GrpcEditor({
: reflectionError
? 'Server Error'
: protoFiles.length > 0
? count('File', protoFiles.length)
? pluralizeCount('File', protoFiles.length)
: services != null && protoFiles.length === 0
? 'Schema Detected'
: 'Select Schema'}

View File

@@ -2,7 +2,7 @@ import { open } from '@tauri-apps/plugin-dialog';
import { useGrpc } from '../hooks/useGrpc';
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
import { useGrpcRequest } from '../hooks/useGrpcRequest';
import { count } from '../lib/pluralize';
import { pluralizeCount } from '../lib/pluralize';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import { IconButton } from './core/IconButton';
@@ -76,7 +76,7 @@ export function GrpcProtoSelection({ requestId }: Props) {
</span>
);
})}
{services?.length > 5 && count('other', services?.length - 5)}
{services?.length > 5 && pluralizeCount('other', services?.length - 5)}
</p>
</Banner>
)}

View File

@@ -2,7 +2,7 @@ import { formatDistanceToNowStrict } from 'date-fns';
import { useDeleteGrpcConnection } from '../hooks/useDeleteGrpcConnection';
import { useDeleteGrpcConnections } from '../hooks/useDeleteGrpcConnections';
import type { GrpcConnection } from '@yaakapp-internal/models';
import { count } from '../lib/pluralize';
import { pluralizeCount } from '../lib/pluralize';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
@@ -34,7 +34,7 @@ export function RecentConnectionsDropdown({
},
{
key: 'clear-all',
label: `Clear ${count('Connection', connections.length)}`,
label: `Clear ${pluralizeCount('Connection', connections.length)}`,
onSelect: deleteAllConnections.mutate,
hidden: connections.length <= 1,
disabled: connections.length === 0,

View File

@@ -1,6 +1,7 @@
import { defaultKeymap } from '@codemirror/commands';
import { forceParsing } from '@codemirror/language';
import { Compartment, EditorState, type Extension } from '@codemirror/state';
import { defaultKeymap, historyField } from '@codemirror/commands';
import { foldState, forceParsing } from '@codemirror/language';
import type { EditorStateConfig, Extension } from '@codemirror/state';
import { Compartment, EditorState } from '@codemirror/state';
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
import type { EnvironmentVariable } from '@yaakapp-internal/models';
import type { TemplateFunction } from '@yaakapp-internal/plugin';
@@ -74,6 +75,8 @@ export interface EditorProps {
stateKey: string | null;
}
const stateFields = { history: historyField, folds: foldState };
const emptyVariables: EnvironmentVariable[] = [];
export const Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
@@ -305,40 +308,42 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
onClickPathParameter,
});
const extensions = [
languageCompartment.of(langExt),
placeholderCompartment.current.of(
placeholderExt(placeholderElFromText(placeholder ?? '')),
),
wrapLinesCompartment.current.of(wrapLines ? [EditorView.lineWrapping] : []),
...getExtensions({
container,
readOnly,
singleLine,
hideGutter,
stateKey,
onChange: handleChange,
onPaste: handlePaste,
onPasteOverwrite: handlePasteOverwrite,
onFocus: handleFocus,
onBlur: handleBlur,
onKeyDown: handleKeyDown,
}),
...(extraExtensions ?? []),
];
const cachedJsonState = getCachedEditorState(defaultValue ?? '', stateKey);
const state =
cachedJsonState ??
EditorState.create({
doc: `${defaultValue ?? ''}`,
extensions: [
languageCompartment.of(langExt),
placeholderCompartment.current.of(
placeholderExt(placeholderElFromText(placeholder ?? '')),
),
wrapLinesCompartment.current.of(wrapLines ? [EditorView.lineWrapping] : []),
...getExtensions({
container,
readOnly,
singleLine,
hideGutter,
stateKey,
onChange: handleChange,
onPaste: handlePaste,
onPasteOverwrite: handlePasteOverwrite,
onFocus: handleFocus,
onBlur: handleBlur,
onKeyDown: handleKeyDown,
}),
...(extraExtensions ?? []),
],
});
const doc = `${defaultValue ?? ''}`;
const config: EditorStateConfig = { extensions, doc };
const state = cachedJsonState
? EditorState.fromJSON(cachedJsonState, config, stateFields)
: EditorState.create(config);
const view = new EditorView({ state, parent: container });
// For large documents, the parser may parse the max number of lines and fail to add
// things like fold markers because of it.
// This forces it to parse more but keeps the timeout to the default of 100ms.
// This forces it to parse more but keeps the timeout to the default of 100 ms.
forceParsing(view, 9e6, 100);
cm.current = { view, languageCompartment };
@@ -544,24 +549,25 @@ const placeholderElFromText = (text: string) => {
return el;
};
declare global {
interface Window {
editorStates: Record<string, EditorState>;
}
}
window.editorStates = window.editorStates ?? {};
function saveCachedEditorState(stateKey: string | null, state: EditorState | null) {
if (!stateKey || state == null) return;
window.editorStates[stateKey] = state;
sessionStorage.setItem(stateKey, JSON.stringify(state.toJSON(stateFields)));
}
function getCachedEditorState(doc: string, stateKey: string | null) {
if (stateKey == null) return;
const state = window.editorStates[stateKey] ?? null;
if (state == null) return null;
if (state.doc.toString() !== doc) return null;
const stateStr = sessionStorage.getItem(stateKey)
if (stateStr == null) return null;
return state;
try {
const state = JSON.parse(stateStr);
if (state.doc !== doc) return null;
return state;
} catch {
// Nothing
}
return null;
}

View File

@@ -6,8 +6,8 @@ import {
} from '@codemirror/autocomplete';
import { history, historyKeymap, indentWithTab } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { markdown } from '@codemirror/lang-markdown';
import { json } from '@codemirror/lang-json';
import { markdown } from '@codemirror/lang-markdown';
import { xml } from '@codemirror/lang-xml';
import type { LanguageSupport } from '@codemirror/language';
import {
@@ -37,7 +37,8 @@ import type { EnvironmentVariable } from '@yaakapp-internal/models';
import type { TemplateFunction } from '@yaakapp-internal/plugin';
import { graphql } from 'cm6-graphql';
import { EditorView } from 'codemirror';
import type {EditorProps} from "./Editor";
import { pluralizeCount } from '../../../lib/pluralize';
import type { EditorProps } from './Editor';
import { pairs } from './pairs/extension';
import { text } from './text/extension';
import { twig } from './twig/extension';
@@ -162,11 +163,15 @@ export const multiLineExtensions = ({ hideGutter }: { hideGutter?: boolean }) =>
const el = document.createElement('span');
el.onclick = onclick;
el.className = 'cm-foldPlaceholder';
el.innerText = prepared;
el.innerText = prepared || '…';
el.title = 'unfold';
el.ariaLabel = 'folded code';
return el;
},
/**
* Show the number of items when code folded. NOTE: this doesn't get called when restoring
* a previous serialized editor state, which is a bummer
*/
preparePlaceholder(state, range) {
let count: number | undefined;
let startToken = '{';
@@ -191,13 +196,9 @@ export const multiLineExtensions = ({ hideGutter }: { hideGutter?: boolean }) =>
}
if (count !== undefined) {
const label = isArray ? 'item' : 'prop';
const plural = count === 1 ? '' : 's';
return `${count} ${label}${plural}`;
const label = isArray ? 'item' : 'key';
return pluralizeCount(label, count);
}
return '…';
},
}),
EditorState.allowMultipleSelections.of(true),

View File

@@ -1,5 +1,5 @@
import { useSetAtom } from 'jotai/index';
import { count } from '../lib/pluralize';
import { pluralizeCount } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { getActiveWorkspaceId } from './useActiveWorkspace';
import { useAlert } from './useAlert';
@@ -15,8 +15,8 @@ export function useDeleteSendHistory() {
const httpResponses = useHttpResponses();
const grpcConnections = useGrpcConnections();
const labels = [
httpResponses.length > 0 ? count('Http Response', httpResponses.length) : null,
grpcConnections.length > 0 ? count('Grpc Connection', grpcConnections.length) : null,
httpResponses.length > 0 ? pluralizeCount('Http Response', httpResponses.length) : null,
grpcConnections.length > 0 ? pluralizeCount('Grpc Connection', grpcConnections.length) : null,
].filter((l) => l != null);
return useFastMutation({

View File

@@ -10,7 +10,7 @@ import { Button } from '../components/core/Button';
import { FormattedError } from '../components/core/FormattedError';
import { VStack } from '../components/core/Stacks';
import { ImportDataDialog } from '../components/ImportDataDialog';
import { count } from '../lib/pluralize';
import { pluralizeCount } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { getActiveWorkspace } from './useActiveWorkspace';
import { useAlert } from './useAlert';
@@ -47,11 +47,11 @@ export function useImportData() {
return (
<VStack space={3} className="pb-4">
<ul className="list-disc pl-6">
<li>{count('Workspace', workspaces.length)}</li>
<li>{count('Environment', environments.length)}</li>
<li>{count('Folder', folders.length)}</li>
<li>{count('HTTP Request', httpRequests.length)}</li>
<li>{count('GRPC Request', grpcRequests.length)}</li>
<li>{pluralizeCount('Workspace', workspaces.length)}</li>
<li>{pluralizeCount('Environment', environments.length)}</li>
<li>{pluralizeCount('Folder', folders.length)}</li>
<li>{pluralizeCount('HTTP Request', httpRequests.length)}</li>
<li>{pluralizeCount('GRPC Request', grpcRequests.length)}</li>
</ul>
<div>
<Button className="ml-auto" onClick={hide} color="primary">

View File

@@ -5,7 +5,7 @@ export function pluralize(word: string, count: number): string {
return `${word}s`;
}
export function count(
export function pluralizeCount(
word: string,
count: number,
opt: { omitSingle?: boolean; noneWord?: string } = {},