Skip to content

Commit

Permalink
add module registry
Browse files Browse the repository at this point in the history
  • Loading branch information
jhnnsrs committed Nov 27, 2024
1 parent 132cab2 commit be6bd2b
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 0 deletions.
146 changes: 146 additions & 0 deletions src/hooks/use-metaapp.ts
Original file line number Diff line number Diff line change
@@ -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<T extends { [key: string]: any }> = {
ports: T;
options?: StateOptions;
};

export const buildState = <T extends zod.objectInputType<any, any>>(
args: T,
options?: StateOptions,
): StateDefinition<T> => {
return { ports: args, options };
};

export const buildAction = <
T extends zod.ZodObject<any, any>,
R extends zod.ZodObject<any, any>,
>(
args: T = {} as T,
returns: R = zod.object({}) as R,
options?: ActionOptions,
): ActionDefinition<T, R> => {
return { args, returns, options };
};

export type ActionOptions = {
forceHash: string;
};

export type StateOptions = {
forceHash: string;
};

export type ActionDefinition<
T extends zod.ZodObject<any, any>,
R extends zod.ZodObject<any, any>,
> = {
args: T;
returns: R;
options?: ActionOptions;
};

export type MetaApplication<
States extends { [key: string]: StateDefinition<any> },
Actions extends { [key: string]: ActionDefinition<any, any> },
> = {
states: States;
actions: Actions;
};

export type MetaApplicationAdds<T extends MetaApplication<any, any>> = T & {
useState: <K extends keyof T["states"]>(
state: K,
) => { value?: zod.infer<T["states"][K]["ports"]>; updatedAt?: string };
useAction: <K extends keyof T["actions"]>(
action: K,
) => [
(
args: zod.infer<T["actions"][K]["args"]>,
) => Promise<zod.infer<T["actions"][K]["returns"]>>,
{ loading: boolean; error: any },
];
};

export type AgentContext = {
agent: string;
};

export const UsedAgentContext = React.createContext<AgentContext>({
agent: "",
});

const useAgentContext = () => React.useContext(UsedAgentContext);

const buildUseRekuestState = <T extends MetaApplication<any, any>>(
app: T,
): MetaApplicationAdds<T>["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 = <T extends MetaApplication<any, any>>(
app: T,
): MetaApplicationAdds<T>["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 = <T extends MetaApplication<any, any>>(
app: T,
): MetaApplicationAdds<T> => {
return {
app,
useState: buildUseRekuestState(app),
useAction: buildUseRekuestActions(app),
} as unknown as MetaApplicationAdds<T>;
};
27 changes: 27 additions & 0 deletions src/modules/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Guard } from "@/arkitekt/Arkitekt";
import { Registry } from "./registry";
import { ModuleWrapper } from "./Wrapper";

export const Home = (props: { registry: Registry }) => {
return (
<Guard.Rekuest>
<div className="flex flex-col gap-2">
{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 (
<ModuleWrapper app={module}>
{" "}
<Component />{" "}
</ModuleWrapper>
);
})}
</div>
</Guard.Rekuest>
);
};
58 changes: 58 additions & 0 deletions src/modules/Positioner.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div className="positioner">{value?.position_y.toFixed()}</div>;
<button onClick={() => moveX({ x: 1 })}>
{loading ? "Moving" : "MoveX"}
</button>
</>
);
};

export const PositionerPlaceholder = () => {
return <div className="positioner">Placeholder</div>;
};
29 changes: 29 additions & 0 deletions src/modules/Wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { MetaApplication, UsedAgentContext } from "@/hooks/use-metaapp";
import { useAgentsQuery } from "@/rekuest/api/graphql";

export const ModuleWrapper = (props: {
app: MetaApplication<any, any>;
children: React.ReactNode;
}) => {
const { data } = useAgentsQuery({
variables: {
filters: {
hasStates: props.app.states,
},
},
});

if (!data) {
return <></>;
}

if (data.agents.length === 0) {
return <div>No agents Implementing this</div>;
}

return (
<UsedAgentContext.Provider value={{ agent: data.agents.at(0)?.id || "" }}>
{props.children}
</UsedAgentContext.Provider>
);
};
47 changes: 47 additions & 0 deletions src/modules/registry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { MetaApplication } from "@/hooks/use-metaapp";
import {
Positioner,
PositionerModule,
PositionerPlaceholder,
} from "./Positioner";

export type Registration = {
name: string;
module: MetaApplication<any, any>;
component: React.ComponentType;
placeholder: React.ComponentType;
};

export class Registry {
modules: Map<string, MetaApplication<any, any>>;
components: Map<string, Registration>;

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;
4 changes: 4 additions & 0 deletions src/settings/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -70,6 +72,8 @@ const Page: React.FC<IRepresentationScreenProps> = () => {
/>
</form>
</Form>

<Home registry={registry} />
</PageLayout>
);
};
Expand Down

0 comments on commit be6bd2b

Please sign in to comment.