From be6bd2b17e9eb5f017b2ec33c680c16fc94c248a Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 27 Nov 2024 22:18:37 +0100 Subject: [PATCH] add module registry --- src/hooks/use-metaapp.ts | 146 ++++++++++++++++++++++++++++++++ src/modules/Home.tsx | 27 ++++++ src/modules/Positioner.tsx | 58 +++++++++++++ src/modules/Wrapper.tsx | 29 +++++++ src/modules/registry.tsx | 47 ++++++++++ src/settings/pages/HomePage.tsx | 4 + 6 files changed, 311 insertions(+) create mode 100644 src/hooks/use-metaapp.ts create mode 100644 src/modules/Home.tsx create mode 100644 src/modules/Positioner.tsx create mode 100644 src/modules/Wrapper.tsx create mode 100644 src/modules/registry.tsx diff --git a/src/hooks/use-metaapp.ts b/src/hooks/use-metaapp.ts new file mode 100644 index 0000000..53085b9 --- /dev/null +++ b/src/hooks/use-metaapp.ts @@ -0,0 +1,146 @@ +import { useGetStateForQuery } from "@/rekuest/api/graphql"; +import React from "react"; +import zod from "zod"; + +export const ports = zod.object; + +export const port = { + boolean: zod.boolean(), + string: zod.string(), + integer: zod.number(), + number: zod.number(), + structure: zod.string(), + any: zod.any(), +}; + +export type StateDefinition = { + ports: T; + options?: StateOptions; +}; + +export const buildState = >( + args: T, + options?: StateOptions, +): StateDefinition => { + return { ports: args, options }; +}; + +export const buildAction = < + T extends zod.ZodObject, + R extends zod.ZodObject, +>( + args: T = {} as T, + returns: R = zod.object({}) as R, + options?: ActionOptions, +): ActionDefinition => { + return { args, returns, options }; +}; + +export type ActionOptions = { + forceHash: string; +}; + +export type StateOptions = { + forceHash: string; +}; + +export type ActionDefinition< + T extends zod.ZodObject, + R extends zod.ZodObject, +> = { + args: T; + returns: R; + options?: ActionOptions; +}; + +export type MetaApplication< + States extends { [key: string]: StateDefinition }, + Actions extends { [key: string]: ActionDefinition }, +> = { + states: States; + actions: Actions; +}; + +export type MetaApplicationAdds> = T & { + useState: ( + state: K, + ) => { value?: zod.infer; updatedAt?: string }; + useAction: ( + action: K, + ) => [ + ( + args: zod.infer, + ) => Promise>, + { loading: boolean; error: any }, + ]; +}; + +export type AgentContext = { + agent: string; +}; + +export const UsedAgentContext = React.createContext({ + agent: "", +}); + +const useAgentContext = () => React.useContext(UsedAgentContext); + +const buildUseRekuestState = >( + app: T, +): MetaApplicationAdds["useState"] => { + const hook = (state: keyof T["states"]) => { + const { agent } = useAgentContext(); + + const { data } = useGetStateForQuery({ + variables: { + agent, + stateHash: app.states[state].options?.forceHash, + }, + }); + + if (!data) { + return { + value: undefined, + updatedAt: undefined, + }; + } + + return data?.stateFor; + }; + + return hook as any; +}; + +const buildUseRekuestActions = >( + app: T, +): MetaApplicationAdds["useAction"] => { + const hook = (action: keyof T["actions"]) => { + const { agent } = useAgentContext(); + + const doStuff = (args: any) => { + return new Promise((resolve) => { + console.log( + "doStuff", + app.actions[action].options?.forceHash, + args, + agent, + ); + resolve(args); + }); + }; + + return [doStuff, { loading: false, error: null }]; + }; + + return hook as any; +}; + +export const buildModule = >( + app: T, +): MetaApplicationAdds => { + return { + app, + useState: buildUseRekuestState(app), + useAction: buildUseRekuestActions(app), + } as unknown as MetaApplicationAdds; +}; diff --git a/src/modules/Home.tsx b/src/modules/Home.tsx new file mode 100644 index 0000000..d4ead21 --- /dev/null +++ b/src/modules/Home.tsx @@ -0,0 +1,27 @@ +import { Guard } from "@/arkitekt/Arkitekt"; +import { Registry } from "./registry"; +import { ModuleWrapper } from "./Wrapper"; + +export const Home = (props: { registry: Registry }) => { + return ( + +
+ {Array.from(props.registry.modules.keys()).map((key) => { + const Component = props.registry.components.get(key)?.component; + const module = props.registry.components.get(key)?.module; + + if (!Component || !module) { + return <>FAULTY; + } + + return ( + + {" "} + {" "} + + ); + })} +
+
+ ); +}; diff --git a/src/modules/Positioner.tsx b/src/modules/Positioner.tsx new file mode 100644 index 0000000..145504f --- /dev/null +++ b/src/modules/Positioner.tsx @@ -0,0 +1,58 @@ +import { + buildAction, + buildModule, + buildState, + port, + ports, +} from "@/hooks/use-metaapp"; + +export const PositionerModule = buildModule({ + states: { + positioner: buildState( + ports({ + position_x: port.number, + position_y: port.integer, + position_z: port.number, + stream: port.structure, + }), + { forceHash: "positioner" }, + ), + }, + actions: { + moveX: buildAction( + ports({ + x: port.number, + }), + ports({ + x: port.number, + }), + ), + moveY: buildAction( + ports({ + y: port.number, + }), + ports({ + y: port.number, + }), + ), + }, +}); + +export const Positioner = () => { + const { value } = PositionerModule.useState("positioner"); + + const [moveX, { loading }] = PositionerModule.useAction("moveX"); + + return ( + <> +
{value?.position_y.toFixed()}
; + + + ); +}; + +export const PositionerPlaceholder = () => { + return
Placeholder
; +}; diff --git a/src/modules/Wrapper.tsx b/src/modules/Wrapper.tsx new file mode 100644 index 0000000..9738f5a --- /dev/null +++ b/src/modules/Wrapper.tsx @@ -0,0 +1,29 @@ +import { MetaApplication, UsedAgentContext } from "@/hooks/use-metaapp"; +import { useAgentsQuery } from "@/rekuest/api/graphql"; + +export const ModuleWrapper = (props: { + app: MetaApplication; + children: React.ReactNode; +}) => { + const { data } = useAgentsQuery({ + variables: { + filters: { + hasStates: props.app.states, + }, + }, + }); + + if (!data) { + return <>; + } + + if (data.agents.length === 0) { + return
No agents Implementing this
; + } + + return ( + + {props.children} + + ); +}; diff --git a/src/modules/registry.tsx b/src/modules/registry.tsx new file mode 100644 index 0000000..f2009e4 --- /dev/null +++ b/src/modules/registry.tsx @@ -0,0 +1,47 @@ +import { MetaApplication } from "@/hooks/use-metaapp"; +import { + Positioner, + PositionerModule, + PositionerPlaceholder, +} from "./Positioner"; + +export type Registration = { + name: string; + module: MetaApplication; + component: React.ComponentType; + placeholder: React.ComponentType; +}; + +export class Registry { + modules: Map>; + components: Map; + + constructor() { + this.modules = new Map(); + this.components = new Map(); + } + + register(register: Registration) { + this.modules.set(register.name, register.module); + this.components.set(register.name, register); + } + + getModule(name: string) { + return this.modules.get(name); + } + + getComponent(name: string) { + return this.components.get(name); + } +} + +const registry = new Registry(); + +registry.register({ + name: "Positioner", + module: PositionerModule, + component: Positioner, + placeholder: PositionerPlaceholder, +}); + +export default registry; diff --git a/src/settings/pages/HomePage.tsx b/src/settings/pages/HomePage.tsx index c40253d..fd25cbf 100644 --- a/src/settings/pages/HomePage.tsx +++ b/src/settings/pages/HomePage.tsx @@ -3,6 +3,8 @@ import { SwitchField } from "@/components/fields/SwitchField"; import { PageLayout } from "@/components/layout/PageLayout"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Form } from "@/components/ui/form"; +import { Home } from "@/modules/Home"; +import registry from "@/modules/registry"; import { useSettings } from "@/providers/settings/SettingsContext"; import deepEqual from "deep-equal"; import React, { useEffect } from "react"; @@ -70,6 +72,8 @@ const Page: React.FC = () => { /> + + ); };