From d2c9d95379dd51df58469b1ac89119195df0779e Mon Sep 17 00:00:00 2001 From: c0rtexR <180119760+c0rtexR@users.noreply.github.com> Date: Thu, 17 Oct 2024 01:28:23 +0200 Subject: [PATCH 01/11] feat(editor): added Monaco editor with a dark theme and basic configuration view for request. --- packages/editor/package.json | 1 + .../src/components/MonacoEditorWrapper.tsx | 43 + packages/editor/src/components/RightPanel.tsx | 6 + .../components/panelViews/RequestSetup.tsx | 249 +++ packages/editor/src/index.tsx | 2 +- packages/editor/src/themes/dark.json | 1933 +++++++++++++++++ packages/editor/src/workflows/initial.ts | 13 + packages/editor/tsconfig.json | 2 +- packages/shared/package.json | 1 + .../shared/src/ui/components/ui/accordion.tsx | 56 + packages/shared/src/ui/index.ts | 24 + pnpm-lock.yaml | 48 +- tsconfig.json | 1 + 13 files changed, 2373 insertions(+), 6 deletions(-) create mode 100644 packages/editor/src/components/MonacoEditorWrapper.tsx create mode 100644 packages/editor/src/components/panelViews/RequestSetup.tsx create mode 100644 packages/editor/src/themes/dark.json create mode 100644 packages/shared/src/ui/components/ui/accordion.tsx create mode 100644 packages/shared/src/ui/index.ts diff --git a/packages/editor/package.json b/packages/editor/package.json index 7f7cdca..67ff98f 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -15,6 +15,7 @@ "@data-river/blocks": "workspace:*", "@data-river/execution-engine": "workspace:*", "@data-river/shared": "workspace:*", + "@monaco-editor/react": "^4.6.0", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.1.0", diff --git a/packages/editor/src/components/MonacoEditorWrapper.tsx b/packages/editor/src/components/MonacoEditorWrapper.tsx new file mode 100644 index 0000000..2a204ef --- /dev/null +++ b/packages/editor/src/components/MonacoEditorWrapper.tsx @@ -0,0 +1,43 @@ +import { Editor, EditorProps, OnMount } from "@monaco-editor/react"; +import { MemoExoticComponent, useCallback, useEffect, useState } from "react"; +import { editor } from "monaco-editor"; +import darkTheme from "../themes/dark.json"; + +const MonacoEditorWrapper = (props: EditorProps) => { + const [MonacoEditor, setMonacoEditor] = useState< + MemoExoticComponent + >(null as unknown as MemoExoticComponent); + + useEffect(() => { + import("@monaco-editor/react").then((module) => { + setMonacoEditor( + () => module.default as unknown as MemoExoticComponent, + ); + }); + }, []); + + const handleEditorDidMount: OnMount = useCallback((editor, monaco) => { + const customTheme: editor.IStandaloneThemeData = { + base: "vs-dark", + inherit: true, + colors: darkTheme.colors, + rules: [], + }; + + monaco.editor.defineTheme("customTheme", customTheme); + monaco.editor.setTheme("customTheme"); + editor.updateOptions({ theme: "customTheme" }); + }, []); + + if (!MonacoEditor) return
Loading Editor...
; + + return ( + + ); +}; + +export default MonacoEditorWrapper; diff --git a/packages/editor/src/components/RightPanel.tsx b/packages/editor/src/components/RightPanel.tsx index 3f06be9..989287c 100644 --- a/packages/editor/src/components/RightPanel.tsx +++ b/packages/editor/src/components/RightPanel.tsx @@ -8,6 +8,7 @@ import LogicNodePanelView from "./panelViews/LogicNodePanelView"; import { ICondition } from "@data-river/shared/interfaces/ICondition"; import { Button } from "@data-river/shared/ui/components/ui/button"; import { toggleRightPanelVisible } from "@/store"; +import RequestSetup from "./panelViews/RequestSetup"; const RightPanel = () => { const { isRightPanelVisible } = useLayoutState(); const { nodes, selectedNodeId } = useReactFlowState(); @@ -48,6 +49,11 @@ const RightPanel = () => { inputs={selectedNode.data.inputs || {}} /> ); + + case "request@0.1": + return ( + + ); // Add cases for other node types here default: return
No settings available for this node type.
; diff --git a/packages/editor/src/components/panelViews/RequestSetup.tsx b/packages/editor/src/components/panelViews/RequestSetup.tsx new file mode 100644 index 0000000..7aa433f --- /dev/null +++ b/packages/editor/src/components/panelViews/RequestSetup.tsx @@ -0,0 +1,249 @@ +import { useState, useEffect } from "react"; +import { useForm, Controller } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useDispatch } from "react-redux"; +import { z } from "zod"; +import { PlusCircle, Trash2 } from "lucide-react"; +import { + Select, + Input, + Button, + Label, + SelectTrigger, + SelectItem, + SelectContent, + SelectValue, +} from "@data-river/shared/ui"; +import MonacoEditorWrapper from "../MonacoEditorWrapper"; + +const HeaderSchema = z.object({ + key: z.string().nonempty("Header key is required"), + value: z.string().nonempty("Header value is required"), +}); + +const RequestFormSchema = z.object({ + httpMethod: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]), + route: z.string().url("Invalid URL"), + headers: z.array(HeaderSchema).optional(), + body: z + .string() + .optional() + .refine( + (value) => { + if (!value) return true; + try { + JSON.parse(value); + return true; + } catch { + return false; + } + }, + { message: "Invalid JSON format" }, + ), +}); + +type FormData = z.infer; + +export default function RequestBlock() { + const dispatch = useDispatch(); + const [isOpen, setIsOpen] = useState(false); + const [jsonError, setJsonError] = useState(null); + const { + control, + register, + handleSubmit, + formState: { errors }, + watch, + setValue, + } = useForm({ + resolver: zodResolver(RequestFormSchema), + defaultValues: { + httpMethod: "GET", + route: "https://", + headers: [ + { key: "Content-Type", value: "application/json" }, + { key: "Accept", value: "application/json" }, + ], + body: `{ + +}`, + }, + }); + + const onSubmit = (data: FormData) => { + // dispatch(updateRequestBlock(data)); + setIsOpen(false); + }; + + const httpMethod = watch("httpMethod"); + const route = watch("route"); + const body = watch("body"); + + useEffect(() => { + if (body) { + try { + JSON.parse(body); + setJsonError(null); + } catch (error) { + setJsonError("Invalid JSON format"); + } + } else { + setJsonError(null); + } + }, [body]); + + const addHeader = () => { + const headers = watch("headers") || []; + headers.push({ key: "", value: "" }); + setValue("headers", headers); + }; + + const removeHeader = (index: number) => { + const headers = watch("headers") || []; + headers.splice(index, 1); + setValue("headers", headers); + }; + + const handleEditorChange = (value: string | undefined) => { + setValue("body", value || "{}"); + }; + + return ( +
+
+
+ + {httpMethod} + + {route || "/path"} +
+ +
+
+
+
+ + ( + + )} + /> +
+
+ + + {errors.route && ( +

+ {errors.route.message} +

+ )} +
+
+ +
+ + {watch("headers")?.map((_, index) => ( +
+ + + +
+ ))} + +
+ +
+ +
+ +
+ {jsonError && ( +

{jsonError}

+ )} +
+
+ + +
+
+
+ ); +} diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx index 5fd541c..0f2b781 100644 --- a/packages/editor/src/index.tsx +++ b/packages/editor/src/index.tsx @@ -43,7 +43,7 @@ const Editor = () => { const footerRef = useRef(null); const rightSidebarRef = useRef(null); const debouncedSetBottomPanelSize = useMemo( - () => _.debounce((size: number) => dispatch(setBottomPanelSize(size)), 100), + () => _.debounce((size: number) => dispatch(setBottomPanelSize(size)), 1), [dispatch], ); diff --git a/packages/editor/src/themes/dark.json b/packages/editor/src/themes/dark.json new file mode 100644 index 0000000..c4583c0 --- /dev/null +++ b/packages/editor/src/themes/dark.json @@ -0,0 +1,1933 @@ +{ + "name": "Data-river dark", + "colors": { + "activityBar.activeBackground": "#0a0a0a", + "activityBar.background": "#0a0a0a", + "activityBar.border": "#383838", + "activityBar.foreground": "#e3e1e3", + "activityBar.inactiveForeground": "#7a797a", + "activityBarBadge.background": "#228df2", + "activityBarBadge.foreground": "#d6d6dd", + "badge.background": "#228df2", + "badge.foreground": "#d6d6dd", + "breadcrumb.activeSelectionForeground": "#d6d6dd", + "breadcrumb.background": "#0a0a0a", + "breadcrumb.focusForeground": "#d6d6dd", + "breadcrumb.foreground": "#a6a6a6", + "button.background": "#228df2", + "button.foreground": "#e6e6ed", + "button.hoverBackground": "#359dff", + "button.secondaryBackground": "#1d1d1d", + "button.secondaryForeground": "#d6d6dd", + "button.secondaryHoverBackground": "#303030", + "checkbox.background": "#1d1d1d", + "checkbox.border": "#4f4f4f", + "checkbox.foreground": "#d6d6dd", + "commandCenter.activeBackground": "#1d1d1d", + "commandCenter.background": "#292929", + "commandCenter.foreground": "#c1c1c1", + "debugExceptionWidget.background": "#1d1d1d", + "debugExceptionWidget.border": "#383838", + "debugToolBar.background": "#343334", + "debugToolBar.border": "#383838", + "diffEditor.border": "#383838", + "diffEditor.insertedTextBackground": "#83d6c530", + "diffEditor.insertedTextBorder": "#d6d6dd00", + "diffEditor.removedTextBackground": "#f14c4c30", + "diffEditor.removedTextBorder": "#d6d6dd00", + "dropdown.background": "#1d1d1d", + "dropdown.border": "#383838", + "dropdown.foreground": "#d6d6dd", + "editor.background": "#0a0a0a", + "editor.findMatchBackground": "#163764", + "editor.findMatchBorder": "#00000000", + "editor.findMatchHighlightBackground": "#7c511a", + "editor.findMatchHighlightBorder": "#d7d7dd02", + "editor.findRangeHighlightBackground": "#1d1d1d", + "editor.findRangeHighlightBorder": "#d6d6dd00", + "editor.foldBackground": "#1d1d1d", + "editor.foreground": "#d6d6dd", + "editor.hoverHighlightBackground": "#5b51ec70", + "editor.inactiveSelectionBackground": "#363636", + "editor.lineHighlightBackground": "#292929", + "editor.lineHighlightBorder": "#d6d6dd00", + "editor.rangeHighlightBackground": "#1d1d1d", + "editor.rangeHighlightBorder": "#38383800", + "editor.selectionBackground": "#163761", + "editor.selectionHighlightBackground": "#16376170", + "editor.selectionHighlightBorder": "#d6d6dd00", + "editor.wordHighlightBackground": "#ff000000", + "editor.wordHighlightBorder": "#d6d6dd00", + "editor.wordHighlightStrongBackground": "#16376170", + "editor.wordHighlightStrongBorder": "#d6d6dd00", + "editorBracketMatch.background": "#163761", + "editorBracketMatch.border": "#d6d6dd00", + "editorCodeLens.foreground": "#d6d6dd", + "editorCursor.background": "#0a0a0a", + "editorCursor.foreground": "#d6d6dd", + "editorError.background": "#b73a3400", + "editorError.border": "#d6d6dd00", + "editorError.foreground": "#f14c4c", + "editorGroup.border": "#383838", + "editorGroup.emptyBackground": "#0a0a0a", + "editorGroupHeader.border": "#d6d6dd00", + "editorGroupHeader.tabsBackground": "#292929", + "editorGroupHeader.tabsBorder": "#d6d6dd00", + "editorGutter.addedBackground": "#15ac91", + "editorGutter.background": "#0a0a0a", + "editorGutter.commentRangeForeground": "#d6d6dd", + "editorGutter.deletedBackground": "#f14c4c", + "editorGutter.foldingControlForeground": "#d6d6dd", + "editorGutter.modifiedBackground": "#e5b95c", + "editorHoverWidget.background": "#1d1d1d", + "editorHoverWidget.border": "#383838", + "editorHoverWidget.foreground": "#d6d6dd", + "editorIndentGuide.activeBackground": "#737377", + "editorIndentGuide.background": "#383838", + "editorInfo.background": "#d6d6dd00", + "editorInfo.border": "#d6d6dd00", + "editorInfo.foreground": "#228df2", + "editorInlayHint.background": "#2b2b2b", + "editorInlayHint.foreground": "#838383", + "editorLineNumber.activeForeground": "#c2c2c2", + "editorLineNumber.foreground": "#535353", + "editorLink.activeForeground": "#228df2", + "editorMarkerNavigation.background": "#2d2d30", + "editorMarkerNavigationError.background": "#f14c4c", + "editorMarkerNavigationInfo.background": "#4c9df3", + "editorMarkerNavigationWarning.background": "#e5b95c", + "editorOverviewRuler.background": "#25252500", + "editorOverviewRuler.border": "#7f7f7f4d", + "editorRuler.foreground": "#383838", + "editorSuggestWidget.background": "#1d1d1d", + "editorSuggestWidget.border": "#383838", + "editorSuggestWidget.foreground": "#d6d6dd", + "editorSuggestWidget.highlightForeground": "#d6d6dd", + "editorSuggestWidget.selectedBackground": "#163761", + "editorWarning.background": "#a9904000", + "editorWarning.border": "#d6d6dd00", + "editorWarning.foreground": "#ea7620", + "editorWhitespace.foreground": "#737373", + "editorWidget.background": "#292929", + "editorWidget.foreground": "#d6d6dd", + "editorWidget.resizeBorder": "#ea7620", + "focusBorder": "#d6d6dd00", + "foreground": "#d6d6dd", + "gitDecoration.addedResourceForeground": "#5a964d", + "gitDecoration.conflictingResourceForeground": "#aaa0fa", + "gitDecoration.deletedResourceForeground": "#f14c4c", + "gitDecoration.ignoredResourceForeground": "#666666", + "gitDecoration.modifiedResourceForeground": "#1981ef", + "gitDecoration.stageDeletedResourceForeground": "#f14c4c", + "gitDecoration.stageModifiedResourceForeground": "#1981ef", + "gitDecoration.submoduleResourceForeground": "#1981ef", + "gitDecoration.untrackedResourceForeground": "#3ea17f", + "icon.foreground": "#d6d6dd", + "input.background": "#212121", + "input.border": "#ffffff1e", + "input.foreground": "#d6d6dd", + "input.placeholderForeground": "#7b7b7b", + "inputOption.activeBackground": "#de3c72", + "inputOption.activeBorder": "#d6d6dd00", + "inputOption.activeForeground": "#d6d6dd", + "list.activeSelectionBackground": "#163761", + "list.activeSelectionForeground": "#d6d6dd", + "list.dropBackground": "#d6d6dd00", + "list.focusBackground": "#5b51ec", + "list.focusForeground": "#d6d6dd", + "list.highlightForeground": "#d6d6dd", + "list.hoverBackground": "#2a282a", + "list.hoverForeground": "#d6d6dd", + "list.inactiveSelectionBackground": "#3c3b3c", + "list.inactiveSelectionForeground": "#d6d6dd", + "listFilterWidget.background": "#5b51ec", + "listFilterWidget.noMatchesOutline": "#f14c4c", + "listFilterWidget.outline": "#00000000", + "menu.background": "#292929", + "menu.border": "#000000", + "menu.foreground": "#d6d6dd", + "menu.selectionBackground": "#194176", + "menu.selectionBorder": "#00000000", + "menu.selectionForeground": "#d6d6dd", + "menu.separatorBackground": "#3e3e3e", + "menubar.selectionBackground": "#d6d6dd20", + "menubar.selectionBorder": "#d6d6dd00", + "menubar.selectionForeground": "#d6d6dd", + "merge.commonContentBackground": "#1d1d1d", + "merge.commonHeaderBackground": "#323232", + "merge.currentContentBackground": "#1a493d", + "merge.currentHeaderBackground": "#83d6c595", + "merge.incomingContentBackground": "#28384b", + "merge.incomingHeaderBackground": "#395f8f", + "minimap.background": "#0a0a0a", + "minimap.errorHighlight": "#f14c4c", + "minimap.findMatchHighlight": "#15ac9170", + "minimap.selectionHighlight": "#363636", + "minimap.warningHighlight": "#ea7620", + "minimapGutter.addedBackground": "#15ac91", + "minimapGutter.deletedBackground": "#f14c4c", + "minimapGutter.modifiedBackground": "#e5b95c", + "notebook.focusedCellBorder": "#15ac91", + "notebook.focusedEditorBorder": "#15ac9177", + "notificationCenter.border": "#2c2c2c", + "notificationCenterHeader.background": "#2c2c2c", + "notificationCenterHeader.foreground": "#d6d6dd", + "notificationToast.border": "#383838", + "notifications.background": "#1d1d1d", + "notifications.border": "#2c2c2c", + "notifications.foreground": "#d6d6dd", + "notificationsErrorIcon.foreground": "#f14c4c", + "notificationsInfoIcon.foreground": "#228df2", + "notificationsWarningIcon.foreground": "#ea7620", + "panel.background": "#292929", + "panel.border": "#0a0a0a", + "panelSection.border": "#383838", + "panelTitle.activeBorder": "#d6d6dd", + "panelTitle.activeForeground": "#d6d6dd", + "panelTitle.inactiveForeground": "#d6d6dd", + "peekView.border": "#383838", + "peekViewEditor.background": "#001f33", + "peekViewEditor.matchHighlightBackground": "#ea762070", + "peekViewEditor.matchHighlightBorder": "#d6d6dd00", + "peekViewEditorGutter.background": "#001f33", + "peekViewResult.background": "#1d1d1d", + "peekViewResult.fileForeground": "#d6d6dd", + "peekViewResult.lineForeground": "#d6d6dd", + "peekViewResult.matchHighlightBackground": "#ea762070", + "peekViewResult.selectionBackground": "#363636", + "peekViewResult.selectionForeground": "#d6d6dd", + "peekViewTitle.background": "#1d1d1d", + "peekViewTitleDescription.foreground": "#d6d6dd", + "peekViewTitleLabel.foreground": "#d6d6dd", + "pickerGroup.border": "#383838", + "pickerGroup.foreground": "#d6d6dd", + "progressBar.background": "#15ac91", + "scrollbar.shadow": "#d6d6dd00", + "scrollbarSlider.activeBackground": "#676767", + "scrollbarSlider.background": "#67676750", + "scrollbarSlider.hoverBackground": "#676767", + "selection.background": "#163761", + "settings.focusedRowBackground": "#d6d6dd07", + "settings.headerForeground": "#d6d6dd", + "sideBar.background": "#0a0a0a", + "sideBar.border": "#383838", + "sideBar.dropBackground": "#d6d6dd00", + "sideBar.foreground": "#d1d1d1", + "sideBarSectionHeader.background": "#0a0a0a00", + "sideBarSectionHeader.border": "#d1d1d100", + "sideBarSectionHeader.foreground": "#d1d1d1", + "sideBarTitle.foreground": "#d1d1d1", + "statusBar.background": "#0a0a0a", + "statusBar.border": "#383838", + "statusBar.debuggingBackground": "#ea7620", + "statusBar.debuggingBorder": "#d6d6dd00", + "statusBar.debuggingForeground": "#e7e7e7", + "statusBar.foreground": "#d6d6dd", + "statusBar.noFolderBackground": "#0a0a0a", + "statusBar.noFolderBorder": "#d6d6dd00", + "statusBar.noFolderForeground": "#6b6b6b", + "statusBarItem.activeBackground": "#d6d6dd25", + "statusBarItem.hoverBackground": "#d6d6dd20", + "statusBarItem.remoteBackground": "#5b51ec", + "statusBarItem.remoteForeground": "#d6d6dd", + "tab.activeBackground": "#0a0a0a", + "tab.activeBorder": "#d6d6dd00", + "tab.activeBorderTop": "#d6d6dd", + "tab.activeForeground": "#d6d6dd", + "tab.border": "#d6d6dd00", + "tab.hoverBorder": "#6d6d7071", + "tab.hoverForeground": "#d6d6dd", + "tab.inactiveBackground": "#292929", + "tab.inactiveForeground": "#d6d6dd", + "terminal.ansiBlack": "#676767", + "terminal.ansiBlue": "#4c9df3", + "terminal.ansiBrightBlack": "#676767", + "terminal.ansiBrightBlue": "#4c9df3", + "terminal.ansiBrightCyan": "#75d3ba", + "terminal.ansiBrightGreen": "#15ac91", + "terminal.ansiBrightMagenta": "#e567dc", + "terminal.ansiBrightRed": "#f14c4c", + "terminal.ansiBrightWhite": "#d6d6dd", + "terminal.ansiBrightYellow": "#e5b95c", + "terminal.ansiCyan": "#75d3ba", + "terminal.ansiGreen": "#15ac91", + "terminal.ansiMagenta": "#e567dc", + "terminal.ansiRed": "#f14c4c", + "terminal.ansiWhite": "#d6d6dd", + "terminal.ansiYellow": "#e5b95c", + "terminal.background": "#191919", + "terminal.border": "#383838", + "terminal.foreground": "#d6d6dd", + "terminal.selectionBackground": "#636262fd", + "terminalCursor.background": "#5b51ec", + "terminalCursor.foreground": "#d6d6dd", + "textLink.foreground": "#228df2", + "titleBar.activeBackground": "#292929", + "titleBar.activeForeground": "#d1d1d1", + "titleBar.border": "#383838", + "titleBar.inactiveBackground": "#3c3b3c", + "titleBar.inactiveForeground": "#cccccc99", + "tree.indentGuidesStroke": "#d6d6dd00", + "walkThrough.embeddedEditorBackground": "#00000050", + "widget.shadow": "#111111eb" + }, + "tokenColors": [ + { + "scope": "string.quoted.binary.single.python", + "settings": { + "foreground": "#A8CC7C", + "fontStyle": "" + } + }, + { + "scope": [ + "constant.language.false.cpp", + "constant.language.true.cpp" + ], + "settings": { + "foreground": "#82D2CE", + "fontStyle": "" + } + }, + { + "scope": "punctuation.definition.delayed.unison,punctuation.definition.list.begin.unison,punctuation.definition.list.end.unison,punctuation.definition.ability.begin.unison,punctuation.definition.ability.end.unison,punctuation.operator.assignment.as.unison,punctuation.separator.pipe.unison,punctuation.separator.delimiter.unison,punctuation.definition.hash.unison", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.control.directive", + "settings": { + "foreground": "#A8CC7C" + } + }, + { + "scope": "constant.other.ellipsis.python", + "settings": { + "foreground": "#D1D1D1", + "fontStyle": "" + } + }, + { + "scope": "variable.other.generic-type.haskell", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "punctuation.definition.tag", + "settings": { + "foreground": "#898989", + "fontStyle": "" + } + }, + { + "scope": "storage.type.haskell", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "support.variable.magic.python", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.separator.period.python,punctuation.separator.element.python,punctuation.parenthesis.begin.python,punctuation.parenthesis.end.python", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "variable.parameter.function.language.special.self.python", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "variable.language.this.cpp", + "settings": { + "foreground": "#82D2CE", + "fontStyle": "" + } + }, + { + "scope": "storage.modifier.lifetime.rust", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "support.function.std.rust", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "entity.name.lifetime.rust", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "variable.other.property", + "settings": { + "foreground": "#AA9BF5" + } + }, + { + "scope": "variable.language.rust", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "support.constant.edge", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "constant.other.character-class.regexp", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.quantifier.regexp", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "punctuation.definition.string.begin,punctuation.definition.string.end", + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": "variable.parameter.function", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "comment markup.link", + "settings": { + "foreground": "#6D6D6D" + } + }, + { + "scope": "markup.changed.diff", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "meta.diff.header.from-file,meta.diff.header.to-file,punctuation.definition.from-file.diff,punctuation.definition.to-file.diff", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "markup.inserted.diff", + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": "markup.deleted.diff", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "meta.function.c,meta.function.cpp", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.section.block.begin.bracket.curly.cpp,punctuation.section.block.end.bracket.curly.cpp,punctuation.terminator.statement.c,punctuation.section.block.begin.bracket.curly.c,punctuation.section.block.end.bracket.curly.c,punctuation.section.parens.begin.bracket.round.c,punctuation.section.parens.end.bracket.round.c,punctuation.section.parameters.begin.bracket.round.c,punctuation.section.parameters.end.bracket.round.c", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.separator.key-value", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.expression.import", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "support.constant.math", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "support.constant.property.math", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "variable.other.constant", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "variable.other.constant", + "settings": { + "foreground": "#AA9BF5" + } + }, + { + "scope": [ + "storage.type.annotation.java", + "storage.type.object.array.java" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "source.java", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.section.block.begin.java,punctuation.section.block.end.java,punctuation.definition.method-parameters.begin.java,punctuation.definition.method-parameters.end.java,meta.method.identifier.java,punctuation.section.method.begin.java,punctuation.section.method.end.java,punctuation.terminator.java,punctuation.section.class.begin.java,punctuation.section.class.end.java,punctuation.section.inner-class.begin.java,punctuation.section.inner-class.end.java,meta.method-call.java,punctuation.section.class.begin.bracket.curly.java,punctuation.section.class.end.bracket.curly.java,punctuation.section.method.begin.bracket.curly.java,punctuation.section.method.end.bracket.curly.java,punctuation.separator.period.java,punctuation.bracket.angle.java,punctuation.definition.annotation.java,meta.method.body.java", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "meta.method.java", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "storage.modifier.import.java,storage.type.java,storage.type.generic.java", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "keyword.operator.instanceof.java", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "meta.definition.variable.name.java", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.logical", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.bitwise", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.channel", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.css,keyword.operator.scss,keyword.operator.less", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "support.constant.color.w3c-standard-color-name.css,support.constant.color.w3c-standard-color-name.scss", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "punctuation.separator.list.comma.css", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "support.constant.color.w3c-standard-color-name.css", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "support.module.node,support.type.object.module,support.module.node", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "entity.name.type.module", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": ",meta.object-literal.key,support.variable.object.process,support.variable.object.node", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "variable.other.readwrite", + "settings": { + "foreground": "#94C1FA" + } + }, + { + "scope": "support.variable.property", + "settings": { + "foreground": "#AA9BF5" + } + }, + { + "scope": "support.constant.json", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": [ + "keyword.operator.expression.instanceof", + "keyword.operator.new", + "keyword.operator.ternary", + "keyword.operator.optional", + "keyword.operator.expression.keyof" + ], + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "support.type.object.console", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "support.variable.property.process", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "entity.name.function.js,support.function.console.js", + "settings": { + "foreground": "#EBC88D" + } + }, + { + "scope": "keyword.operator.misc.rust", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.sigil.rust", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "keyword.operator.delete", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "support.type.object.dom", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "support.variable.dom,support.variable.property.dom", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.arithmetic,keyword.operator.comparison,keyword.operator.decrement,keyword.operator.increment,keyword.operator.relational", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.assignment.c,keyword.operator.comparison.c,keyword.operator.c,keyword.operator.increment.c,keyword.operator.decrement.c,keyword.operator.bitwise.shift.c,keyword.operator.assignment.cpp,keyword.operator.comparison.cpp,keyword.operator.cpp,keyword.operator.increment.cpp,keyword.operator.decrement.cpp,keyword.operator.bitwise.shift.cpp", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "punctuation.separator.delimiter", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.separator.c,punctuation.separator.cpp", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "support.type.posix-reserved.c,support.type.posix-reserved.cpp", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.sizeof.c,keyword.operator.sizeof.cpp", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "variable.parameter.function.language.python", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "support.type.python", + "settings": { + "foreground": "#82D2CE" + } + }, + { + "scope": "keyword.operator.logical.python", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "variable.parameter.function.python", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "punctuation.definition.arguments.begin.python,punctuation.definition.arguments.end.python,punctuation.separator.arguments.python,punctuation.definition.list.begin.python,punctuation.definition.list.end.python", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "meta.function-call.generic.python", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "constant.character.format.placeholder.other.python", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "keyword.operator", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.assignment.compound", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "keyword.operator.assignment.compound.js,keyword.operator.assignment.compound.ts", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "entity.name.namespace", + "settings": { + "foreground": "#D1D1D1" + } + }, + { + "scope": "variable", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "variable.c", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "variable.language", + "settings": { + "foreground": "#C1808A" + } + }, + { + "scope": "token.variable.parameter.java", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "import.storage.java", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "token.package.keyword", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "token.package", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "entity.name.type.namespace", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "support.class, entity.name.type.class", + "settings": { + "foreground": "#87C3FF" + } + }, + { + "scope": "entity.name.class.identifier.namespace.type", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "entity.name.class", + "variable.other.class.js", + "variable.other.class.ts" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "variable.other.class.php", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "entity.name.type", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "keyword.control.directive.include.cpp", + "settings": { + "foreground": "#A8CC7C" + } + }, + { + "scope": "control.elements, keyword.operator.less", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "keyword.other.special-method", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "storage", + "settings": { + "foreground": "#82D2CE" + } + }, + { + "scope": [ + "storage.modifier.reference", + "storage.modifier.pointer" + ], + "settings": { + "foreground": "#D1D1D1", + "fontStyle": "" + } + }, + { + "scope": "token.storage", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "keyword.operator.expression.delete,keyword.operator.expression.in,keyword.operator.expression.of,keyword.operator.expression.instanceof,keyword.operator.new,keyword.operator.expression.typeof,keyword.operator.expression.void", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "token.storage.type.java", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "support.function", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "meta.property-name.css", + "settings": { + "foreground": "#87C3FF", + "fontStyle": "" + } + }, + { + "scope": "meta.tag", + "settings": { + "foreground": "#FAD075" + } + }, + { + "scope": "string", + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "constant.other.symbol", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "constant.numeric", + "settings": { + "foreground": "#EBC88D" + } + }, + { + "scope": "constant.other.color", + "settings": { + "foreground": "#EBC88D" + } + }, + { + "scope": "punctuation.definition.constant", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": [ + "entity.name.tag.template", + "entity.name.tag.script", + "entity.name.tag.style" + ], + "settings": { + "foreground": "#AF9CFF" + } + }, + { + "scope": [ + "entity.name.tag.html" + ], + "settings": { + "foreground": "#87C3FF" + } + }, + { + "scope": "meta.property-value.css", + "settings": { + "foreground": "#E394DC", + "fontStyle": "" + } + }, + { + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "entity.other.attribute-name.id", + "settings": { + "foreground": "#AAA0FA", + "fontStyle": "" + } + }, + { + "scope": "entity.other.attribute-name.class.css", + "settings": { + "foreground": "#F8C762", + "fontStyle": "" + } + }, + { + "scope": "meta.selector", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "markup.heading", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "markup.heading punctuation.definition.heading, entity.name.section", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "keyword.other.unit", + "settings": { + "foreground": "#EBC88D" + } + }, + { + "scope": "markup.bold,todo.bold", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "punctuation.definition.bold", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "markup.italic, punctuation.definition.italic,todo.emphasis", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "emphasis md", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "entity.name.section.markdown", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.definition.heading.markdown", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.definition.list.begin.markdown", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "markup.heading.setext", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.definition.bold.markdown", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "markup.inline.raw.markdown", + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": "markup.inline.raw.string.markdown", + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": "punctuation.definition.list.markdown", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "punctuation.definition.string.begin.markdown", + "punctuation.definition.string.end.markdown", + "punctuation.definition.metadata.markdown" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "beginning.punctuation.definition.list.markdown" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.definition.metadata.markdown", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "markup.underline.link.markdown,markup.underline.link.image.markdown", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "string.other.link.title.markdown,string.other.link.description.markdown", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "string.regexp", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "constant.character.escape", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.section.embedded, variable.interpolation", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.section.embedded.begin,punctuation.section.embedded.end", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "invalid.illegal", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "invalid.illegal.bad-ampersand.html", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "invalid.broken", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "invalid.deprecated", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "invalid.unimplemented", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > value.json > string.quoted.json,source.json meta.structure.array.json > value.json > string.quoted.json,source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation,source.json meta.structure.array.json > value.json > string.quoted.json > punctuation", + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > constant.language.json,source.json meta.structure.array.json > constant.language.json", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "support.type.property-name.json", + "settings": { + "foreground": "#82D2CE" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "support.other.namespace.use.php,support.other.namespace.use-as.php,support.other.namespace.php,entity.other.alias.php,meta.interface.php", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "keyword.operator.error-control.php", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "keyword.operator.type.php", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "punctuation.section.array.begin.php", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "punctuation.section.array.end.php", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "invalid.illegal.non-null-typehinted.php", + "settings": { + "foreground": "#F44747" + } + }, + { + "scope": "storage.type.php,meta.other.type.phpdoc.php,keyword.other.type.php,keyword.other.array.phpdoc.php", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "meta.function-call.php,meta.function-call.object.php,meta.function-call.static.php", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "punctuation.definition.parameters.begin.bracket.round.php,punctuation.definition.parameters.end.bracket.round.php,punctuation.separator.delimiter.php,punctuation.section.scope.begin.php,punctuation.section.scope.end.php,punctuation.terminator.expression.php,punctuation.definition.arguments.begin.bracket.round.php,punctuation.definition.arguments.end.bracket.round.php,punctuation.definition.storage-type.begin.bracket.round.php,punctuation.definition.storage-type.end.bracket.round.php,punctuation.definition.array.begin.bracket.round.php,punctuation.definition.array.end.bracket.round.php,punctuation.definition.begin.bracket.round.php,punctuation.definition.end.bracket.round.php,punctuation.definition.begin.bracket.curly.php,punctuation.definition.end.bracket.curly.php,punctuation.definition.section.switch-block.end.bracket.curly.php,punctuation.definition.section.switch-block.start.bracket.curly.php,punctuation.definition.section.switch-block.begin.bracket.curly.php,punctuation.definition.section.switch-block.end.bracket.curly.php", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "support.constant.core.rust", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "support.constant.ext.php,support.constant.std.php,support.constant.core.php,support.constant.parser-token.php", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "entity.name.goto-label.php,support.other.php", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "keyword.operator.logical.php,keyword.operator.bitwise.php,keyword.operator.arithmetic.php", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.regexp.php", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "keyword.operator.comparison.php", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "keyword.operator.heredoc.php,keyword.operator.nowdoc.php", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": "meta.function.decorator.python", + "settings": { + "foreground": "#A8CC7C" + } + }, + { + "scope": "punctuation.definition.decorator.python,entity.name.function.decorator.python", + "settings": { + "foreground": "#A8CC7C", + "fontStyle": "" + } + }, + { + "scope": "support.token.decorator.python,meta.function.decorator.identifier.python", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "function.parameter", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "function.brace", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "function.parameter.ruby, function.parameter.cs", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "constant.language.symbol.ruby", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "rgb-value", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "inline-color-decoration rgb-value", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "less rgb-value", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "selector.sass", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "support.type.primitive.ts,support.type.builtin.ts,support.type.primitive.tsx,support.type.builtin.tsx", + "settings": { + "foreground": "#82D2CE" + } + }, + { + "scope": "block.scope.end,block.scope.begin", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "storage.type.cs", + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": "entity.name.variable.local.cs", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "#F44747" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": [ + "punctuation.definition.template-expression.begin", + "punctuation.definition.template-expression.end", + "punctuation.section.embedded" + ], + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": [ + "meta.template.expression" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "keyword.operator.module" + ], + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": [ + "support.type.type.flowtype" + ], + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": [ + "support.type.primitive" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "meta.property.object" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "variable.parameter.function.js" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "keyword.other.template.begin" + ], + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": [ + "keyword.other.template.end" + ], + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": [ + "keyword.other.substitution.begin" + ], + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": [ + "keyword.other.substitution.end" + ], + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": [ + "keyword.operator.assignment" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "keyword.operator.assignment.go" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "keyword.operator.arithmetic.go", + "keyword.operator.address.go" + ], + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": [ + "entity.name.package.go" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "support.type.prelude.elm" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "support.constant.elm" + ], + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": [ + "punctuation.quasi.element" + ], + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": [ + "constant.character.entity" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "entity.other.attribute-name.pseudo-element", + "entity.other.attribute-name.pseudo-class" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "entity.global.clojure" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "meta.symbol.clojure" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "constant.keyword.clojure" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "meta.arguments.coffee", + "variable.parameter.function.coffee" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "source.ini" + ], + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": [ + "meta.scope.prerequisites.makefile" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "source.makefile" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "storage.modifier.import.groovy" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "meta.method.groovy" + ], + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": [ + "meta.definition.variable.name.groovy" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "meta.definition.class.inherited.classes.groovy" + ], + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": [ + "support.variable.semantic.hlsl" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "support.type.texture.hlsl", + "support.type.sampler.hlsl", + "support.type.object.hlsl", + "support.type.object.rw.hlsl", + "support.type.fx.hlsl", + "support.type.object.hlsl" + ], + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": [ + "text.variable", + "text.bracketed" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "support.type.swift", + "support.type.vb.asp" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "entity.name.function.xi" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "entity.name.class.xi" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "constant.character.character-class.regexp.xi" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "constant.regexp.xi" + ], + "settings": { + "foreground": "#83D6C5" + } + }, + { + "scope": [ + "keyword.control.xi" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "invalid.xi" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "beginning.punctuation.definition.quote.markdown.xi" + ], + "settings": { + "foreground": "#E394DC" + } + }, + { + "scope": [ + "beginning.punctuation.definition.list.markdown.xi" + ], + "settings": { + "foreground": "#6D6D6D" + } + }, + { + "scope": [ + "constant.character.xi" + ], + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": [ + "accent.xi" + ], + "settings": { + "foreground": "#AAA0FA" + } + }, + { + "scope": [ + "wikiword.xi" + ], + "settings": { + "foreground": "#F8C762" + } + }, + { + "scope": [ + "constant.other.color.rgb-value.xi" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "punctuation.definition.tag.xi" + ], + "settings": { + "foreground": "#6D6D6D" + } + }, + { + "scope": [ + "entity.name.label.cs", + "entity.name.scope-resolution.function.call", + "entity.name.scope-resolution.function.definition" + ], + "settings": { + "foreground": "#EFB080" + } + }, + { + "scope": [ + "entity.name.label.cs", + "markup.heading.setext.1.markdown", + "markup.heading.setext.2.markdown" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + " meta.brace.square" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "comment, punctuation.definition.comment", + "settings": { + "foreground": "#6D6D6D", + "fontStyle": "italic" + } + }, + { + "scope": "markup.quote.markdown", + "settings": { + "foreground": "#6D6D6D" + } + }, + { + "scope": "punctuation.definition.block.sequence.item.yaml", + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": [ + "constant.language.symbol.elixir" + ], + "settings": { + "foreground": "#D6D6DD" + } + }, + { + "scope": "entity.other.attribute-name.js,entity.other.attribute-name.ts,entity.other.attribute-name.jsx,entity.other.attribute-name.tsx,variable.parameter,variable.language.super", + "settings": { + "fontStyle": "italic" + } + }, + { + "scope": "comment.line.double-slash,comment.block.documentation", + "settings": { + "fontStyle": "italic" + } + }, + { + "scope": "keyword.control.import.python,keyword.control.flow.python", + "settings": { + "fontStyle": "italic" + } + }, + { + "scope": "markup.italic.markdown", + "settings": { + "fontStyle": "italic" + } + } + ] +} \ No newline at end of file diff --git a/packages/editor/src/workflows/initial.ts b/packages/editor/src/workflows/initial.ts index 2a4b953..890e24a 100644 --- a/packages/editor/src/workflows/initial.ts +++ b/packages/editor/src/workflows/initial.ts @@ -43,6 +43,19 @@ const initialNodes: Node[] = [ ], }, }, + { + id: "99", + type: "custom", + position: { x: 400, y: 300 }, + data: { + block: "request@0.1", + label: "Request", + color: "rgb(234 179 8)", + sourceHandle: true, + targetHandle: true, + icon: "Square", + }, + }, { id: "3", type: "custom", diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json index 2e2e9c5..ccd63c1 100644 --- a/packages/editor/tsconfig.json +++ b/packages/editor/tsconfig.json @@ -26,7 +26,7 @@ "@data-river/blocks/*": ["../blocks/src/*"] } }, - "include": ["src/**/*.ts", "src/**/*.tsx"], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/themes/*.json"], "references": [ { "path": "../shared" }, { "path": "../execution-engine" }, diff --git a/packages/shared/package.json b/packages/shared/package.json index 8237b86..6318380 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -12,6 +12,7 @@ "tailwindcss-animate": "^1.0.7" }, "dependencies": { + "@radix-ui/react-accordion": "^1.2.1", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.1.0", diff --git a/packages/shared/src/ui/components/ui/accordion.tsx b/packages/shared/src/ui/components/ui/accordion.tsx new file mode 100644 index 0000000..ad41986 --- /dev/null +++ b/packages/shared/src/ui/components/ui/accordion.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; + +import { cn } from "@/ui/utils"; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)); + +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/packages/shared/src/ui/index.ts b/packages/shared/src/ui/index.ts new file mode 100644 index 0000000..1159519 --- /dev/null +++ b/packages/shared/src/ui/index.ts @@ -0,0 +1,24 @@ +// Re-export components +export * from "./components/ui/badge"; +export * from "./components/ui/button"; +export * from "./components/ui/card"; +export * from "./components/ui/dropdown-menu"; +export * from "./components/ui/input"; +export * from "./components/ui/label"; +export * from "./components/ui/resizable"; +export * from "./components/ui/scroll-area"; +export * from "./components/ui/select"; +export * from "./components/ui/separator"; +export * from "./components/ui/sheet"; +export * from "./components/ui/tabs"; +export * from "./components/ui/textarea"; +export * from "./components/ui/toast"; +export * from "./components/ui/toaster"; +export * from "./components/ui/tooltip"; +export * from "./components/ui/accordion"; + +// Re-export utilities +export * from "./utils"; + +// Re-export hooks +export * from "./hooks/use-toast"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d022f94..58c9458 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -362,6 +362,9 @@ importers: '@data-river/shared': specifier: workspace:* version: link:../shared + '@monaco-editor/react': + specifier: ^4.6.0 + version: 4.6.0(monaco-editor@0.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -503,6 +506,9 @@ importers: packages/shared: dependencies: + '@radix-ui/react-accordion': + specifier: ^1.2.1 + version: 1.2.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1518,6 +1524,18 @@ packages: '@mdx-js/mdx@2.3.0': resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} + '@monaco-editor/loader@1.4.0': + resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} + peerDependencies: + monaco-editor: '>= 0.21.0 < 1' + + '@monaco-editor/react@4.6.0': + resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@napi-rs/wasm-runtime@0.2.4': resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} @@ -5836,6 +5854,9 @@ packages: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} + monaco-editor@0.52.0: + resolution: {integrity: sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==} + morgan@1.10.0: resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} engines: {node: '>= 0.8.0'} @@ -7014,6 +7035,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -8579,6 +8603,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@monaco-editor/loader@1.4.0(monaco-editor@0.52.0)': + dependencies: + monaco-editor: 0.52.0 + state-local: 1.0.7 + + '@monaco-editor/react@4.6.0(monaco-editor@0.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@monaco-editor/loader': 1.4.0(monaco-editor@0.52.0) + monaco-editor: 0.52.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@napi-rs/wasm-runtime@0.2.4': dependencies: '@emnapi/core': 1.2.0 @@ -11740,7 +11776,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 9.12.0(jiti@1.21.6) - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.12.0(jiti@1.21.6)))(eslint@9.12.0(jiti@1.21.6)) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.12.0(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -11753,7 +11789,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.12.0(jiti@1.21.6)))(eslint@9.12.0(jiti@1.21.6)): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.12.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: @@ -11763,7 +11799,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.12.0(jiti@1.21.6)))(eslint@9.12.0(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.12.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: @@ -11785,7 +11821,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.12.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.12.0(jiti@1.21.6)))(eslint@9.12.0(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.9.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.12.0(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -13966,6 +14002,8 @@ snapshots: modify-values@1.0.1: {} + monaco-editor@0.52.0: {} + morgan@1.10.0: dependencies: basic-auth: 2.0.1 @@ -15284,6 +15322,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + state-local@1.0.7: {} + statuses@2.0.1: {} stop-iteration-iterator@1.0.0: diff --git a/tsconfig.json b/tsconfig.json index 379c477..7bd834f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "baseUrl": ".", + "resolveJsonModule": true, "paths": { "~/*": ["packages/www/app/*"], "@data-river/shared/*": ["packages/shared/src/*"], From 18c16777887587e42e76a4761fe6aee97b675ed9 Mon Sep 17 00:00:00 2001 From: c0rtexR <180119760+c0rtexR@users.noreply.github.com> Date: Thu, 17 Oct 2024 01:45:42 +0200 Subject: [PATCH 02/11] feat: add save button to headers --- .../components/panelViews/RequestSetup.tsx | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/packages/editor/src/components/panelViews/RequestSetup.tsx b/packages/editor/src/components/panelViews/RequestSetup.tsx index 7aa433f..6507096 100644 --- a/packages/editor/src/components/panelViews/RequestSetup.tsx +++ b/packages/editor/src/components/panelViews/RequestSetup.tsx @@ -70,6 +70,11 @@ export default function RequestBlock() { }, }); + const saveHeaders = () => { + const data = watch(); + onSubmit(data); + }; + const onSubmit = (data: FormData) => { // dispatch(updateRequestBlock(data)); setIsOpen(false); @@ -109,7 +114,7 @@ export default function RequestBlock() { }; return ( -
+
))} - +
+ + + +
From e72fb8c55cf5cf4d76bb1c85eab65a15662818cd Mon Sep 17 00:00:00 2001 From: c0rtexR <180119760+c0rtexR@users.noreply.github.com> Date: Thu, 17 Oct 2024 01:46:32 +0200 Subject: [PATCH 03/11] chore: remove random debugger span from flow debugging panning. --- packages/editor/src/components/Flow.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/editor/src/components/Flow.tsx b/packages/editor/src/components/Flow.tsx index bab7ae2..029d7f5 100644 --- a/packages/editor/src/components/Flow.tsx +++ b/packages/editor/src/components/Flow.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import ReactFlow, { ConnectionMode, Background, @@ -11,7 +11,6 @@ import "reactflow/dist/style.css"; import { useReactFlowState } from "@hooks/useReactFlowState"; import { useReactFlowHooks } from "@hooks/useReactFlowHooks"; import { useReactFlowEventHandlers } from "@hooks/useReactFlowEventHandlers"; -import useEditorState from "@/hooks/useEditorState"; import CustomNode from "./CustomNode"; import CustomNodeInfo from "./CustomNodeInfo"; @@ -30,7 +29,6 @@ const FlowChart: React.FC = () => { const { lightTheme, nodes, edges } = useReactFlowState(); const eventHandlers = useReactFlowEventHandlers(); useReactFlowHooks(); - const isPanning = useEditorState((state) => state.isPanning); return (
{ - - {isPanning ? "true" : "false"} -
); }; From d679822f30233bfe6cd94eeb8715d0d69da98842 Mon Sep 17 00:00:00 2001 From: c0rtexR <180119760+c0rtexR@users.noreply.github.com> Date: Thu, 17 Oct 2024 06:46:56 +0200 Subject: [PATCH 04/11] Make better interactions in request settings sidebar. --- packages/blocks/src/block/index.ts | 19 +- packages/blocks/src/blockFactory/index.ts | 14 +- packages/blocks/src/endBlock/index.ts | 5 +- packages/blocks/src/index.ts | 1 + packages/blocks/src/outputBlock/index.ts | 5 +- packages/blocks/src/plugins/RequestBlock.ts | 44 --- packages/blocks/src/requestBlock/index.ts | 77 +++++ packages/client/vite.config.ts | 3 +- packages/editor/src/App.tsx | 12 + packages/editor/src/blocks/index.ts | 2 + packages/editor/src/blocks/requestNode.ts | 33 ++ packages/editor/src/components/RightPanel.tsx | 7 +- .../nodeComponents/InputWithLabel.tsx | 61 +++- .../nodeComponents/NodeControls.tsx | 11 + .../nodeComponents/RequestBlock/index.tsx | 47 +++ .../nodeComponents/TextAreaWithLabel.tsx | 2 +- .../panelViews/ConditionsSection.tsx | 6 - .../panelViews/Request/AddQueryParam.tsx | 74 +++++ .../panelViews/Request/QueryParamsTable.tsx | 291 ++++++++++++++++++ .../components/panelViews/RequestSetup.tsx | 170 +++++----- packages/editor/src/types/NodeTypes.ts | 7 +- packages/editor/src/workflows/initial.ts | 11 +- .../execution-engine/src/ExecutionEngine.ts | 4 +- packages/shared/package.json | 3 + .../src/contracts/blocks/request/index.ts | 55 ++++ .../shared/src/interfaces/IBlockConfig.ts | 2 +- .../shared/src/ui/components/ui/table.tsx | 117 +++++++ .../src/ui/components/ui/toggle-group.tsx | 59 ++++ .../shared/src/ui/components/ui/toggle.tsx | 43 +++ packages/shared/tsconfig.json | 3 +- pnpm-lock.yaml | 28 ++ 31 files changed, 1044 insertions(+), 172 deletions(-) delete mode 100644 packages/blocks/src/plugins/RequestBlock.ts create mode 100644 packages/blocks/src/requestBlock/index.ts create mode 100644 packages/editor/src/App.tsx create mode 100644 packages/editor/src/blocks/requestNode.ts create mode 100644 packages/editor/src/components/nodeComponents/RequestBlock/index.tsx create mode 100644 packages/editor/src/components/panelViews/Request/AddQueryParam.tsx create mode 100644 packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx create mode 100644 packages/shared/src/contracts/blocks/request/index.ts create mode 100644 packages/shared/src/ui/components/ui/table.tsx create mode 100644 packages/shared/src/ui/components/ui/toggle-group.tsx create mode 100644 packages/shared/src/ui/components/ui/toggle.tsx diff --git a/packages/blocks/src/block/index.ts b/packages/blocks/src/block/index.ts index 8d17546..40f1eb6 100644 --- a/packages/blocks/src/block/index.ts +++ b/packages/blocks/src/block/index.ts @@ -7,7 +7,10 @@ export abstract class Block implements IBlock { readonly type: string; inputs: Record; outputs: Record; - private inputConfigs: Record; + private inputConfigs: Record< + string, + { type: string | string[]; required: boolean } + >; private outputConfigs: Record; readonly retry: number; readonly timeout: number; @@ -65,7 +68,15 @@ export abstract class Block implements IBlock { inputs[key] !== null && inputs[key] !== "" ) { - if (typeof inputs[key] !== config.type) { + if ( + Array.isArray(config.type) && + !config.type.includes(typeof inputs[key]) + ) { + invalidFields.push(key); + } else if ( + !Array.isArray(config.type) && + typeof inputs[key] !== config.type + ) { invalidFields.push(key); } else { cleanedInputs[key] = inputs[key]; @@ -73,6 +84,10 @@ export abstract class Block implements IBlock { } } + if (missingFields.length > 0 || invalidFields.length > 0) { + debugger; + } + return { valid: missingFields.length === 0 && invalidFields.length === 0, missingFields, diff --git a/packages/blocks/src/blockFactory/index.ts b/packages/blocks/src/blockFactory/index.ts index 401bb7f..895129c 100644 --- a/packages/blocks/src/blockFactory/index.ts +++ b/packages/blocks/src/blockFactory/index.ts @@ -1,11 +1,14 @@ import { IBlockConfig, IBlock } from "@data-river/shared/interfaces"; import { ILogger } from "@data-river/shared/interfaces/ILogger"; -import { StartBlock } from "../startBlock"; -import { EndBlock } from "../endBlock"; -import { InputBlock } from "../inputBlock"; -import { OutputBlock } from "../outputBlock"; -import { LogicBlock } from ".."; +import { + RequestBlock, + StartBlock, + EndBlock, + InputBlock, + OutputBlock, + LogicBlock, +} from ".."; type BlockConstructor = new (config: IBlockConfig, logger: ILogger) => IBlock; @@ -15,6 +18,7 @@ const blockRegistry: Record = { "blocks/input@0.1": InputBlock, "blocks/output@0.1": OutputBlock, "blocks/logic@0.1": LogicBlock, + "blocks/request@0.1": RequestBlock, }; export function createBlock(config: IBlockConfig, logger: ILogger): IBlock { diff --git a/packages/blocks/src/endBlock/index.ts b/packages/blocks/src/endBlock/index.ts index a02e121..736cfcf 100644 --- a/packages/blocks/src/endBlock/index.ts +++ b/packages/blocks/src/endBlock/index.ts @@ -9,7 +9,10 @@ export class EndBlock extends Block { { ...config, inputConfigs: { - data: { type: "string", required: false }, + data: { + type: ["string", "number", "boolean", "object", "array"], + required: false, + }, }, outputConfigs: {}, }, diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts index 5cf8cf0..e28cc13 100644 --- a/packages/blocks/src/index.ts +++ b/packages/blocks/src/index.ts @@ -4,5 +4,6 @@ export * from "./endBlock"; export * from "./inputBlock"; export * from "./outputBlock"; export * from "./logicBlock"; +export * from "./requestBlock"; export * from "./blockFactory"; // Note: We're not exporting DatabaseBlock here, as it's a plugin diff --git a/packages/blocks/src/outputBlock/index.ts b/packages/blocks/src/outputBlock/index.ts index 0b3e45f..c8e8b35 100644 --- a/packages/blocks/src/outputBlock/index.ts +++ b/packages/blocks/src/outputBlock/index.ts @@ -9,7 +9,10 @@ export class OutputBlock extends Block { { ...config, inputConfigs: { - data: { type: "string", required: true }, + data: { + type: ["string", "number", "boolean", "object", "array"], + required: true, + }, }, outputConfigs: {}, }, diff --git a/packages/blocks/src/plugins/RequestBlock.ts b/packages/blocks/src/plugins/RequestBlock.ts deleted file mode 100644 index c0b1d4c..0000000 --- a/packages/blocks/src/plugins/RequestBlock.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { IBlockConfig } from "@data-river/shared/interfaces"; -import { Block } from "../block"; -import { ILogger } from "@data-river/shared/interfaces/ILogger"; -import fetch from "node-fetch"; -import { z } from "zod"; - -export class RequestBlock extends Block { - constructor(config: IBlockConfig, logger: ILogger) { - super(config, logger); - } - - async execute( - inputs: Record, - ): Promise> { - const urlSchema = z.string().url(); - const methodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]); - const headersSchema = z.record(z.string(), z.string()); - const queryParamsSchema = z.record(z.string(), z.string()); - const bodySchema = z.object({}).passthrough(); - - try { - const url = urlSchema.parse(inputs.url); - const method = methodSchema.parse(inputs.method); - const headers = headersSchema.parse(inputs.headers || {}); - const queryParams = queryParamsSchema.parse(inputs.queryParams || {}); - const body = bodySchema.parse(inputs.body || {}); - - const queryString = new URLSearchParams(queryParams).toString(); - const fullUrl = `${url}?${queryString}`; - - const response = await fetch(fullUrl, { - method, - headers, - body: method !== "GET" ? JSON.stringify(body) : undefined, - }); - - const jsonResponse = await response.json(); - return { response: jsonResponse }; - } catch (error) { - this.logger.error("RequestBlock execution failed:", error); - throw new Error("RequestBlock execution failed"); - } - } -} diff --git a/packages/blocks/src/requestBlock/index.ts b/packages/blocks/src/requestBlock/index.ts new file mode 100644 index 0000000..ddd10b2 --- /dev/null +++ b/packages/blocks/src/requestBlock/index.ts @@ -0,0 +1,77 @@ +import { + RequestFormData, + RequestFormSchema, +} from "@data-river/shared/contracts/blocks/request"; +import axios, { AxiosRequestConfig, Method } from "axios"; +import { Block } from ".."; +import { IBlockConfig } from "@data-river/shared/interfaces"; +import { ILogger } from "@data-river/shared/interfaces/ILogger"; + +export class RequestBlock extends Block { + private config: RequestFormData; + + constructor(config: IBlockConfig, logger: ILogger) { + super(config, logger); + const result = RequestFormSchema.safeParse(config.config); + if (!result.success) { + throw new Error(`Invalid request configuration: ${result.error}`); + } + this.config = result.data; + } + + async execute(): Promise { + const { httpMethod, url, headers, queryParams, bodyType, body } = + this.config; + + const axiosConfig: AxiosRequestConfig = { + method: httpMethod as Method, + url, + headers: headers?.reduce( + (acc, { key, value }) => ({ ...acc, [key]: value }), + {}, + ), + params: queryParams, + }; + + if (body) { + let contentType: string | undefined = undefined; + + switch (bodyType) { + case "json": + contentType = "application/json"; + axiosConfig.data = JSON.parse(body); + break; + case "form-data": + contentType = "multipart/form-data"; + axiosConfig.data = body; + break; + case "x-www-form-urlencoded": + contentType = "application/x-www-form-urlencoded"; + axiosConfig.data = new URLSearchParams(body); + break; + } + + if (contentType) { + axiosConfig.headers = { + ...axiosConfig.headers, + "Content-Type": contentType, + }; + } + } + + try { + const response = await axios(axiosConfig); + return { + data: response.data, + status: response.status, + statusText: response.statusText, + }; + } catch (error) { + // TODO: Add option in config to accept error and handle it manually if needed. + if (axios.isAxiosError(error)) { + throw new Error(`Request failed: ${error.message}`); + } + throw error; + } + } +} diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts index 6d93d5f..98dfceb 100644 --- a/packages/client/vite.config.ts +++ b/packages/client/vite.config.ts @@ -26,9 +26,10 @@ export default defineConfig({ "../execution-engine/src", ), }, + extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json"], }, optimizeDeps: { - include: ["@data-river/editor", "@data-river/shared"], + include: ["@data-river/editor", "@data-river/shared", "@data-river/blocks"], }, build: { commonjsOptions: { diff --git a/packages/editor/src/App.tsx b/packages/editor/src/App.tsx new file mode 100644 index 0000000..9d5c91f --- /dev/null +++ b/packages/editor/src/App.tsx @@ -0,0 +1,12 @@ +import { Toaster } from "@data-river/shared/ui/components/ui/toaster"; + +function App() { + return ( + <> + {/* Your other components */} + + + ); +} + +export default App; diff --git a/packages/editor/src/blocks/index.ts b/packages/editor/src/blocks/index.ts index 05b4fc4..945e24f 100644 --- a/packages/editor/src/blocks/index.ts +++ b/packages/editor/src/blocks/index.ts @@ -3,6 +3,7 @@ import { inputNode } from "./inputNode"; import { logicNode } from "./logicNode"; import { outputNode } from "./outputNode"; import { endNode } from "./endNode"; +import { requestNode } from "./requestNode"; export const blockConfigs = { start: startNode, @@ -10,6 +11,7 @@ export const blockConfigs = { logic: logicNode, output: outputNode, end: endNode, + request: requestNode, }; export type BlockType = keyof typeof blockConfigs; diff --git a/packages/editor/src/blocks/requestNode.ts b/packages/editor/src/blocks/requestNode.ts new file mode 100644 index 0000000..fbaac41 --- /dev/null +++ b/packages/editor/src/blocks/requestNode.ts @@ -0,0 +1,33 @@ +import { Node } from "reactflow"; +import { RequestNodeData } from "@/types/NodeTypes"; + +export const requestNode: Omit, "position"> = { + id: "0", + type: "custom", + data: { + block: "request@0.1", + label: "Request", + color: "rgb(234 179 8)", + sourceHandle: true, + targetHandle: true, + icon: "Network", + config: { + httpMethod: "GET", + url: "https://pokeapi.co/api/v2/pokemon/ditto", + headers: [ + { + key: "Content-Type", + value: "application/json", + }, + ], + body: "{}", + }, + controls: [ + { + type: "request-info", + label: "Request Info", + name: "request-info", + }, + ], + }, +}; diff --git a/packages/editor/src/components/RightPanel.tsx b/packages/editor/src/components/RightPanel.tsx index 989287c..c84fc1c 100644 --- a/packages/editor/src/components/RightPanel.tsx +++ b/packages/editor/src/components/RightPanel.tsx @@ -9,6 +9,7 @@ import { ICondition } from "@data-river/shared/interfaces/ICondition"; import { Button } from "@data-river/shared/ui/components/ui/button"; import { toggleRightPanelVisible } from "@/store"; import RequestSetup from "./panelViews/RequestSetup"; +import { RequestFormData } from "@data-river/shared/contracts/blocks/request"; const RightPanel = () => { const { isRightPanelVisible } = useLayoutState(); const { nodes, selectedNodeId } = useReactFlowState(); @@ -52,7 +53,11 @@ const RightPanel = () => { case "request@0.1": return ( - + ); // Add cases for other node types here default: diff --git a/packages/editor/src/components/nodeComponents/InputWithLabel.tsx b/packages/editor/src/components/nodeComponents/InputWithLabel.tsx index 85610c7..d57e972 100644 --- a/packages/editor/src/components/nodeComponents/InputWithLabel.tsx +++ b/packages/editor/src/components/nodeComponents/InputWithLabel.tsx @@ -1,5 +1,9 @@ import { Label } from "@data-river/shared/ui/components/ui/label"; import { Input } from "@data-river/shared/ui/components/ui/input"; +import { CopyIcon } from "@radix-ui/react-icons"; +import { useState } from "react"; +import { useToast } from "@data-river/shared/ui"; + const InputWithLabel = ({ label, name, @@ -10,25 +14,58 @@ const InputWithLabel = ({ }: { label: string; name: string; - placeholder: string | undefined; value: string; - onChange: (value: string) => void; + placeholder?: string; + onChange?: (value: string) => void; inputClassName?: string; }) => { + const [isHovered, setIsHovered] = useState(false); + const { toast } = useToast(); + + const handleCopy = ( + event: React.MouseEvent & React.MouseEvent, + ) => { + event.stopPropagation(); + navigator.clipboard.writeText(value); + toast({ + description: "Copied to clipboard!", + duration: 2000, + }); + }; + return ( -
+
- { - onChange(e.target.value); - }} - /> + {onChange ? ( + onChange(e.target.value)} + /> + ) : ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + {value} + + {isHovered && ( + + )} +
+ )}
); }; diff --git a/packages/editor/src/components/nodeComponents/NodeControls.tsx b/packages/editor/src/components/nodeComponents/NodeControls.tsx index 1aae976..4db1073 100644 --- a/packages/editor/src/components/nodeComponents/NodeControls.tsx +++ b/packages/editor/src/components/nodeComponents/NodeControls.tsx @@ -12,6 +12,8 @@ import { updateNodesData } from "@/slices/reactFlowSlice"; import { useReactFlowState } from "@/hooks/useReactFlowState"; import { LogicBlock } from "./LogicBlock"; import { ConditionsSummaryProps } from "./LogicBlock/ConditionsSummary"; +import RequestBlock from "./RequestBlock"; +import { RequestFormData } from "@data-river/shared/contracts/blocks/request"; interface NodeControlsProps { nodeId: string; @@ -151,6 +153,15 @@ const NodeControls: React.FC = ({ isSelected={isSelected} /> ); + case "request-info": + return ( + + ); default: return (
diff --git a/packages/editor/src/components/nodeComponents/RequestBlock/index.tsx b/packages/editor/src/components/nodeComponents/RequestBlock/index.tsx new file mode 100644 index 0000000..66e6b7a --- /dev/null +++ b/packages/editor/src/components/nodeComponents/RequestBlock/index.tsx @@ -0,0 +1,47 @@ +import { RequestFormData } from "@data-river/shared/contracts/blocks/request"; +import { Badge } from "@data-river/shared/ui"; +import InputWithLabel from "../InputWithLabel"; + +const methodColorClasses = { + GET: "text-blue-500 border-blue-500", + POST: "text-green-500 border-green-500", + PUT: "text-yellow-500 border-yellow-500", + PATCH: "text-orange-500 border-orange-500", + DELETE: "text-red-500 border-red-500", +}; + +const RequestBlock = ({ + config, + nodeId, + isSelected, +}: { + config: RequestFormData; + nodeId: string; + isSelected: boolean; +}) => { + const colorClass = + methodColorClasses[config.httpMethod] || methodColorClasses.DELETE; + + return ( +
+
+ + + {config.httpMethod} + + +
+ +
+ ); +}; + +export default RequestBlock; diff --git a/packages/editor/src/components/nodeComponents/TextAreaWithLabel.tsx b/packages/editor/src/components/nodeComponents/TextAreaWithLabel.tsx index fae32ab..910eedf 100644 --- a/packages/editor/src/components/nodeComponents/TextAreaWithLabel.tsx +++ b/packages/editor/src/components/nodeComponents/TextAreaWithLabel.tsx @@ -26,7 +26,7 @@ const TextAreaWithLabel: React.FC = ({ className="nodrag nowheel min-w-[20rem]" id={name} placeholder={placeholder} - value={value} + value={typeof value === "object" ? JSON.stringify(value) : value} onChange={(e) => onChange?.(e.target.value)} onBlur={(e) => onBlur?.(e.target.value)} /> diff --git a/packages/editor/src/components/panelViews/ConditionsSection.tsx b/packages/editor/src/components/panelViews/ConditionsSection.tsx index b95b505..76c3bf9 100644 --- a/packages/editor/src/components/panelViews/ConditionsSection.tsx +++ b/packages/editor/src/components/panelViews/ConditionsSection.tsx @@ -1,11 +1,5 @@ import React from "react"; import { ICondition } from "@data-river/shared/interfaces/ICondition"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@data-river/shared/ui/components/ui/card"; import ConditionSetup from "./ConditionSetup"; interface ConditionsSectionProps { diff --git a/packages/editor/src/components/panelViews/Request/AddQueryParam.tsx b/packages/editor/src/components/panelViews/Request/AddQueryParam.tsx new file mode 100644 index 0000000..e307019 --- /dev/null +++ b/packages/editor/src/components/panelViews/Request/AddQueryParam.tsx @@ -0,0 +1,74 @@ +import React, { useState } from "react"; +import { Button } from "@data-river/shared/ui/components/ui/button"; +import { Input } from "@data-river/shared/ui/components/ui/input"; +import { Trash2 } from "lucide-react"; +import _ from "lodash"; + +export type QueryParam = { + id: string; + key: string; + value: string; +}; + +interface AddQueryParamProps { + onAdd: (params: QueryParam[]) => void; +} + +export function AddQueryParam({ onAdd }: AddQueryParamProps) { + const [newParams, setNewParams] = useState([]); + + const updateParam = (id: string, field: "key" | "value", value: string) => { + setNewParams( + newParams.map((param) => + param.id === id ? { ...param, [field]: value } : param, + ), + ); + }; + + const removeParam = (id: string) => { + setNewParams(newParams.filter((param) => param.id !== id)); + }; + + const handleSave = () => { + const validParams = newParams.filter((param) => param.key.trim() !== ""); + if (validParams.length > 0) { + onAdd(validParams); + setNewParams([]); + } + }; + + return ( +
+ {newParams.map((param) => ( +
+ updateParam(param.id, "key", e.target.value)} + placeholder="Key" + className="w-40 grow-0 truncate" + /> + updateParam(param.id, "value", e.target.value)} + placeholder="Value" + className="w-40 grow truncate" + /> + +
+ ))} + {newParams.length > 0 && ( + + )} +
+ ); +} diff --git a/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx new file mode 100644 index 0000000..63a6b09 --- /dev/null +++ b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx @@ -0,0 +1,291 @@ +import * as React from "react"; +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { + ArrowUpDown, + ChevronDown, + MoreHorizontal, + PlusCircle, + Save, + Trash2, +} from "lucide-react"; + +import { Button } from "@data-river/shared/ui/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@data-river/shared/ui/components/ui/dropdown-menu"; +import { Input } from "@data-river/shared/ui/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@data-river/shared/ui/components/ui/table"; +import _ from "lodash"; + +export type QueryParam = { + id: string; + key: string; + value: string; +}; + +export function QueryParamsTable({ + data, + setData, +}: { + data: QueryParam[]; + setData: (data: QueryParam[]) => void; +}) { + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [], + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + const [newParams, setNewParams] = React.useState([]); + + const columns: ColumnDef[] = [ + { + accessorKey: "key", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) =>
{row.getValue("key")}
, + }, + { + accessorKey: "value", + header: "Value", + cell: ({ row }) =>
{row.getValue("value")}
, + }, + { + id: "actions", + enableHiding: false, + cell: ({ row }) => { + const param = row.original; + + return ( + + + + + + Actions + navigator.clipboard.writeText(param.id)} + > + Copy param ID + + + handleEditParam(param)}> + Edit param + + handleDeleteParam(param.id)}> + Delete param + + + + ); + }, + }, + ]; + + const table = useReactTable({ + data, + columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + }); + + const addNewParam = () => { + setNewParams([ + ...newParams, + { id: _.uniqueId("new-param-"), key: "", value: "" }, + ]); + }; + + const updateNewParam = ( + id: string, + field: "key" | "value", + value: string, + ) => { + setNewParams( + newParams.map((param) => + param.id === id ? { ...param, [field]: value } : param, + ), + ); + }; + + const removeNewParam = (id: string) => { + setNewParams(newParams.filter((param) => param.id !== id)); + }; + + const handleSaveNewParams = () => { + const validParams = newParams.filter((param) => param.key.trim() !== ""); + if (validParams.length > 0) { + setData([...data, ...validParams]); + setNewParams([]); + } + }; + + const handleEditParam = (param: QueryParam) => { + setNewParams([...newParams, { ...param }]); + setData(data.filter((p) => p.id !== param.id)); + }; + + const handleDeleteParam = (id: string) => { + setData(data.filter((p) => p.id !== id)); + }; + + return ( +
+
+ + table.getColumn("key")?.setFilterValue(event.target.value) + } + className="max-w-sm" + /> +
+
0 ? "w-10 opacity-100" : "w-0 opacity-0" + }`} + > + +
+ +
+
+
+ {newParams.map((param) => ( +
+ updateNewParam(param.id, "key", e.target.value)} + placeholder="Key" + className="w-40 grow-0 truncate" + /> + + updateNewParam(param.id, "value", e.target.value) + } + placeholder="Value" + className="w-40 grow truncate" + /> + +
+ ))} +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ))} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+ ); +} diff --git a/packages/editor/src/components/panelViews/RequestSetup.tsx b/packages/editor/src/components/panelViews/RequestSetup.tsx index 6507096..ca0e9b1 100644 --- a/packages/editor/src/components/panelViews/RequestSetup.tsx +++ b/packages/editor/src/components/panelViews/RequestSetup.tsx @@ -1,8 +1,6 @@ import { useState, useEffect } from "react"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useDispatch } from "react-redux"; -import { z } from "zod"; import { PlusCircle, Trash2 } from "lucide-react"; import { Select, @@ -15,39 +13,42 @@ import { SelectValue, } from "@data-river/shared/ui"; import MonacoEditorWrapper from "../MonacoEditorWrapper"; +import { + RequestFormSchema, + RequestFormData, +} from "@data-river/shared/contracts/blocks/request"; +import { QueryParamsTable, QueryParam } from "./Request/QueryParamsTable"; +import { AddQueryParam } from "./Request/AddQueryParam"; +import _ from "lodash"; -const HeaderSchema = z.object({ - key: z.string().nonempty("Header key is required"), - value: z.string().nonempty("Header value is required"), -}); - -const RequestFormSchema = z.object({ - httpMethod: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]), - route: z.string().url("Invalid URL"), - headers: z.array(HeaderSchema).optional(), - body: z - .string() - .optional() - .refine( - (value) => { - if (!value) return true; - try { - JSON.parse(value); - return true; - } catch { - return false; - } - }, - { message: "Invalid JSON format" }, - ), -}); - -type FormData = z.infer; - -export default function RequestBlock() { - const dispatch = useDispatch(); - const [isOpen, setIsOpen] = useState(false); +export default function RequestSetup({ + nodeId, + config, + onConfigChange, +}: { + nodeId: string; + config: RequestFormData; + onConfigChange: (config: RequestFormData) => void; +}) { const [jsonError, setJsonError] = useState(null); + const [existingQueryParams, setExistingQueryParams] = useState( + () => { + if (Array.isArray(config.queryParams)) { + return config.queryParams; + } else if ( + typeof config.queryParams === "object" && + config.queryParams !== null + ) { + return Object.entries(config.queryParams).map(([key, value]) => ({ + id: _.uniqueId("query-param-"), + key, + value, + })); + } + return []; + }, + ); + const { control, register, @@ -55,34 +56,34 @@ export default function RequestBlock() { formState: { errors }, watch, setValue, - } = useForm({ + } = useForm({ resolver: zodResolver(RequestFormSchema), defaultValues: { - httpMethod: "GET", - route: "https://", - headers: [ - { key: "Content-Type", value: "application/json" }, - { key: "Accept", value: "application/json" }, - ], - body: `{ - -}`, + httpMethod: config.httpMethod, + url: config.url, + headers: config.headers, + body: config.body, + queryParams: config.queryParams, }, }); - const saveHeaders = () => { - const data = watch(); - onSubmit(data); - }; - - const onSubmit = (data: FormData) => { - // dispatch(updateRequestBlock(data)); - setIsOpen(false); + const onSubmit = (data: RequestFormData) => { + onConfigChange({ + ...data, + queryParams: Object.fromEntries( + existingQueryParams.map((param) => [param.key, param.value]), + ), + }); }; - const httpMethod = watch("httpMethod"); - const route = watch("route"); const body = watch("body"); + const headers = watch("headers"); + + useEffect(() => { + if (!headers || headers.length === 0) { + setValue("headers", [{ key: "", value: "" }]); + } + }, [headers]); useEffect(() => { if (body) { @@ -113,28 +114,26 @@ export default function RequestBlock() { setValue("body", value || "{}"); }; + const handleAddQueryParams = (newParams: QueryParam[]) => { + setExistingQueryParams([...existingQueryParams, ...newParams]); + updateFormQueryParams([...existingQueryParams, ...newParams]); + }; + + const handleQueryParamsChange = (updatedParams: QueryParam[]) => { + setExistingQueryParams(updatedParams); + updateFormQueryParams(updatedParams); + }; + + const updateFormQueryParams = (params: QueryParam[]) => { + setValue( + "queryParams", + Object.fromEntries(params.map((param) => [param.key, param.value])), + ); + }; + return (
-
- - {httpMethod} - - {route || "/path"} -
-
- {errors.route && ( + {errors.url && (

- {errors.route.message} + {errors.url.message}

)}
@@ -202,6 +201,7 @@ export default function RequestBlock() { type="button" variant="outline" size="icon" + disabled={headers?.length === 1} onClick={() => removeHeader(index)} > @@ -218,15 +218,6 @@ export default function RequestBlock() { Add Header - -
@@ -255,6 +246,15 @@ export default function RequestBlock() {
+
+ + + +
+ diff --git a/packages/editor/src/types/NodeTypes.ts b/packages/editor/src/types/NodeTypes.ts index 717c4ac..34d5d19 100644 --- a/packages/editor/src/types/NodeTypes.ts +++ b/packages/editor/src/types/NodeTypes.ts @@ -1,7 +1,8 @@ +import { RequestFormData } from "@data-river/shared/contracts/blocks/request"; import Icons from "lucide-react"; interface Control { - type: "text" | "text-area" | "select" | "conditions-summary"; + type: "text" | "text-area" | "select" | "conditions-summary" | "request-info"; label: string; name: string; placeholder?: string; @@ -22,3 +23,7 @@ export interface NodeData { outputs?: Record; config?: Record; } + +export type RequestNodeData = Omit & { + config: RequestFormData; +}; diff --git a/packages/editor/src/workflows/initial.ts b/packages/editor/src/workflows/initial.ts index 890e24a..0dba443 100644 --- a/packages/editor/src/workflows/initial.ts +++ b/packages/editor/src/workflows/initial.ts @@ -1,5 +1,6 @@ import { Edge, Node } from "reactflow"; import { NodeData } from "@/types/NodeTypes"; +import { blockConfigs } from "@/blocks"; const initialNodes: Node[] = [ { @@ -44,17 +45,9 @@ const initialNodes: Node[] = [ }, }, { + ...blockConfigs.request, id: "99", - type: "custom", position: { x: 400, y: 300 }, - data: { - block: "request@0.1", - label: "Request", - color: "rgb(234 179 8)", - sourceHandle: true, - targetHandle: true, - icon: "Square", - }, }, { id: "3", diff --git a/packages/execution-engine/src/ExecutionEngine.ts b/packages/execution-engine/src/ExecutionEngine.ts index 570cebf..5e68e85 100644 --- a/packages/execution-engine/src/ExecutionEngine.ts +++ b/packages/execution-engine/src/ExecutionEngine.ts @@ -207,10 +207,12 @@ export class ExecutionEngine { } } + this.logger.info("Inputs for block", { inputs }); + // Externally provided inputs if (blockConfig.inputs) { Object.entries(blockConfig.inputs).forEach(([key, value]) => { - inputs[key] = value; + if (inputs[key] === undefined) inputs[key] = value; }); } diff --git a/packages/shared/package.json b/packages/shared/package.json index 6318380..40fcaf9 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -22,7 +22,10 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-toast": "^1.2.2", + "@radix-ui/react-toggle": "^1.1.0", + "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.3", + "@tanstack/react-table": "^8.20.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.452.0", diff --git a/packages/shared/src/contracts/blocks/request/index.ts b/packages/shared/src/contracts/blocks/request/index.ts new file mode 100644 index 0000000..fc6507e --- /dev/null +++ b/packages/shared/src/contracts/blocks/request/index.ts @@ -0,0 +1,55 @@ +import { z } from "zod"; + +export const HeaderSchema = z.object({ + key: z.string().min(1, "Header key is required"), + value: z.string().min(1, "Header value is required"), +}); + +export const RequestFormSchema = z + .object({ + httpMethod: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]), + url: z.string().url("Invalid URL"), + headers: z.array(HeaderSchema).optional(), + queryParams: z.record(z.string(), z.string()).optional(), + bodyType: z.enum(["json", "form-data", "x-www-form-urlencoded"]).optional(), + body: z.string().optional(), + }) + .superRefine((data, ctx) => { + if (data.bodyType && data.body) { + switch (data.bodyType) { + case "json": + try { + JSON.parse(data.body); + } catch (error) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Invalid JSON format", + path: ["body"], + }); + } + break; + case "form-data": + // Basic check for form-data format (key=value pairs) + if (!/^(.+=.+(\r\n|\n)?)+$/.test(data.body)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Invalid form-data format", + path: ["body"], + }); + } + break; + case "x-www-form-urlencoded": + // Basic check for x-www-form-urlencoded format + if (!/^([^=&]+=[^=&]*&?)+$/.test(data.body)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Invalid x-www-form-urlencoded format", + path: ["body"], + }); + } + break; + } + } + }); + +export type RequestFormData = z.infer; diff --git a/packages/shared/src/interfaces/IBlockConfig.ts b/packages/shared/src/interfaces/IBlockConfig.ts index 4568172..c789d1c 100644 --- a/packages/shared/src/interfaces/IBlockConfig.ts +++ b/packages/shared/src/interfaces/IBlockConfig.ts @@ -1,7 +1,7 @@ export interface IBlockConfig { id: string; type: string; - inputConfigs?: Record; + inputConfigs?: Record; outputConfigs?: Record; config?: Record; inputs?: Record; diff --git a/packages/shared/src/ui/components/ui/table.tsx b/packages/shared/src/ui/components/ui/table.tsx new file mode 100644 index 0000000..6df68c1 --- /dev/null +++ b/packages/shared/src/ui/components/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react"; + +import { cn } from "@/ui/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className, + )} + {...props} + /> +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}; diff --git a/packages/shared/src/ui/components/ui/toggle-group.tsx b/packages/shared/src/ui/components/ui/toggle-group.tsx new file mode 100644 index 0000000..8f6b0d1 --- /dev/null +++ b/packages/shared/src/ui/components/ui/toggle-group.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"; +import { type VariantProps } from "class-variance-authority"; + +import { cn } from "@/ui/utils"; +import { toggleVariants } from "@/ui/components/ui/toggle"; + +const ToggleGroupContext = React.createContext< + VariantProps +>({ + size: "default", + variant: "default", +}); + +const ToggleGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, size, children, ...props }, ref) => ( + + + {children} + + +)); + +ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName; + +const ToggleGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, children, variant, size, ...props }, ref) => { + const context = React.useContext(ToggleGroupContext); + + return ( + + {children} + + ); +}); + +ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName; + +export { ToggleGroup, ToggleGroupItem }; diff --git a/packages/shared/src/ui/components/ui/toggle.tsx b/packages/shared/src/ui/components/ui/toggle.tsx new file mode 100644 index 0000000..0be8ca8 --- /dev/null +++ b/packages/shared/src/ui/components/ui/toggle.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; +import * as TogglePrimitive from "@radix-ui/react-toggle"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/ui/utils"; + +const toggleVariants = cva( + "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground", + { + variants: { + variant: { + default: "bg-transparent", + outline: + "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground", + }, + size: { + default: "h-10 px-3", + sm: "h-9 px-2.5", + lg: "h-11 px-5", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +const Toggle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, size, ...props }, ref) => ( + +)); + +Toggle.displayName = TogglePrimitive.Root.displayName; + +export { Toggle, toggleVariants }; diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index e6877c1..1701f78 100644 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "jsx": "react-jsx", "target": "es6", - "module": "commonjs", + "module": "esnext", + "moduleResolution": "node", "declaration": true, "outDir": "./dist", "strict": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58c9458..691acb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -536,9 +536,18 @@ importers: '@radix-ui/react-toast': specifier: ^1.2.2 version: 1.2.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': + specifier: ^1.1.0 + version: 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': + specifier: ^1.1.0 + version: 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-tooltip': specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-table': + specifier: ^8.20.5 + version: 8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -2817,6 +2826,17 @@ packages: '@supabase/supabase-js@2.45.4': resolution: {integrity: sha512-E5p8/zOLaQ3a462MZnmnz03CrduA5ySH9hZyL03Y+QZLIOO4/Gs8Rdy4ZCKDHsN7x0xdanVEWWFN3pJFQr9/hg==} + '@tanstack/react-table@8.20.5': + resolution: {integrity: sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/table-core@8.20.5': + resolution: {integrity: sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==} + engines: {node: '>=12'} + '@tufjs/canonical-json@2.0.0': resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} engines: {node: ^16.14.0 || >=18.0.0} @@ -10143,6 +10163,14 @@ snapshots: - bufferutil - utf-8-validate + '@tanstack/react-table@8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/table-core': 8.20.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/table-core@8.20.5': {} + '@tufjs/canonical-json@2.0.0': {} '@tufjs/models@2.0.1': From aacb46600c9f34fce4d4f2fb106dae5481cc01f7 Mon Sep 17 00:00:00 2001 From: c0rtexR <180119760+c0rtexR@users.noreply.github.com> Date: Thu, 17 Oct 2024 07:03:41 +0200 Subject: [PATCH 05/11] polish query parameter table --- .../components/panelViews/Request/QueryParamsTable.tsx | 9 ++------- .../editor/src/components/panelViews/RequestSetup.tsx | 8 ++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx index 63a6b09..96034e3 100644 --- a/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx +++ b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx @@ -99,11 +99,6 @@ export function QueryParamsTable({ Actions - navigator.clipboard.writeText(param.id)} - > - Copy param ID - handleEditParam(param)}> Edit param @@ -186,12 +181,12 @@ export function QueryParamsTable({ onChange={(event) => table.getColumn("key")?.setFilterValue(event.target.value) } - className="max-w-sm" + className="w-full" />
0 ? "w-10 opacity-100" : "w-0 opacity-0" + newParams.length > 0 ? "w-9 opacity-100" : "w-0 opacity-0" }`} > -
- ))} - {newParams.length > 0 && ( - - )} -
- ); -} diff --git a/packages/editor/src/components/panelViews/RequestSetup.tsx b/packages/editor/src/components/panelViews/RequestSetup.tsx index 1697298..576b733 100644 --- a/packages/editor/src/components/panelViews/RequestSetup.tsx +++ b/packages/editor/src/components/panelViews/RequestSetup.tsx @@ -12,6 +12,10 @@ import { SelectContent, SelectValue, useToast, + Tabs, + TabsContent, + TabsList, + TabsTrigger, } from "@data-river/shared/ui"; import MonacoEditorWrapper from "../MonacoEditorWrapper"; import { @@ -19,7 +23,6 @@ import { RequestFormData, } from "@data-river/shared/contracts/blocks/request"; import { QueryParamsTable, QueryParam } from "./Request/QueryParamsTable"; -import { AddQueryParam } from "./Request/AddQueryParam"; import _ from "lodash"; export default function RequestSetup({ @@ -49,20 +52,23 @@ export default function RequestSetup({ return []; }, ); + const [headers, setHeaders] = useState( + config.headers || [{ key: "", value: "" }], + ); const { control, register, - handleSubmit, formState: { errors }, watch, setValue, + getValues, } = useForm({ resolver: zodResolver(RequestFormSchema), defaultValues: { httpMethod: config.httpMethod, url: config.url, - headers: config.headers, + headers: headers, body: config.body, queryParams: config.queryParams, }, @@ -70,9 +76,12 @@ export default function RequestSetup({ const { toast } = useToast(); - const onSubmit = (data: RequestFormData) => { + const saveConfiguration = () => { + const formData = getValues(); + console.log("Saving configuration", formData); onConfigChange({ - ...data, + ...formData, + headers: headers, queryParams: Object.fromEntries( existingQueryParams.map((param) => [param.key, param.value]), ), @@ -85,13 +94,6 @@ export default function RequestSetup({ }; const body = watch("body"); - const headers = watch("headers"); - - useEffect(() => { - if (!headers || headers.length === 0) { - setValue("headers", [{ key: "", value: "" }]); - } - }, [headers]); useEffect(() => { if (body) { @@ -107,45 +109,37 @@ export default function RequestSetup({ }, [body]); const addHeader = () => { - const headers = watch("headers") || []; - headers.push({ key: "", value: "" }); - setValue("headers", headers); + setHeaders([...headers, { key: "", value: "" }]); }; const removeHeader = (index: number) => { - const headers = watch("headers") || []; - headers.splice(index, 1); - setValue("headers", headers); + const newHeaders = [...headers]; + newHeaders.splice(index, 1); + setHeaders(newHeaders); }; - const handleEditorChange = (value: string | undefined) => { - setValue("body", value || "{}"); + const updateHeader = ( + index: number, + field: "key" | "value", + value: string, + ) => { + const newHeaders = [...headers]; + newHeaders[index][field] = value; + setHeaders(newHeaders); }; - const handleAddQueryParams = (newParams: QueryParam[]) => { - setExistingQueryParams([...existingQueryParams, ...newParams]); - updateFormQueryParams([...existingQueryParams, ...newParams]); + const handleEditorChange = (value: string | undefined) => { + setValue("body", value || "{}"); }; const handleQueryParamsChange = (updatedParams: QueryParam[]) => { setExistingQueryParams(updatedParams); - updateFormQueryParams(updatedParams); - }; - - const updateFormQueryParams = (params: QueryParam[]) => { - setValue( - "queryParams", - Object.fromEntries(params.map((param) => [param.key, param.value])), - ); }; return (
- +
@@ -190,83 +184,106 @@ export default function RequestSetup({
-
- - {watch("headers")?.map((_, index) => ( -
- - + + Params + Headers + Body + + +
+ + -
- ))} -
- -
-
- -
- -
- -
- {jsonError && ( -

{jsonError}

- )} -
-
- -
- - - + + +
+ + {headers.map((header, index) => ( +
+ + updateHeader(index, "key", e.target.value) + } + placeholder="Key" + className="w-40 grow-0 truncate" + /> + + updateHeader(index, "value", e.target.value) + } + placeholder="Value" + className="w-40 grow truncate" + /> + +
+ ))} +
+ +
+
+
+ +
+ +
+ +
+ {jsonError && ( +

{jsonError}

+ )} +
+
+
- - +
); From 3b6c911076fdd6c69b036a7b6d529967657bad7e Mon Sep 17 00:00:00 2001 From: c0rtexR <180119760+c0rtexR@users.noreply.github.com> Date: Thu, 17 Oct 2024 07:45:43 +0200 Subject: [PATCH 07/11] better column sizing. --- .../panelViews/Request/QueryParamsTable.tsx | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx index 96034e3..2fe1828 100644 --- a/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx +++ b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx @@ -65,6 +65,7 @@ export function QueryParamsTable({ const columns: ColumnDef[] = [ { accessorKey: "key", + maxSize: 50, header: ({ column }) => { return ( - - - Actions - - handleEditParam(param)}> - Edit param - - handleDeleteParam(param.id)}> - Delete param - - - +
+ + + + + + Actions + + handleEditParam(param)}> + Edit param + + handleDeleteParam(param.id)}> + Delete param + + + +
); }, }, From 830cd7e9111f5ae63a4513fc5cc7a179dedb53aa Mon Sep 17 00:00:00 2001 From: c0rtexR <180119760+c0rtexR@users.noreply.github.com> Date: Thu, 17 Oct 2024 07:53:14 +0200 Subject: [PATCH 08/11] Key value table for both params and headers. --- .../panelViews/Request/QueryParamsTable.tsx | 98 +++++++------- .../components/panelViews/RequestSetup.tsx | 125 +++++------------- 2 files changed, 76 insertions(+), 147 deletions(-) diff --git a/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx index 2fe1828..463d9bb 100644 --- a/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx +++ b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx @@ -13,7 +13,6 @@ import { } from "@tanstack/react-table"; import { ArrowUpDown, - ChevronDown, MoreHorizontal, PlusCircle, Save, @@ -40,18 +39,20 @@ import { } from "@data-river/shared/ui/components/ui/table"; import _ from "lodash"; -export type QueryParam = { +export type KeyValuePair = { id: string; key: string; value: string; }; -export function QueryParamsTable({ +export function KeyValueTable({ data, setData, + title, }: { - data: QueryParam[]; - setData: (data: QueryParam[]) => void; + data: KeyValuePair[]; + setData: (data: KeyValuePair[]) => void; + title: string; }) { const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState( @@ -60,12 +61,11 @@ export function QueryParamsTable({ const [columnVisibility, setColumnVisibility] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({}); - const [newParams, setNewParams] = React.useState([]); + const [newItems, setNewItems] = React.useState([]); - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { accessorKey: "key", - maxSize: 50, header: ({ column }) => { return (
-
- {newParams.map((param) => ( -
+ {newItems.map((item) => ( +
updateNewParam(param.id, "key", e.target.value)} + value={item.key} + onChange={(e) => updateNewItem(item.id, "key", e.target.value)} placeholder="Key" className="w-40 grow-0 truncate" /> - updateNewParam(param.id, "value", e.target.value) - } + value={item.value} + onChange={(e) => updateNewItem(item.id, "value", e.target.value)} placeholder="Value" className="w-40 grow truncate" /> @@ -231,7 +225,7 @@ export function QueryParamsTable({ type="button" variant="outline" size="icon" - onClick={() => removeNewParam(param.id)} + onClick={() => removeNewItem(item.id)} > diff --git a/packages/editor/src/components/panelViews/RequestSetup.tsx b/packages/editor/src/components/panelViews/RequestSetup.tsx index 576b733..94990bc 100644 --- a/packages/editor/src/components/panelViews/RequestSetup.tsx +++ b/packages/editor/src/components/panelViews/RequestSetup.tsx @@ -1,7 +1,6 @@ import { useState, useEffect } from "react"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusCircle, Trash2 } from "lucide-react"; import { Select, Input, @@ -22,7 +21,7 @@ import { RequestFormSchema, RequestFormData, } from "@data-river/shared/contracts/blocks/request"; -import { QueryParamsTable, QueryParam } from "./Request/QueryParamsTable"; +import { KeyValueTable, KeyValuePair } from "./Request/QueryParamsTable"; import _ from "lodash"; export default function RequestSetup({ @@ -35,25 +34,23 @@ export default function RequestSetup({ onConfigChange: (config: RequestFormData) => void; }) { const [jsonError, setJsonError] = useState(null); - const [existingQueryParams, setExistingQueryParams] = useState( - () => { - if (Array.isArray(config.queryParams)) { - return config.queryParams; - } else if ( - typeof config.queryParams === "object" && - config.queryParams !== null - ) { - return Object.entries(config.queryParams).map(([key, value]) => ({ - id: _.uniqueId("query-param-"), - key, - value, - })); - } - return []; - }, - ); - const [headers, setHeaders] = useState( - config.headers || [{ key: "", value: "" }], + const [queryParams, setQueryParams] = useState(() => { + if (Array.isArray(config.queryParams)) { + return config.queryParams; + } else if ( + typeof config.queryParams === "object" && + config.queryParams !== null + ) { + return Object.entries(config.queryParams).map(([key, value]) => ({ + id: _.uniqueId("query-param-"), + key, + value, + })); + } + return []; + }); + const [headers, setHeaders] = useState( + config.headers?.map((h) => ({ ...h, id: _.uniqueId("header-") })) || [], ); const { @@ -68,7 +65,7 @@ export default function RequestSetup({ defaultValues: { httpMethod: config.httpMethod, url: config.url, - headers: headers, + headers: config.headers, body: config.body, queryParams: config.queryParams, }, @@ -81,9 +78,9 @@ export default function RequestSetup({ console.log("Saving configuration", formData); onConfigChange({ ...formData, - headers: headers, + headers: headers.map(({ key, value }) => ({ key, value })), queryParams: Object.fromEntries( - existingQueryParams.map((param) => [param.key, param.value]), + queryParams.map((param) => [param.key, param.value]), ), }); @@ -108,34 +105,10 @@ export default function RequestSetup({ } }, [body]); - const addHeader = () => { - setHeaders([...headers, { key: "", value: "" }]); - }; - - const removeHeader = (index: number) => { - const newHeaders = [...headers]; - newHeaders.splice(index, 1); - setHeaders(newHeaders); - }; - - const updateHeader = ( - index: number, - field: "key" | "value", - value: string, - ) => { - const newHeaders = [...headers]; - newHeaders[index][field] = value; - setHeaders(newHeaders); - }; - const handleEditorChange = (value: string | undefined) => { setValue("body", value || "{}"); }; - const handleQueryParamsChange = (updatedParams: QueryParam[]) => { - setExistingQueryParams(updatedParams); - }; - return (
@@ -193,59 +166,21 @@ export default function RequestSetup({
-
- {headers.map((header, index) => ( -
- - updateHeader(index, "key", e.target.value) - } - placeholder="Key" - className="w-40 grow-0 truncate" - /> - - updateHeader(index, "value", e.target.value) - } - placeholder="Value" - className="w-40 grow truncate" - /> - -
- ))} -
- -
+
From 4506d4259641844d6e491eec910e64f83c63afac Mon Sep 17 00:00:00 2001 From: c0rtexR <180119760+c0rtexR@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:39:09 +0200 Subject: [PATCH 09/11] Improve tabs in request setup. --- .../src/components/MonacoEditorWrapper.tsx | 25 ++- .../components/panelViews/Request/BodyTab.tsx | 64 +++++++ .../panelViews/Request/HeadersTab.tsx | 16 ++ .../panelViews/Request/ParamsTab.tsx | 20 ++ .../panelViews/Request/QueryParamsTable.tsx | 8 +- .../components/panelViews/RequestSetup.tsx | 64 +++---- packages/shared/package.json | 6 +- packages/shared/src/tailwind.css | 2 + packages/shared/src/ui/components/ui/form.tsx | 177 ++++++++++++++++++ .../src/ui/components/ui/radio-group.tsx | 42 +++++ packages/shared/src/ui/hooks/useTheme.ts | 34 ++++ packages/shared/src/ui/index.ts | 3 +- pnpm-lock.yaml | 12 ++ 13 files changed, 422 insertions(+), 51 deletions(-) create mode 100644 packages/editor/src/components/panelViews/Request/BodyTab.tsx create mode 100644 packages/editor/src/components/panelViews/Request/HeadersTab.tsx create mode 100644 packages/editor/src/components/panelViews/Request/ParamsTab.tsx create mode 100644 packages/shared/src/ui/components/ui/form.tsx create mode 100644 packages/shared/src/ui/components/ui/radio-group.tsx create mode 100644 packages/shared/src/ui/hooks/useTheme.ts diff --git a/packages/editor/src/components/MonacoEditorWrapper.tsx b/packages/editor/src/components/MonacoEditorWrapper.tsx index 2a204ef..fee1e81 100644 --- a/packages/editor/src/components/MonacoEditorWrapper.tsx +++ b/packages/editor/src/components/MonacoEditorWrapper.tsx @@ -1,13 +1,23 @@ import { Editor, EditorProps, OnMount } from "@monaco-editor/react"; -import { MemoExoticComponent, useCallback, useEffect, useState } from "react"; +import { + MemoExoticComponent, + useCallback, + useEffect, + useRef, + useState, +} from "react"; import { editor } from "monaco-editor"; import darkTheme from "../themes/dark.json"; +import useTheme from "@data-river/shared/ui/hooks/useTheme"; const MonacoEditorWrapper = (props: EditorProps) => { const [MonacoEditor, setMonacoEditor] = useState< MemoExoticComponent >(null as unknown as MemoExoticComponent); + const editor = useRef(null); + + const theme = useTheme(); useEffect(() => { import("@monaco-editor/react").then((module) => { setMonacoEditor( @@ -16,7 +26,7 @@ const MonacoEditorWrapper = (props: EditorProps) => { }); }, []); - const handleEditorDidMount: OnMount = useCallback((editor, monaco) => { + const handleEditorDidMount: OnMount = useCallback((_editor, monaco) => { const customTheme: editor.IStandaloneThemeData = { base: "vs-dark", inherit: true, @@ -25,10 +35,17 @@ const MonacoEditorWrapper = (props: EditorProps) => { }; monaco.editor.defineTheme("customTheme", customTheme); - monaco.editor.setTheme("customTheme"); - editor.updateOptions({ theme: "customTheme" }); + editor.current = _editor; }, []); + useEffect(() => { + if (theme === "dark") { + editor.current?.updateOptions({ theme: "customTheme" }); + } else { + editor.current?.updateOptions({ theme: "vs-light" }); + } + }, [theme, editor.current]); + if (!MonacoEditor) return
Loading Editor...
; return ( diff --git a/packages/editor/src/components/panelViews/Request/BodyTab.tsx b/packages/editor/src/components/panelViews/Request/BodyTab.tsx new file mode 100644 index 0000000..9d3c91d --- /dev/null +++ b/packages/editor/src/components/panelViews/Request/BodyTab.tsx @@ -0,0 +1,64 @@ +import { useState, useEffect } from "react"; +import { Label, RadioGroup, RadioGroupItem } from "@data-river/shared/ui"; +import MonacoEditorWrapper from "../../MonacoEditorWrapper"; + +interface BodyTabProps { + body: string; + handleEditorChange: (value: string | undefined) => void; + jsonError: string | null; +} + +export function BodyTab({ body, handleEditorChange, jsonError }: BodyTabProps) { + return ( +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+ {jsonError &&

{jsonError}

} +
+
+ ); +} diff --git a/packages/editor/src/components/panelViews/Request/HeadersTab.tsx b/packages/editor/src/components/panelViews/Request/HeadersTab.tsx new file mode 100644 index 0000000..43b64d6 --- /dev/null +++ b/packages/editor/src/components/panelViews/Request/HeadersTab.tsx @@ -0,0 +1,16 @@ +import { Label } from "@data-river/shared/ui"; +import { KeyValueTable, KeyValuePair } from "./QueryParamsTable"; + +interface HeadersTabProps { + headers: KeyValuePair[]; + setHeaders: React.Dispatch>; +} + +export function HeadersTab({ headers, setHeaders }: HeadersTabProps) { + return ( +
+ + +
+ ); +} diff --git a/packages/editor/src/components/panelViews/Request/ParamsTab.tsx b/packages/editor/src/components/panelViews/Request/ParamsTab.tsx new file mode 100644 index 0000000..6621804 --- /dev/null +++ b/packages/editor/src/components/panelViews/Request/ParamsTab.tsx @@ -0,0 +1,20 @@ +import { Label } from "@data-river/shared/ui"; +import { KeyValueTable, KeyValuePair } from "./QueryParamsTable"; + +interface ParamsTabProps { + queryParams: KeyValuePair[]; + setQueryParams: React.Dispatch>; +} + +export function ParamsTab({ queryParams, setQueryParams }: ParamsTabProps) { + return ( +
+ + +
+ ); +} diff --git a/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx index 463d9bb..2b2764e 100644 --- a/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx +++ b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx @@ -70,10 +70,12 @@ export function KeyValueTable({ return ( ); }, @@ -271,7 +273,7 @@ export function KeyValueTable({ No results. diff --git a/packages/editor/src/components/panelViews/RequestSetup.tsx b/packages/editor/src/components/panelViews/RequestSetup.tsx index 94990bc..80b697e 100644 --- a/packages/editor/src/components/panelViews/RequestSetup.tsx +++ b/packages/editor/src/components/panelViews/RequestSetup.tsx @@ -15,14 +15,17 @@ import { TabsContent, TabsList, TabsTrigger, + Badge, } from "@data-river/shared/ui"; -import MonacoEditorWrapper from "../MonacoEditorWrapper"; import { RequestFormSchema, RequestFormData, } from "@data-river/shared/contracts/blocks/request"; -import { KeyValueTable, KeyValuePair } from "./Request/QueryParamsTable"; +import { KeyValuePair } from "./Request/QueryParamsTable"; import _ from "lodash"; +import { ParamsTab } from "./Request/ParamsTab"; +import { HeadersTab } from "./Request/HeadersTab"; +import { BodyTab } from "./Request/BodyTab"; export default function RequestSetup({ nodeId, @@ -110,7 +113,13 @@ export default function RequestSetup({ }; return ( -
+
+ + Experimental +
@@ -164,49 +173,20 @@ export default function RequestSetup({ Body -
- - -
+
-
- - -
+
-
- -
- -
- {jsonError && ( -

{jsonError}

- )} -
+
diff --git a/packages/shared/package.json b/packages/shared/package.json index 40fcaf9..d0475cb 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -12,10 +12,12 @@ "tailwindcss-animate": "^1.0.7" }, "dependencies": { + "@hookform/resolvers": "^3.9.0", "@radix-ui/react-accordion": "^1.2.1", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-radio-group": "^1.2.1", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", @@ -29,7 +31,9 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.452.0", + "react-hook-form": "^7.53.0", "react-resizable-panels": "^2.1.4", - "tailwind-merge": "^2.5.3" + "tailwind-merge": "^2.5.3", + "zod": "^3.23.8" } } diff --git a/packages/shared/src/tailwind.css b/packages/shared/src/tailwind.css index cb5cd2c..8d4384b 100644 --- a/packages/shared/src/tailwind.css +++ b/packages/shared/src/tailwind.css @@ -20,6 +20,8 @@ --accent-foreground: 0 0% 9%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; + --focus: 217 91 60%; + --focus-foreground: 0 0% 98%; --border: 0 0% 89.8%; --input: 0 0% 89.8%; --ring: 0 0% 3.9%; diff --git a/packages/shared/src/ui/components/ui/form.tsx b/packages/shared/src/ui/components/ui/form.tsx new file mode 100644 index 0000000..224382e --- /dev/null +++ b/packages/shared/src/ui/components/ui/form.tsx @@ -0,0 +1,177 @@ +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { Slot } from "@radix-ui/react-slot"; +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form"; + +import { cn } from "@/ui/utils"; +import { Label } from "@/ui/components/ui/label"; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName; +}; + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue, +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error("useFormField should be used within "); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext( + {} as FormItemContextValue, +); + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +
+ + ); +}); +FormItem.displayName = "FormItem"; + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( +