diff --git a/packages/blocks/src/block/index.ts b/packages/blocks/src/block/index.ts index 8d17546..124d657 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]; 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/PluginRegistration.ts b/packages/blocks/src/plugins/PluginRegistration.ts index 958044c..b250783 100644 --- a/packages/blocks/src/plugins/PluginRegistration.ts +++ b/packages/blocks/src/plugins/PluginRegistration.ts @@ -2,8 +2,4 @@ import { registerBlockType } from "../blockFactory"; import { DatabaseBlock } from "./DatabaseBlock"; -import { RequestBlock } from "./RequestBlock"; - registerBlockType("database", DatabaseBlock); - -registerBlockType("request", RequestBlock); 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/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/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..a195652 --- /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", + }, + ], + bodyType: "none", + }, + controls: [ + { + type: "request-info", + label: "Request Info", + name: "request-info", + }, + ], + }, +}; 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"} -
); }; diff --git a/packages/editor/src/components/MonacoEditorWrapper.tsx b/packages/editor/src/components/MonacoEditorWrapper.tsx new file mode 100644 index 0000000..e810f3b --- /dev/null +++ b/packages/editor/src/components/MonacoEditorWrapper.tsx @@ -0,0 +1,73 @@ +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"; +import useTheme from "@data-river/shared/ui/hooks/useTheme"; +import { Skeleton } from "@data-river/shared/ui"; + +const EditorSkeleton = () => { + return ( +
+ + + + +
+ ); +}; + +const MonacoEditorWrapper = (props: EditorProps) => { + const [MonacoEditor, setMonacoEditor] = useState< + MemoExoticComponent + >(null as unknown as MemoExoticComponent); + + const [editor, setEditor] = useState( + null, + ); + + const theme = useTheme(); + 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); + theme === "dark" + ? monaco.editor.setTheme("customTheme") + : monaco.editor.setTheme("vs-light"); + setEditor(_editor); + }, + [theme, setEditor], + ); + + useEffect(() => { + editor?.updateOptions({ + theme: theme === "dark" ? "customTheme" : "vs-light", + }); + }, [theme, editor]); + + if (!MonacoEditor) return ; + + return ( + } + /> + ); +}; + +export default MonacoEditorWrapper; diff --git a/packages/editor/src/components/RightPanel.tsx b/packages/editor/src/components/RightPanel.tsx index 3f06be9..c84fc1c 100644 --- a/packages/editor/src/components/RightPanel.tsx +++ b/packages/editor/src/components/RightPanel.tsx @@ -8,6 +8,8 @@ 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"; +import { RequestFormData } from "@data-river/shared/contracts/blocks/request"; const RightPanel = () => { const { isRightPanelVisible } = useLayoutState(); const { nodes, selectedNodeId } = useReactFlowState(); @@ -48,6 +50,15 @@ 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/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/BodyTab.tsx b/packages/editor/src/components/panelViews/Request/BodyTab.tsx new file mode 100644 index 0000000..254cf6f --- /dev/null +++ b/packages/editor/src/components/panelViews/Request/BodyTab.tsx @@ -0,0 +1,221 @@ +import { useState, useEffect, useCallback } from "react"; +import { Label, RadioGroup, RadioGroupItem } from "@data-river/shared/ui"; +import MonacoEditorWrapper from "../../MonacoEditorWrapper"; +import { KeyValueTable, KeyValuePair } from "./QueryParamsTable"; +import _ from "lodash"; + +export type RequestBodyType = + | "none" + | "json" + | "form-data" + | "x-www-form-urlencoded"; + +interface BodyTabProps { + body: string; + bodyType: RequestBodyType; + handleBodyChange: (value: string | undefined) => void; + handleBodyTypeChange: (value: RequestBodyType) => void; + validationError: string | null; +} + +export function BodyTab({ + body, + bodyType, + handleBodyChange, + handleBodyTypeChange, + validationError, +}: BodyTabProps) { + const [jsonBody, setJsonBody] = useState( + body && bodyType === "json" ? body : "{}", + ); + const [formData, setFormData] = useState([]); + const [urlEncodedData, setUrlEncodedData] = useState([]); + + useEffect(() => { + if (bodyType === "json" && body !== jsonBody) { + setJsonBody(body || "{}"); + } else if ( + bodyType === "form-data" || + bodyType === "x-www-form-urlencoded" + ) { + const parsedData = parseBodyToFormData(body); + if (bodyType === "form-data") { + setFormData(parsedData); + } else { + setUrlEncodedData(parsedData); + } + } + }, [body, bodyType]); + + const updateBody = useCallback( + (newBodyType: RequestBodyType, newBody: string) => { + handleBodyTypeChange(newBodyType); + handleBodyChange(newBody); + }, + [handleBodyTypeChange, handleBodyChange], + ); + + const parseBodyToFormData = (bodyString: string): KeyValuePair[] => { + if (!bodyString || bodyString === "{}" || bodyString === "[]") return []; + try { + if ( + bodyString.trim().startsWith("{") || + bodyString.trim().startsWith("[") + ) { + // It's likely JSON, don't parse it as form data + return []; + } + const pairs = bodyString.split("&"); + return pairs.map((pair) => { + const [key, value] = pair.split("="); + return { + id: _.uniqueId("form-data-"), + key: decodeURIComponent(key), + value: decodeURIComponent(value || ""), + }; + }); + } catch (error) { + console.error("Error parsing body to form data:", error); + return []; + } + }; + + const formDataToString = (data: KeyValuePair[]): string => { + return data + .filter((item) => item.key.trim() !== "") // Only include items with non-empty keys + .map( + (item) => + `${encodeURIComponent(item.key)}=${encodeURIComponent(item.value)}`, + ) + .join("&"); + }; + + const handleFormDataChange = ( + newFormData: KeyValuePair[], + type: "form-data" | "x-www-form-urlencoded", + ) => { + if (type === "form-data") { + setFormData(newFormData); + } else { + setUrlEncodedData(newFormData); + } + const newBodyString = formDataToString(newFormData); + updateBody(type, newBodyString); + }; + + const handleJsonChange = (value: string | undefined) => { + setJsonBody(value || "{}"); + updateBody("json", value || "{}"); + }; + + const handleBodyTypeChangeInternal = (newBodyType: RequestBodyType) => { + let newBody = ""; + switch (newBodyType) { + case "json": + newBody = jsonBody; + break; + case "form-data": + newBody = formDataToString(formData); + break; + case "x-www-form-urlencoded": + newBody = formDataToString(urlEncodedData); + break; + case "none": + newBody = ""; + break; + } + updateBody(newBodyType, newBody); + }; + + const renderBodyContent = () => { + switch (bodyType) { + case "none": + return ( +
+

+ This request does not have a body +

+
+ ); + case "json": + return ( +
+ +
+ ); + case "form-data": + return ( + handleFormDataChange(newData, "form-data")} + title="Form Field" + /> + ); + case "x-www-form-urlencoded": + return ( + + handleFormDataChange(newData, "x-www-form-urlencoded") + } + title="Form Field" + /> + ); + default: + return null; + } + }; + + return ( +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ {renderBodyContent()} + {bodyType === "json" && validationError && ( +

{validationError}

+ )} +
+
+ ); +} 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 new file mode 100644 index 0000000..2b2764e --- /dev/null +++ b/packages/editor/src/components/panelViews/Request/QueryParamsTable.tsx @@ -0,0 +1,287 @@ +import * as React from "react"; +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { + ArrowUpDown, + 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 KeyValuePair = { + id: string; + key: string; + value: string; +}; + +export function KeyValueTable({ + data, + setData, + title, +}: { + data: KeyValuePair[]; + setData: (data: KeyValuePair[]) => void; + title: string; +}) { + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [], + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + const [newItems, setNewItems] = 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, + size: 0, + maxSize: 10, + cell: ({ row }) => { + const item = row.original; + + return ( +
+ + + + + + Actions + + handleEditItem(item)}> + Edit {title} + + handleDeleteItem(item.id)}> + Delete {title} + + + +
+ ); + }, + }, + ]; + + 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 addNewItem = () => { + setNewItems([ + ...newItems, + { id: _.uniqueId("new-item-"), key: "", value: "" }, + ]); + }; + + const updateNewItem = (id: string, field: "key" | "value", value: string) => { + setNewItems( + newItems.map((item) => + item.id === id ? { ...item, [field]: value } : item, + ), + ); + }; + + const removeNewItem = (id: string) => { + setNewItems(newItems.filter((item) => item.id !== id)); + }; + + const handleSaveNewItems = () => { + const validItems = newItems.filter((item) => item.key.trim() !== ""); + if (validItems.length > 0) { + setData([...data, ...validItems]); + setNewItems([]); + } + }; + + const handleEditItem = (item: KeyValuePair) => { + setNewItems([...newItems, { ...item }]); + setData(data.filter((p) => p.id !== item.id)); + }; + + const handleDeleteItem = (id: string) => { + setData(data.filter((p) => p.id !== id)); + }; + + return ( +
+
+ + table.getColumn("key")?.setFilterValue(event.target.value) + } + className="w-full" + /> +
+
0 ? "w-9 opacity-100" : "w-0 opacity-0" + }`} + > + +
+ +
+
+
+ {newItems.map((item) => ( +
+ updateNewItem(item.id, "key", e.target.value)} + placeholder="Key" + className="w-40 grow-0 truncate" + /> + updateNewItem(item.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 new file mode 100644 index 0000000..4a5b3b7 --- /dev/null +++ b/packages/editor/src/components/panelViews/RequestSetup.tsx @@ -0,0 +1,241 @@ +import { useState, useEffect } from "react"; +import { useForm, Controller } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + Select, + Input, + Button, + Label, + SelectTrigger, + SelectItem, + SelectContent, + SelectValue, + useToast, + Tabs, + TabsContent, + TabsList, + TabsTrigger, + Badge, +} from "@data-river/shared/ui"; +import { + RequestFormSchema, + RequestFormData, +} from "@data-river/shared/contracts/blocks/request"; +import { KeyValuePair } from "./Request/QueryParamsTable"; +import _ from "lodash"; +import { ParamsTab } from "./Request/ParamsTab"; +import { HeadersTab } from "./Request/HeadersTab"; +import { BodyTab, RequestBodyType } from "./Request/BodyTab"; + +export default function RequestSetup({ + nodeId, + config, + onConfigChange, +}: { + nodeId: string; + config: RequestFormData; + onConfigChange: (config: RequestFormData) => void; +}) { + const [validationError, setValidationError] = useState(null); + 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 [bodyType, setBodyType] = useState( + config.bodyType || "none", + ); + const { + control, + register, + formState: { errors }, + watch, + setValue, + getValues, + } = useForm({ + resolver: zodResolver(RequestFormSchema), + defaultValues: { + httpMethod: config.httpMethod, + url: config.url, + headers: config.headers, + body: config.body, + queryParams: config.queryParams, + bodyType: config.bodyType || "none", + }, + }); + + const { toast } = useToast(); + + const saveConfiguration = () => { + const formData = getValues(); + onConfigChange({ + ...formData, + headers: headers.map(({ key, value }) => ({ key, value })), + queryParams: Object.fromEntries( + queryParams.map((param) => [param.key, param.value]), + ), + bodyType, + }); + + toast({ + title: "Request configuration saved", + description: "Your request configuration has been saved", + }); + }; + + const body = watch("body"); + const watchedBodyType = watch("bodyType"); + + useEffect(() => { + setBodyType(watchedBodyType as RequestBodyType); + }, [watchedBodyType]); + + useEffect(() => { + if (body && bodyType) { + try { + switch (bodyType) { + case "json": + JSON.parse(body); + setValidationError(null); + break; + case "form-data": + case "x-www-form-urlencoded": + // Basic validation for form data + if (!/^(.+=.+(&.+=.+)*)?$/.test(body)) { + setValidationError("Invalid form data format"); + } else { + setValidationError(null); + } + break; + case "none": + setValidationError(null); + break; + } + } catch (error) { + setValidationError("Invalid format for selected body type"); + } + } else { + setValidationError(null); + } + }, [body, bodyType]); + + const handleBodyChange = (value: string | undefined) => { + setValue("body", value || ""); + }; + + const handleBodyTypeChange = (value: RequestBodyType) => { + setValue("bodyType", value); + setBodyType(value); + if (value === "none") { + setValue("body", ""); + } else if (value === "json" && (!body || body === "")) { + setValue("body", "{}"); + } + }; + + return ( +
+ + Experimental + +
+
+
+
+
+ + ( + + )} + /> +
+
+ + + {errors.url && ( +

+ {errors.url.message} +

+ )} +
+
+ + + + Params + Headers + Body + + + + + + + + + + + +
+ + +
+
+
+ ); +} 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/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 6050cbb..3d7d3d5 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[] = [ { @@ -43,6 +44,11 @@ const initialNodes: Node[] = [ ], }, }, + { + ...blockConfigs.request, + id: "99", + position: { x: 400, y: 300 }, + }, { 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/execution-engine/src/ExecutionEngine.ts b/packages/execution-engine/src/ExecutionEngine.ts index 570cebf..78de48e 100644 --- a/packages/execution-engine/src/ExecutionEngine.ts +++ b/packages/execution-engine/src/ExecutionEngine.ts @@ -207,10 +207,12 @@ export class ExecutionEngine { } } + this.logger.debug("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 8237b86..d0475cb 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -12,20 +12,28 @@ "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", "@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", + "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/contracts/blocks/request/index.ts b/packages/shared/src/contracts/blocks/request/index.ts new file mode 100644 index 0000000..e79ac8c --- /dev/null +++ b/packages/shared/src/contracts/blocks/request/index.ts @@ -0,0 +1,57 @@ +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(["none", "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/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/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/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 ( +