diff --git a/frontend/apps/hub/index.html b/frontend/apps/hub/index.html index 26d32a47b2..ff1d3b1a87 100644 --- a/frontend/apps/hub/index.html +++ b/frontend/apps/hub/index.html @@ -1,8 +1,6 @@ - - diff --git a/frontend/apps/hub/package.json b/frontend/apps/hub/package.json index a1211deeee..979edf6a21 100644 --- a/frontend/apps/hub/package.json +++ b/frontend/apps/hub/package.json @@ -15,7 +15,8 @@ "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.2", "@hookform/resolvers": "^3.3.4", - "@rivet-gg/actor-protocol": "workspace:*", + "@rivet-gg/actor-client": "*", + "@rivet-gg/actor-protocol": "*", "@rivet-gg/api": "file:vendor/rivet-gg-api.tgz", "@rivet-gg/api-ee": "file:vendor/rivet-gg-api-ee.tgz", "@rivet-gg/components": "workspace:*", @@ -31,12 +32,13 @@ "@tanstack/router-zod-adapter": "^1.81.5", "@types/bcryptjs": "^2.4.6", "bcryptjs": "^2.4.3", + "esast-util-from-js": "^2.0.1", "file-saver": "^2.0.5", "framer-motion": "^11.2.11", "lodash": "^4.17.21", "posthog-js": "^1.144.2", - "react": "*", - "react-dom": "*", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-hook-form": "^7.51.1", "react-konami-code": "^2.3.0", "react-turnstile": "^1.1.3", diff --git a/frontend/apps/hub/src/components/repl/repl-input.tsx b/frontend/apps/hub/src/components/repl/repl-input.tsx new file mode 100644 index 0000000000..0083e3ac1e --- /dev/null +++ b/frontend/apps/hub/src/components/repl/repl-input.tsx @@ -0,0 +1,91 @@ +import { + CodeMirror, + type CodeMirrorRef, + type CompletionContext, + EditorView, + External, + defaultKeymap, + javascript, + javascriptLanguage, + keymap, +} from "@rivet-gg/components/code-mirror"; +import { forwardRef } from "react"; + +const deleteBgTheme = EditorView.theme({ + ".cm-content": { padding: 0 }, +}); + +interface ReplInputProps { + rpcs: string[]; + onRun: (code: string) => void; +} + +export const ReplInput = forwardRef( + ({ rpcs, onRun }, ref) => { + const rivetKeymap = keymap.of([ + { + key: "Shift-Enter", + run: (editor) => { + onRun(editor?.state.doc.toString()); + editor.dispatch({ + changes: { + from: 0, + to: editor.state.doc.length, + insert: "", + }, + annotations: [External.of(true)], + }); + return true; + }, + }, + ...defaultKeymap, + ]); + + const replAutocomplete = javascriptLanguage.data.of({ + autocomplete: (context: CompletionContext) => { + const word = context.matchBefore(/^\w*/); + if (!word || (word?.from === word?.to && !context.explicit)) + return null; + return { + from: word.from, + to: word.to, + boost: 99, + options: [ + { + label: "wait", + apply: "wait", + validFor: /^(@\w*)?$/, + info: "Helper function to wait for a number of milliseconds", + }, + ...rpcs.map((rpc) => ({ + label: rpc, + apply: rpc, + validFor: /^(@\w*)?$/, + info: `Call "${rpc}" RPC on Actor`, + })), + ], + }; + }, + }); + + return ( + + ); + }, +); diff --git a/frontend/apps/hub/src/components/repl/repl-log.tsx b/frontend/apps/hub/src/components/repl/repl-log.tsx new file mode 100644 index 0000000000..6965c72311 --- /dev/null +++ b/frontend/apps/hub/src/components/repl/repl-log.tsx @@ -0,0 +1,121 @@ +import { cn } from "@rivet-gg/components"; +import { + Icon, + faArrowLeft, + faArrowRight, + faCircleX, + faSpinner, +} from "@rivet-gg/icons"; +import type { ReplCommand } from "./repl-state"; + +interface ReplLogProps { + commands: ReplCommand[]; +} + +export function ReplLog({ commands }: ReplLogProps) { + return ( +
+ {commands.map((command) => ( +
+
+
+ +
+ + {command.status === "pending" ? ( +
+ ... +
+ ) : null} + + {command.status !== "pending" && command.formatted ? ( +
+ {command.formatted?.tokens.map( + (tokensLine, index) => ( + + {tokensLine.map((token, index) => ( + + {token.content} + + ))} + + ), + )} +
+ ) : null} +
+
+ {command.logs?.map((log, index) => ( +
+ {log.message} +
+ ))} +
+ {command.status !== "success" && + command.status !== "error" ? ( +
+
+ +
+ Waiting for response... +
+ ) : null} + {command.status === "success" ? ( +
+
+ +
+ +
+ {(command.result as string) || "undefined"} +
+
+ ) : null} + {command.status === "error" ? ( +
+
+ +
+ +
+ {typeof command.error === "object" && + command.error && + "message" in command.error + ? (command.error.message as string) || + "undefined" + : "undefined"} +
+
+ ) : null} +
+ ))} +
+ ); +} diff --git a/frontend/apps/hub/src/components/repl/repl-schema.ts b/frontend/apps/hub/src/components/repl/repl-schema.ts new file mode 100644 index 0000000000..38bf6c36ee --- /dev/null +++ b/frontend/apps/hub/src/components/repl/repl-schema.ts @@ -0,0 +1,57 @@ +import { z } from "zod"; + +export const MessageSchema = z.object({ + type: z.literal("code"), + data: z.string(), + managerUrl: z.string(), + actorId: z.string(), + rpcs: z.array(z.string()), + id: z.string(), +}); + +export const FormattedCodeSchema = z + .object({ + fg: z.string(), + tokens: z.array( + z.array( + z.object({ + content: z.string(), + color: z.string(), + }), + ), + ), + }) + .catch((ctx) => ctx.input); + +export const LogSchema = z.object({ + level: z.string(), + message: z.any(), +}); + +export const ResponseSchema = z.discriminatedUnion("type", [ + z.object({ + type: z.literal("error"), + id: z.string(), + data: z.any(), + }), + z.object({ + type: z.literal("formatted"), + id: z.string(), + data: FormattedCodeSchema, + }), + z.object({ + type: z.literal("result"), + id: z.string(), + data: z.any(), + }), + z.object({ + type: z.literal("log"), + id: z.string(), + data: LogSchema, + }), +]); + +export type Response = z.infer; +export type Message = z.infer; +export type FormattedCode = z.infer; +export type Log = z.infer; diff --git a/frontend/apps/hub/src/components/repl/repl-state.tsx b/frontend/apps/hub/src/components/repl/repl-state.tsx new file mode 100644 index 0000000000..34fc450bbb --- /dev/null +++ b/frontend/apps/hub/src/components/repl/repl-state.tsx @@ -0,0 +1,167 @@ +import { useCallback, useState, useSyncExternalStore } from "react"; +import { + type FormattedCode, + type Log, + type Message, + ResponseSchema, +} from "./repl-schema"; +import ReplWorker from "./repl.worker?worker"; + +export type ReplCommand = { + logs: Log[]; + code: string; + key: string; +} & ( + | { status: "pending" } + | { status: "formatted"; formatted: FormattedCode } + | { status: "success"; formatted: FormattedCode; result: unknown } + | { status: "error"; formatted: FormattedCode | undefined; error: unknown } +); + +class WorkerState { + commands: ReplCommand[]; + + #listeners: (() => void)[] = []; + #worker: Worker; + + constructor() { + this.#worker = new ReplWorker(); + this.commands = []; + + this.#worker.addEventListener( + "message", + this.#handleMessage.bind(this), + ); + this.#worker.addEventListener("error", (error) => { + console.log(error, error.message, error.error); + }); + } + + runCode({ + code, + actorId, + managerUrl, + rpcs, + }: Omit & { code: string }) { + const key = Date.now().toString(); + this.commands = [ + ...this.commands, + { status: "pending", code, key, logs: [] }, + ]; + + this.#worker.postMessage({ + type: "code", + data: code, + actorId, + managerUrl, + rpcs, + id: key, + } satisfies Message); + this.#update(); + } + + getSnapshot() { + return this.commands; + } + + subscribe(cb: () => void) { + this.#listeners.push(cb); + return () => { + this.#listeners = this.#listeners.filter( + (listener) => listener !== cb, + ); + }; + } + + #handleMessage(event: MessageEvent) { + console.log(event.data); + const { success, data } = ResponseSchema.safeParse(event.data); + + if (!success) { + return; + } + + if (data.type === "formatted") { + const command = this.commands.find( + (command) => command.key === data.id, + ); + if (command) { + const newCommand = { + ...command, + status: "formatted", + formatted: data.data, + }; + Object.assign(command, newCommand); + this.commands = [...this.commands]; + this.#update(); + } + } + + if (data.type === "result") { + const command = this.commands.find( + (command) => command.key === data.id, + ); + if (command) { + const newCommand = { + ...command, + status: "success", + result: data.data, + }; + Object.assign(command, newCommand); + this.commands = [...this.commands]; + this.#update(); + } + } + + if (data.type === "log") { + const command = this.commands.find( + (command) => command.key === data.id, + ); + if (command) { + const newCommand = { + ...command, + logs: [...command.logs, data.data], + }; + Object.assign(command, newCommand); + this.commands = [...this.commands]; + this.#update(); + } + } + + if (data.type === "error") { + const command = this.commands.find( + (command) => command.key === data.id, + ); + if (command) { + const newCommand = { + ...command, + status: "error", + error: data.data, + }; + Object.assign(command, newCommand); + this.commands = [...this.commands]; + this.#update(); + } + } + } + + #update() { + for (const listener of this.#listeners) { + listener(); + } + } +} + +export function useRepl() { + const [state] = useState(() => new WorkerState()); + return [ + useSyncExternalStore( + useCallback((fn) => state.subscribe(fn), [state.subscribe]), + useCallback(() => state.getSnapshot(), [state.getSnapshot]), + ), + useCallback( + (...args) => state.runCode(...args), + [state.runCode], + ) as WorkerState["runCode"], + ] as const; +} diff --git a/frontend/apps/hub/src/components/repl/repl.tsx b/frontend/apps/hub/src/components/repl/repl.tsx new file mode 100644 index 0000000000..7f44d40f13 --- /dev/null +++ b/frontend/apps/hub/src/components/repl/repl.tsx @@ -0,0 +1,55 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Code, WithTooltip } from "@rivet-gg/components"; +import { faChevronRight } from "@rivet-gg/icons"; +import { Fragment } from "react/jsx-runtime"; +import { ReplInput } from "./repl-input"; +import { ReplLog } from "./repl-log"; +import { useRepl } from "./repl-state"; + +interface ReplProps { + rpcs: string[]; + actorId: string; + managerUrl: string; +} + +export function ActorRepl({ rpcs, actorId, managerUrl }: ReplProps) { + const [commands, runCode] = useRepl(); + return ( +
+ +
+
+ +
+
+ { + runCode({ code, actorId, managerUrl, rpcs }); + }} + /> +
+
+

+ You can call all defined RPCs here:{" "} + {rpcs.map((rpc, index) => ( + + {rpc} + {index < rpcs.length - 1 ? ", " : ""} + + ))} + . Available helpers: + wait} + content="Helper function to wait for a number of milliseconds" + /> + . +
+ Press Shift+Enter to run your command. +

+
+ ); +} diff --git a/frontend/apps/hub/src/components/repl/repl.worker.ts b/frontend/apps/hub/src/components/repl/repl.worker.ts new file mode 100644 index 0000000000..4b57c3915a --- /dev/null +++ b/frontend/apps/hub/src/components/repl/repl.worker.ts @@ -0,0 +1,147 @@ +import * as shiki from "shiki"; + +import { type ActorHandle, Client } from "@rivet-gg/actor-client"; +import { fromJs } from "esast-util-from-js"; +import { toJs } from "estree-util-to-js"; +import githubDark from "shiki/themes/github-dark-default.mjs"; +import { MessageSchema, ResponseSchema } from "./repl-schema"; + +export let highlighter: shiki.Highlighter | undefined; + +async function formatCode(code: string) { + highlighter ??= await shiki.getSingletonHighlighter({ + themes: [githubDark], + langs: ["typescript"], + }); + + return highlighter.codeToTokens(code, { + lang: "typescript", + theme: "github-dark-default", + }); +} + +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +async function evaluateCode(code: string, args: Record) { + const argsString = Object.keys(args); + const argValues = Object.values(args); + + const program = fromJs(code, { + module: true, + allowAwaitOutsideFunction: true, + allowReturnOutsideFunction: true, + }); + + const lastStatement = program.body[program.body.length - 1]; + if (lastStatement.type === "ExpressionStatement") { + program.body[program.body.length - 1] = { + type: "ReturnStatement", + argument: lastStatement.expression, + }; + } + + const jsCode = toJs(program); + + return new Function( + "window", + ...argsString, + `"use strict"; + return (async () => { + ${jsCode.value} + })() + `, + )({}, ...argValues); +} + +const createConsole = (id: string) => { + const log = (level: string, message: unknown) => { + postMessage( + ResponseSchema.parse({ + type: "log", + id, + data: { level, message: JSON.stringify(message) }, + }), + ); + }; + + return { + log: log.bind(null, "log"), + info: log.bind(null, "info"), + warn: log.bind(null, "warn"), + error: log.bind(null, "error"), + }; +}; + +addEventListener("message", async (event) => { + console.log(event); + const { success, data } = MessageSchema.safeParse(event.data); + console.log("msg", event.data); + + if (!success) { + return; + } + + if (data.type === "code") { + let handle: ActorHandle | undefined; + try { + const formatted = await formatCode(data.data); + postMessage( + ResponseSchema.parse({ + type: "formatted", + id: data.id, + data: formatted, + }), + ); + + const cl = new Client(data.managerUrl); + handle = await Promise.race([ + cl.getWithId(data.actorId, {}), + wait(5000).then(() => { + throw new Error("Timeout"); + }), + ]); + + const actor = handle; + + if (!actor) { + postMessage( + ResponseSchema.parse({ + type: "error", + id: data.id, + data: new Error("Could not connect to actor"), + }), + ); + return; + } + + const rpcs = Object.fromEntries( + data.rpcs.map( + (rpc) => [rpc, actor[rpc as keyof typeof actor]] as const, + ), + ); + + const evaluated = await evaluateCode(data.data, { + console: createConsole(data.id), + wait, + actor: handle, + ...rpcs, + }); + postMessage( + ResponseSchema.parse({ + type: "result", + id: data.id, + data: JSON.stringify(evaluated), + }), + ); + } catch (e) { + handle?.dispose(); + postMessage( + ResponseSchema.parse({ + type: "error", + id: data.id, + data: e, + }), + ); + } + } +}); diff --git a/frontend/apps/hub/src/domains/project/components/actors/actor-rpc-tab.tsx b/frontend/apps/hub/src/domains/project/components/actors/actor-rpc-tab.tsx index 540a8e9e4b..e878a96bf3 100644 --- a/frontend/apps/hub/src/domains/project/components/actors/actor-rpc-tab.tsx +++ b/frontend/apps/hub/src/domains/project/components/actors/actor-rpc-tab.tsx @@ -1,8 +1,11 @@ +import { ActorRepl } from "@/components/repl/repl"; import type { Rivet } from "@rivet-gg/api"; -import { Accordion, ScrollArea } from "@rivet-gg/components"; -import { useQuery } from "@tanstack/react-query"; -import { actorsRpcsQueryOptions } from "../../queries"; -import { ActorRpc } from "./actor-rpc"; +import { ScrollArea } from "@rivet-gg/components"; +import { useSuspenseQuery } from "@tanstack/react-query"; +import { + actorManagerUrlQueryOptions, + actorsRpcsQueryOptions, +} from "../../queries"; interface ActorRpcTabProps extends Rivet.actor.Actor { projectNameId: string; @@ -15,7 +18,7 @@ export function ActorRpcTab({ environmentNameId, id: actorId, }: ActorRpcTabProps) { - const { data } = useQuery( + const { data } = useSuspenseQuery( actorsRpcsQueryOptions({ actorId, network, @@ -23,20 +26,17 @@ export function ActorRpcTab({ environmentNameId, }), ); + const { data: actorManagerUrl } = useSuspenseQuery( + actorManagerUrlQueryOptions({ projectNameId, environmentNameId }), + ); return ( - {data?.length === 0 ? ( -

- No RPCs found. -

- ) : ( - - {data?.map((rpc) => ( - - ))} - - )} +
); } diff --git a/frontend/apps/hub/src/domains/project/components/actors/actor-state-tab.tsx b/frontend/apps/hub/src/domains/project/components/actors/actor-state-tab.tsx index 1fd1dd325e..53aa4eeddd 100644 --- a/frontend/apps/hub/src/domains/project/components/actors/actor-state-tab.tsx +++ b/frontend/apps/hub/src/domains/project/components/actors/actor-state-tab.tsx @@ -1,13 +1,7 @@ import type { Rivet } from "@rivet-gg/api"; -import { - Code, - JsonCode, - Link, - ScrollArea, - Skeleton, -} from "@rivet-gg/components"; +import { Code, Link, ScrollArea, Skeleton } from "@rivet-gg/components"; +import { JsonCode } from "@rivet-gg/components/code-mirror"; import { useQuery } from "@tanstack/react-query"; - import { actorStateQueryOptions } from "../../queries"; interface ActorStateTabProps extends Rivet.actor.Actor { diff --git a/frontend/apps/hub/src/domains/project/components/actors/actors-actor-details.tsx b/frontend/apps/hub/src/domains/project/components/actors/actors-actor-details.tsx index 6c28fb3627..a92ab69942 100644 --- a/frontend/apps/hub/src/domains/project/components/actors/actors-actor-details.tsx +++ b/frontend/apps/hub/src/domains/project/components/actors/actors-actor-details.tsx @@ -189,7 +189,7 @@ export function ActorsActorDetails({ ) : null} - RPC + Console State Config @@ -258,12 +258,19 @@ export function ActorsActorDetails({ {...data} /> - - + + } + > + + { + const http = Object.values(network.ports).find( + (port) => port.protocol === "http", + ); + if (!http) { + return undefined; + } + const url = new URL(`${http.protocol}://${http.hostname}:${http.port}`); + url.pathname = http.path || "/"; + return url.href; +}; + export const actorInspectQueryOptions = ({ projectNameId, environmentNameId, @@ -401,9 +413,6 @@ export const actorInspectQueryOptions = ({ actorId: string; network: Rivet.actor.Network; }) => { - const http = Object.values(network.ports).find( - (port) => port.protocol === "http", - ); return queryOptions({ queryKey: [ "project", @@ -415,11 +424,8 @@ export const actorInspectQueryOptions = ({ "inspect", ], queryFn: async ({ signal }) => { - const url = new URL( - `${http?.protocol}://${http?.hostname}:${http?.port}`, - ); - url.pathname = `${http?.path}/inspect`; - const response = await fetch(url.href, { signal }); + const href = createActorEndpoint(network); + const response = await fetch(`${href}/inspect`, { signal }); if (!response.ok) { throw response; } @@ -435,7 +441,6 @@ export const actorInspectQueryOptions = ({ return parsed; }, - enabled: http !== undefined, }); }; @@ -483,3 +488,45 @@ export const actorsRpcsQueryOptions = ({ select: (data) => data.rpcs, }); }; + +export const actorManagerUrlQueryOptions = ({ + projectNameId, + environmentNameId, +}: { + projectNameId: string; + environmentNameId: string; +}) => { + return queryOptions({ + queryKey: [ + "project", + projectNameId, + "environment", + environmentNameId, + "actor-manager", + ], + queryFn: async ({ + signal: abortSignal, + queryKey: [_, project, __, environment], + }) => { + const response = await rivetClient.actor.list( + { + project, + environment, + tagsJson: JSON.stringify({ name: "manager" }), + }, + { abortSignal }, + ); + + if (response.actors.length === 0) { + throw new Error("No actor manager found"); + } + const href = createActorEndpoint(response.actors[0].network); + + if (!href) { + throw new Error("No actor manager found"); + } + + return href; + }, + }); +}; diff --git a/frontend/packages/components/package.json b/frontend/packages/components/package.json index 27e21ff0d5..d4b53962f8 100644 --- a/frontend/packages/components/package.json +++ b/frontend/packages/components/package.json @@ -19,7 +19,8 @@ "./auto-form": "./src/auto-form/index.tsx", "./theme.css": "./public/theme.css", "./tailwind-base": "./src/tailwind-base.ts", - "./layout": "./src/layout/index.ts" + "./layout": "./src/layout/index.ts", + "./code-mirror": "./src/code-mirror/index.tsx" }, "types": "./dist/index.d.ts", "dependencies": { diff --git a/frontend/packages/components/src/code-mirror/index.tsx b/frontend/packages/components/src/code-mirror/index.tsx new file mode 100644 index 0000000000..31050f48e4 --- /dev/null +++ b/frontend/packages/components/src/code-mirror/index.tsx @@ -0,0 +1,47 @@ +import { forwardRef } from "react"; +import ReactCodeMirror, { type ReactCodeMirrorRef, type ReactCodeMirrorProps } from "@uiw/react-codemirror"; +import { githubDark, githubDarkInit } from "@uiw/codemirror-theme-github"; +import { Annotation } from "@codemirror/state"; +import { json, jsonParseLinter } from "@codemirror/lang-json"; +import { linter } from "@codemirror/lint"; +import { CodeMirrorContainer } from "../code-mirror-container"; + +const theme = githubDarkInit({ + settings: { + background: "transparent", + lineHighlight: "transparent", + fontSize: "12px", + }, +}); + +export const CodeMirror = forwardRef((props, ref) => { + return ; +}); + +interface JsonCodeProps extends ReactCodeMirrorProps {} + +export const JsonCode = forwardRef( + ({ value, extensions = [], className, ...props }, ref) => { + return ( + + + + ); + }, +); + +export const External = Annotation.define(); +export { defaultKeymap } from "@codemirror/commands"; +export { keymap, type KeyBinding, EditorView } from "@codemirror/view"; +export { javascript, javascriptLanguage } from "@codemirror/lang-javascript"; +export { type CompletionContext } from "@codemirror/autocomplete"; +export { json, jsonParseLinter } from "@codemirror/lang-json"; +export { + type ReactCodeMirrorProps as CodeMirrorProps, + type ReactCodeMirrorRef as CodeMirrorRef, +} from "@uiw/react-codemirror"; diff --git a/frontend/packages/components/src/code.tsx b/frontend/packages/components/src/code.tsx index b2cf72b109..4b534084f7 100644 --- a/frontend/packages/components/src/code.tsx +++ b/frontend/packages/components/src/code.tsx @@ -1,15 +1,6 @@ -import { javascript } from "@codemirror/lang-javascript"; -import { json, jsonParseLinter } from "@codemirror/lang-json"; -import { linter } from "@codemirror/lint"; -import { EditorView } from "@codemirror/view"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@radix-ui/react-tabs"; import { Icon, faCopy, faFile } from "@rivet-gg/icons"; -import { githubDark } from "@uiw/codemirror-theme-github"; -import ReactCodeMirror, { - type ReactCodeMirrorProps, -} from "@uiw/react-codemirror"; -import { Children, type ReactElement, cloneElement, forwardRef } from "react"; -import { CodeMirrorContainer } from "./code-mirror-container"; +import { Children, type ReactElement, cloneElement } from "react"; import { CopyButton } from "./copy-area"; import { cn } from "./lib/utils"; import { Badge } from "./ui/badge"; @@ -17,47 +8,6 @@ import { Button } from "./ui/button"; import { ScrollArea } from "./ui/scroll-area"; import { WithTooltip } from "./ui/tooltip"; -interface JsonCodeProps extends ReactCodeMirrorProps {} - -export const JsonCode = forwardRef( - ({ value, extensions = [], className, ...props }, ref) => { - return ( - - - - ); - }, -); - -export const JavaScriptCode = forwardRef( - ({ value, extensions = [], ...props }, ref) => { - return ( - - - - ); - }, -); - const languageNames = { csharp: "C#", cpp: "C++", diff --git a/frontend/packages/components/src/ui/typography.tsx b/frontend/packages/components/src/ui/typography.tsx index 7eeeae6e10..0c966a53a9 100644 --- a/frontend/packages/components/src/ui/typography.tsx +++ b/frontend/packages/components/src/ui/typography.tsx @@ -3,17 +3,14 @@ import { type PropsWithChildren, forwardRef } from "react"; // This file is based on the typography components from https://ui.shadcn.com/docs/components/typography // with some modifications to fit the project's design system. import { cn } from "../lib/utils"; -import { - type CommonHelperProps, - getCommonHelperClass, - omitCommonHelperProps, -} from "./helpers"; +import { type CommonHelperProps, getCommonHelperClass, omitCommonHelperProps } from "./helpers"; -type TypographyElementProps = - PropsWithChildren & - Partial & { - asChild?: boolean; - }; +type TypographyElementProps = PropsWithChildren< + JSX.IntrinsicElements[T] +> & + Partial & { + asChild?: boolean; + }; const H1 = ({ className, asChild, ...props }: TypographyElementProps<"h1">) => { const Comp = asChild ? Slot : "h1"; @@ -71,37 +68,21 @@ const H4 = ({ className, asChild, ...props }: TypographyElementProps<"h4">) => { ); }; -const Paragraph = ({ - className, - asChild, - ...props -}: TypographyElementProps<"p">) => { +const Paragraph = ({ className, asChild, ...props }: TypographyElementProps<"p">) => { const Comp = asChild ? Slot : "p"; return ( ); }; -const Quote = ({ - className, - asChild, - ...props -}: TypographyElementProps<"blockquote">) => { +const Quote = ({ className, asChild, ...props }: TypographyElementProps<"blockquote">) => { const Comp = asChild ? Slot : "blockquote"; return ( ); @@ -111,11 +92,7 @@ const Ul = ({ className, asChild, ...props }: TypographyElementProps<"ul">) => { const Comp = asChild ? Slot : "ul"; return ( li]:mt-2", - getCommonHelperClass(props), - )} + className={cn(className, "my-6 ml-6 list-disc [&>li]:mt-2", getCommonHelperClass(props))} {...omitCommonHelperProps(props)} /> ); @@ -125,65 +102,44 @@ const Ol = ({ className, asChild, ...props }: TypographyElementProps<"ol">) => { const Comp = asChild ? Slot : "ol"; return ( li]:mt-2", - getCommonHelperClass(props), - )} + className={cn(className, "my-6 ml-6 list-disc [&>li]:mt-2", getCommonHelperClass(props))} {...omitCommonHelperProps(props)} /> ); }; -const Code = ({ - className, - asChild, - ...props -}: TypographyElementProps<"code">) => { - const Comp = asChild ? Slot : "code"; - return ( - - ); -}; +const Code = forwardRef>( + ({ className, asChild, ...props }, ref) => { + const Comp = asChild ? Slot : "code"; + return ( + + ); + }, +); -const Lead = ({ - className, - asChild, - ...props -}: TypographyElementProps<"span">) => { +const Lead = ({ className, asChild, ...props }: TypographyElementProps<"span">) => { const Comp = asChild ? Slot : "span"; return ( ); }; -const LargeText = ({ - className, - asChild, - ...props -}: TypographyElementProps<"span">) => { +const LargeText = ({ className, asChild, ...props }: TypographyElementProps<"span">) => { const Comp = asChild ? Slot : "span"; return ( ); @@ -195,40 +151,24 @@ const SmallText = forwardRef>( return ( ); }, ); -const MutedText = ({ - className, - asChild, - ...props -}: TypographyElementProps<"span">) => { +const MutedText = ({ className, asChild, ...props }: TypographyElementProps<"span">) => { const Comp = asChild ? Slot : "span"; return ( ); }; -const Strong = ({ - className, - asChild, - ...props -}: TypographyElementProps<"span">) => { +const Strong = ({ className, asChild, ...props }: TypographyElementProps<"span">) => { const Comp = asChild ? Slot : "span"; return ( ) => { +const Link = ({ className, asChild, ...props }: TypographyElementProps<"a">) => { const Comp = asChild ? Slot : "a"; return ( ); }; -const DescriptionList = ({ - className, - asChild, - ...props -}: TypographyElementProps<"dl">) => { +const DescriptionList = ({ className, asChild, ...props }: TypographyElementProps<"dl">) => { const Comp = asChild ? Slot : "dl"; return ( ) => { +const DescriptionTerm = ({ className, asChild, ...props }: TypographyElementProps<"dt">) => { const Comp = asChild ? Slot : "dt"; - return ( - - ); + return ; }; -const DescriptionDetails = ({ - className, - asChild, - ...props -}: TypographyElementProps<"dd">) => { +const DescriptionDetails = ({ className, asChild, ...props }: TypographyElementProps<"dd">) => { const Comp = asChild ? Slot : "dd"; return ( ); diff --git a/frontend/packages/components/vite.config.ts b/frontend/packages/components/vite.config.ts index b9da235b79..b7cabe3e90 100644 --- a/frontend/packages/components/vite.config.ts +++ b/frontend/packages/components/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig({ lib: { entry: { index: path.resolve(__dirname, "src/index.ts"), + "code-mirror": path.resolve( + __dirname, + "src/code-mirror/index.tsx", + ), "tailwind-base": path.resolve( __dirname, "src/tailwind-base.ts", diff --git a/sdks/actor/manager-protocol/package.json b/sdks/actor/manager-protocol/package.json index b11b731006..343fa6632d 100644 --- a/sdks/actor/manager-protocol/package.json +++ b/sdks/actor/manager-protocol/package.json @@ -34,10 +34,10 @@ "check-types": "tsc --noEmit" }, "dependencies": { - "@rivet-gg/actor-common": "workspace:*", "zod": "^3.24.1" }, "devDependencies": { + "@rivet-gg/actor-common": "workspace:*", "tsup": "^8.3.5", "typescript": "^5.7.3" }