From 06cc3e4346228ee3e48047bb3fb447ed0eed011b Mon Sep 17 00:00:00 2001 From: Joe Bottigliero <694253+jbottigliero@users.noreply.github.com> Date: Wed, 9 Oct 2024 19:14:16 -0500 Subject: [PATCH 1/3] feat: adds "Input Schema" validation --- public/schemas/input_schema_schema.json | 246 ++++++++++++++++++++++++ src/components/Editor.tsx | 30 ++- src/pages/index.tsx | 54 +++++- src/stores/editor.ts | 9 + 4 files changed, 325 insertions(+), 14 deletions(-) create mode 100644 public/schemas/input_schema_schema.json diff --git a/public/schemas/input_schema_schema.json b/public/schemas/input_schema_schema.json new file mode 100644 index 0000000..4e2623a --- /dev/null +++ b/public/schemas/input_schema_schema.json @@ -0,0 +1,246 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { + "$ref": "#/definitions/nonNegativeInteger" + }, + { + "default": 0 + } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#" + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ], + "default": true + }, + "maxItems": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#" + }, + "maxProperties": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#" + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "propertyNames": { + "format": "regex" + }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/stringArray" + } + ] + } + }, + "propertyNames": { + "$ref": "#" + }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "if": { + "$ref": "#" + }, + "then": { + "$ref": "#" + }, + "else": { + "$ref": "#" + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "not": { + "$ref": "#" + }, + "propertyOrder": { + "$ref": "#/definitions/stringArray" + } + }, + "default": true, + "additionalProperties": false +} diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 7bc06bf..76bca29 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -7,16 +7,30 @@ import MonacoEditor, { import { ValidateButton } from "./Validate"; import { Box } from "@chakra-ui/react"; +export const MODES = { + DEFINITION: "DEFINITION", + INPUT_SCHEMA: "INPUT_SCHEMA", +} as const; + +export type SupportedModes = keyof typeof MODES; + +function isDefinitionMode(settings?: InteralSettings) { + return settings?.mode === MODES.DEFINITION || !settings?.mode; +} + type InteralSettings = { - enableExperimentalValidation: boolean; + mode?: SupportedModes; + enableExperimentalValidation?: boolean; }; function configureEditor( monaco: Monaco, settings: InteralSettings = { + mode: MODES.DEFINITION, enableExperimentalValidation: true, }, ) { + console.log("Configuring editor with settings", settings); monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ enableSchemaRequest: true, validate: true, @@ -26,7 +40,11 @@ function configureEditor( schemas: [ { uri: `${window.location.origin}${process.env.NEXT_PUBLIC_BASE_PATH}/schemas/flow_definition_schema.json`, - fileMatch: ["*"], + fileMatch: ["definition.json"], + }, + { + uri: `${window.location.origin}${process.env.NEXT_PUBLIC_BASE_PATH}/schemas/input_schema_schema.json`, + fileMatch: ["input-schema.json"], }, ], }); @@ -46,9 +64,11 @@ export default function Editor( }} {...props} /> - - - + {isDefinitionMode(props.settings) && ( + + + + )} ); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index add8603..ff72a68 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -24,7 +24,7 @@ import { IconButton, } from "@chakra-ui/react"; import { CloseIcon, HamburgerIcon } from "@chakra-ui/icons"; -import Editor from "../components/Editor"; +import Editor, { MODES, SupportedModes } from "../components/Editor"; import Diagram from "../components/Diagram/Diagram"; import { compressToEncodedURIComponent, @@ -93,6 +93,14 @@ export default function Home() { const [invalidMarkers, setValidity] = useState([]); const [showPanel, setShowPanel] = useState(false); + const [mode, setMode] = useState(MODES.DEFINITION); + + const isDefinitionMode = mode === MODES.DEFINITION; + + const editorContents = isDefinitionMode + ? editorStore.definition + : editorStore.schmea; + useEffect(() => { if (bootstrapped.current) return; bootstrapped.current = true; @@ -115,11 +123,11 @@ export default function Home() { }, [editorStore]); useEffect(() => { - if (definition) { + if (isDefinitionMode && definition) { const v = compressToEncodedURIComponent(JSON.stringify(definition)); router.push(`/?d=${v}`); } - }, [definition, router]); + }, [isDefinitionMode, definition, router]); function handleEditorValidate(markers: any[]) { const errors = markers.filter( @@ -180,18 +188,44 @@ export default function Home() { - + {showPanel && } + { + setMode(index === 0 ? MODES.DEFINITION : MODES.INPUT_SCHEMA); + }} + > + + Definiton + Input Schema + + { + if (isDefinitionMode) { + editorStore.replaceDefinitionFromString(value); + } else { + editorStore.replaceSchemaFromString(value); + } + }} onValidate={handleEditorValidate} theme="vs-dark" - settings={{ enableExperimentalValidation: true }} + settings={{ enableExperimentalValidation: true, mode }} + path={ + mode === MODES.DEFINITION + ? "definition.json" + : "input-schema.json" + } /> - Diagram + Definiton Diagram + {/* Input Schema UI */} Documentation @@ -238,6 +273,7 @@ export default function Home() { )} + {/* */} | undefined; definition: FlowDefinition | undefined; replace: (def?: FlowDefinition) => void; + replaceSchemaFromString: (s?: string) => void; replaceDefinitionFromString: (s?: string) => void; /** * Attempt to restore the definition from local storage. @@ -26,7 +28,14 @@ type EditorState = { const DEFINITION_LOCAL_STORAGE_KEY = "definition"; export const useEditorStore = create((set, get) => ({ + schmea: undefined, definition: undefined, + replaceSchemaFromString(s?: string) { + try { + const v = s ? JSON.parse(s) : undefined; + set({ schmea: v }); + } catch {} + }, replaceDefinitionFromString(s?: string) { try { const v = s ? JSON.parse(s) : undefined; From 4cf337e917a194b76fad065f83af3adef21262a5 Mon Sep 17 00:00:00 2001 From: Joe Bottigliero <694253+jbottigliero@users.noreply.github.com> Date: Tue, 12 Nov 2024 21:38:50 -0600 Subject: [PATCH 2/3] wip:feat: Adds File System integration into the IDE --- package-lock.json | 13 +++ package.json | 2 + src/components/FileSystem/Browser.tsx | 112 ++++++++++++++++++++++++++ src/components/Panel.tsx | 17 +++- src/pages/index.tsx | 2 +- src/stores/file-system.ts | 91 +++++++++++++++++++++ 6 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 src/components/FileSystem/Browser.tsx create mode 100644 src/stores/file-system.ts diff --git a/package-lock.json b/package-lock.json index 5a807cb..33b9226 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@globus/sdk": "^4.3.1", "@monaco-editor/react": "^4.6.0", "framer-motion": "^11.11.7", + "idb-keyval": "^6.2.1", "lodash": "^4.17.21", "lz-string": "^1.5.0", "next": "14.2.15", @@ -30,6 +31,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/wicg-file-system-access": "^2023.10.5", "eslint": "^8", "eslint-config-next": "14.2.13", "eslint-config-prettier": "^9.1.0", @@ -2553,6 +2555,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/wicg-file-system-access": { + "version": "2023.10.5", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.5.tgz", + "integrity": "sha512-e9kZO9kCdLqT2h9Tw38oGv9UNzBBWaR1MzuAavxPcsV/7FJ3tWbU6RI3uB+yKIDPGLkGVbplS52ub0AcRLvrhA==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", @@ -4563,6 +4571,11 @@ "react-is": "^16.7.0" } }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", diff --git a/package.json b/package.json index f4237a4..6c820e4 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@globus/sdk": "^4.3.1", "@monaco-editor/react": "^4.6.0", "framer-motion": "^11.11.7", + "idb-keyval": "^6.2.1", "lodash": "^4.17.21", "lz-string": "^1.5.0", "next": "14.2.15", @@ -31,6 +32,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/wicg-file-system-access": "^2023.10.5", "eslint": "^8", "eslint-config-next": "14.2.13", "eslint-config-prettier": "^9.1.0", diff --git a/src/components/FileSystem/Browser.tsx b/src/components/FileSystem/Browser.tsx new file mode 100644 index 0000000..60361c3 --- /dev/null +++ b/src/components/FileSystem/Browser.tsx @@ -0,0 +1,112 @@ +import React from "react"; +import { Box, Button, ButtonGroup, Center } from "@chakra-ui/react"; +import { MinusIcon, PlusSquareIcon } from "@chakra-ui/icons"; +import { useFileSystem } from "@/stores/file-system"; +import { useEditorStore } from "@/stores/editor"; + +function File({ entry }) { + const editorStore = useEditorStore(); + return ( + { + const file = await entry.handle.getFile(); + const contents = await file.text(); + editorStore.replaceDefinitionFromString(contents); + }} + p={2} + _hover={{ + bg: "gray.600", + cursor: "pointer", + color: "white", + }} + > + {"-".repeat(entry.path.split("/").length)} + {entry.handle.name} + + ); +} + +function Directory({ entry }) { + const fileSystem = useFileSystem(); + return ( + { + const entries = entry.handle.values(); + for await (const e of entries) { + await fileSystem.addEntry({ + handle: e, + path: `${entry.handle.name}/`, + }); + } + }} + p={2} + _hover={{ + bg: "gray.600", + cursor: "pointer", + color: "white", + }} + > + + {entry.handle.name} + + ); +} + +export function FileSystemBrowser() { + const editorStore = useEditorStore(); + const fileSystem = useFileSystem(); + + return ( + <> +
+ + + + +
+ + {fileSystem.entries.map((e, i) => ( + + {e.handle.kind === "file" ? ( + + ) : ( + + )} + + ))} + + + ); +} diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx index 9dc1b1c..10d87cb 100644 --- a/src/components/Panel.tsx +++ b/src/components/Panel.tsx @@ -14,6 +14,8 @@ import { useEffect, useState } from "react"; import { FlowDefinition } from "@/pages"; import { useEditorStore } from "@/stores/editor"; +import { FileSystemBrowser } from "./FileSystem/Browser"; + export default function Panel() { const auth = useGlobusAuth(); const [userFlows, setFlows] = useState([]); @@ -54,7 +56,20 @@ export default function Panel() { - Your Flows + File System + + + + + + + + + + + + + Your Published Flows diff --git a/src/pages/index.tsx b/src/pages/index.tsx index ff72a68..55d34f2 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -80,7 +80,7 @@ const LAYOUT = { /** * Feature flag to enable/disable the "Panel" component. */ -const ENABLE_PANEL = false; +const ENABLE_PANEL = true; export default function Home() { const router = useRouter(); diff --git a/src/stores/file-system.ts b/src/stores/file-system.ts new file mode 100644 index 0000000..130d637 --- /dev/null +++ b/src/stores/file-system.ts @@ -0,0 +1,91 @@ +import { create } from "zustand"; +import { persist, PersistStorage } from "zustand/middleware"; +import { get, set, del } from "idb-keyval"; + +type SupportedHandles = FileSystemDirectoryHandle | FileSystemFileHandle; + +type Entry = { + path?: string; + handle: SupportedHandles; +}; + +type State = { + handles: SupportedHandles[]; + entries: Entry[]; +}; + +const storage: PersistStorage = { + getItem: async (name) => { + return (await get(name)) || null; + }, + setItem: async (name, value) => { + await set(name, value); + }, + removeItem: async (name) => { + await del(name); + }, +}; + +type Actions = { + addEntry: (entry: Entry) => Promise; + addHandle: (handle: SupportedHandles) => Promise; +}; + +const initialState: State = { + handles: [], + entries: [], +}; + +export const useFileSystem = create()( + persist( + (set, get) => ({ + ...initialState, + addEntry: async (entry: Entry) => { + const entries = get().entries; + let isDuplicate = false; + for (const e of entries) { + if (await entry.handle.isSameEntry(e.handle)) { + isDuplicate = true; + } + } + if (isDuplicate) { + return; + } + set((state) => { + return { + ...state, + entries: [...state.entries, entry], + }; + }); + }, + addHandle: async (handle: SupportedHandles) => { + const handles = get().handles; + let isDuplicate = false; + for (const h of handles) { + if (await handle.isSameEntry(h)) { + isDuplicate = true; + } + } + if (isDuplicate) { + return; + } + set((state) => { + return { + ...state, + handles: [...state.handles, handle], + }; + }); + }, + }), + { + name: "file-system", + storage, + partialize: (state) => { + return { + handles: state.handles, + entries: state.entries, + }; + }, + }, + ), +); From 9b14553e044375171b031c432a72b446df7cd59a Mon Sep 17 00:00:00 2001 From: Joe Bottigliero <694253+jbottigliero@users.noreply.github.com> Date: Wed, 13 Nov 2024 09:41:20 -0600 Subject: [PATCH 3/3] feat: adds modification support --- src/components/Editor.tsx | 18 ++++++++++--- src/components/FileSystem/Browser.tsx | 27 +++++++++++++------ src/components/Panel.tsx | 4 ++- src/pages/index.tsx | 21 +++++++-------- src/stores/editor.ts | 38 +++++++++++++++++++++++++-- 5 files changed, 82 insertions(+), 26 deletions(-) diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 76bca29..2d41642 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -6,6 +6,7 @@ import MonacoEditor, { } from "@monaco-editor/react"; import { ValidateButton } from "./Validate"; import { Box } from "@chakra-ui/react"; +import { useEditorStore } from "@/stores/editor"; export const MODES = { DEFINITION: "DEFINITION", @@ -26,7 +27,6 @@ type InteralSettings = { function configureEditor( monaco: Monaco, settings: InteralSettings = { - mode: MODES.DEFINITION, enableExperimentalValidation: true, }, ) { @@ -53,6 +53,13 @@ function configureEditor( export default function Editor( props: { settings?: InteralSettings } & EditorProps, ) { + const editoreStore = useEditorStore(); + const settings = { + ...props.settings, + mode: isDefinitionMode(props.settings) + ? MODES.DEFINITION + : MODES.INPUT_SCHEMA, + }; return ( <> @@ -60,11 +67,16 @@ export default function Editor( defaultLanguage="json" language="json" beforeMount={(monaco) => { - configureEditor(monaco, props.settings); + configureEditor(monaco, settings); }} + path={ + editoreStore.isDefinitionMode() + ? "definition.json" + : "input-schema.json" + } {...props} /> - {isDefinitionMode(props.settings) && ( + {editoreStore.isDefinitionMode() && ( diff --git a/src/components/FileSystem/Browser.tsx b/src/components/FileSystem/Browser.tsx index 60361c3..c1e6d7d 100644 --- a/src/components/FileSystem/Browser.tsx +++ b/src/components/FileSystem/Browser.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { Box, Button, ButtonGroup, Center } from "@chakra-ui/react"; +import React, { useState } from "react"; +import { Box, Button, ButtonGroup, Center, Text } from "@chakra-ui/react"; import { MinusIcon, PlusSquareIcon } from "@chakra-ui/icons"; import { useFileSystem } from "@/stores/file-system"; import { useEditorStore } from "@/stores/editor"; @@ -9,9 +9,18 @@ function File({ entry }) { return ( { + if (editorStore.activeEditorHasModifications()) { + const result = window.confirm( + "Are you sure you want to open a new file? Your changes will be lost.", + ); + if (!result) { + return; + } + } + const file = await entry.handle.getFile(); const contents = await file.text(); - editorStore.replaceDefinitionFromString(contents); + editorStore.replaceActiveEditorFromString(contents); }} p={2} _hover={{ @@ -20,19 +29,23 @@ function File({ entry }) { color: "white", }} > - {"-".repeat(entry.path.split("/").length)} - {entry.handle.name} + {entry.handle.name} ); } function Directory({ entry }) { const fileSystem = useFileSystem(); + const [isOpen, setIsOpen] = useState(false); + return ( { const entries = entry.handle.values(); for await (const e of entries) { + if (e.name.startsWith(".")) { + continue; + } await fileSystem.addEntry({ handle: e, path: `${entry.handle.name}/`, @@ -46,16 +59,14 @@ function Directory({ entry }) { color: "white", }} > - + {isOpen ? : } {entry.handle.name} ); } export function FileSystemBrowser() { - const editorStore = useEditorStore(); const fileSystem = useFileSystem(); - return ( <>
diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx index 10d87cb..1f9069d 100644 --- a/src/components/Panel.tsx +++ b/src/components/Panel.tsx @@ -85,7 +85,9 @@ export default function Panel() { { - editorStore.replace(flow.definition); + editorStore.replaceDefinitionFromString( + JSON.stringify(flow.definition), + ); }} p={2} _hover={{ diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 55d34f2..a1d791f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -93,9 +93,7 @@ export default function Home() { const [invalidMarkers, setValidity] = useState([]); const [showPanel, setShowPanel] = useState(false); - const [mode, setMode] = useState(MODES.DEFINITION); - - const isDefinitionMode = mode === MODES.DEFINITION; + const isDefinitionMode = editorStore.isDefinitionMode(); const editorContents = isDefinitionMode ? editorStore.definition @@ -196,12 +194,14 @@ export default function Home() { backgroundColor="rgb(30, 30, 30)" colorScheme="yellow" onChange={(index) => { - setMode(index === 0 ? MODES.DEFINITION : MODES.INPUT_SCHEMA); + editorStore.setMode( + index === 0 ? MODES.DEFINITION : MODES.INPUT_SCHEMA, + ); }} > - Definiton - Input Schema + Definiton{editorStore.definitionModified && " *"} + Input Schema{editorStore.schemaModified && " *"} { if (isDefinitionMode) { + editorStore.setDefinitionModified(true); editorStore.replaceDefinitionFromString(value); } else { + editorStore.setSchemaModified(true); editorStore.replaceSchemaFromString(value); } }} onValidate={handleEditorValidate} theme="vs-dark" - settings={{ enableExperimentalValidation: true, mode }} - path={ - mode === MODES.DEFINITION - ? "definition.json" - : "input-schema.json" - } + settings={{ enableExperimentalValidation: true }} /> | undefined; definition: FlowDefinition | undefined; - replace: (def?: FlowDefinition) => void; + definitionModified: boolean; + schemaModified: boolean; + setMode: (mode: SupportedModes) => void; + isDefinitionMode: () => boolean; + setDefinitionModified: (modified: boolean) => void; + setSchemaModified: (modified: boolean) => void; + /** + * Replace the active editor with the provided string. + */ + replaceActiveEditorFromString: (s?: string) => void; replaceSchemaFromString: (s?: string) => void; replaceDefinitionFromString: (s?: string) => void; /** @@ -28,8 +42,29 @@ type EditorState = { const DEFINITION_LOCAL_STORAGE_KEY = "definition"; export const useEditorStore = create((set, get) => ({ + mode: MODES.DEFINITION, schmea: undefined, definition: undefined, + definitionModified: false, + schemaModified: false, + setDefinitionModified: (modified: boolean) => + set({ definitionModified: modified }), + setSchemaModified: (modified: boolean) => set({ schemaModified: modified }), + setMode: (mode: SupportedModes) => set({ mode }), + isDefinitionMode: () => get().mode === MODES.DEFINITION, + activeEditorHasModifications: () => { + if (get().isDefinitionMode()) { + return get().definitionModified; + } + return get().schemaModified; + }, + replaceActiveEditorFromString: (s?: string) => { + if (get().isDefinitionMode()) { + get().replaceDefinitionFromString(s); + } else { + get().replaceSchemaFromString(s); + } + }, replaceSchemaFromString(s?: string) { try { const v = s ? JSON.parse(s) : undefined; @@ -42,7 +77,6 @@ export const useEditorStore = create((set, get) => ({ set({ definition: v }); } catch {} }, - replace: (def = undefined) => set({ definition: def }), preserve: () => { const def = get().definition; if (!def) return;