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;