move app to app/ subdir
This commit is contained in:
35
app/src/state/createSelectors.ts
Normal file
35
app/src/state/createSelectors.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { type StoreApi, type UseBoundStore } from "zustand";
|
||||
|
||||
type NestedSelectors<T> = {
|
||||
[K in keyof T]: T[K] extends object
|
||||
? { [NestedK in keyof T[K]]: () => T[K][NestedK] }
|
||||
: () => T[K];
|
||||
};
|
||||
|
||||
type WithSelectors<S> = S extends { getState: () => infer T }
|
||||
? S & { use: NestedSelectors<T> }
|
||||
: never;
|
||||
|
||||
// Adapted from https://docs.pmnd.rs/zustand/guides/auto-generating-selectors
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
export const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(_store: S) => {
|
||||
const store = _store as WithSelectors<typeof _store>;
|
||||
store.use = {};
|
||||
for (const k of Object.keys(store.getState())) {
|
||||
// @ts-expect-error black magic
|
||||
const stateValue = store.getState()[k];
|
||||
if (typeof stateValue === "object" && stateValue !== null) {
|
||||
(store.use as any)[k] = {};
|
||||
for (const nestedK of Object.keys(stateValue)) {
|
||||
// @ts-expect-error black magic
|
||||
(store.use as any)[k][nestedK] = () => store((s) => s[k][nestedK as keyof (typeof s)[k]]);
|
||||
}
|
||||
} else {
|
||||
(store.use as any)[k] = () => store((s) => s[k as keyof typeof s]);
|
||||
}
|
||||
}
|
||||
|
||||
return store;
|
||||
};
|
||||
99
app/src/state/sharedVariantEditor.slice.ts
Normal file
99
app/src/state/sharedVariantEditor.slice.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { type RouterOutputs } from "~/utils/api";
|
||||
import { type SliceCreator } from "./store";
|
||||
import loader from "@monaco-editor/loader";
|
||||
import formatPromptConstructor from "~/promptConstructor/format";
|
||||
|
||||
export const editorBackground = "#fafafa";
|
||||
|
||||
export type SharedVariantEditorSlice = {
|
||||
monaco: null | ReturnType<typeof loader.__getMonacoInstance>;
|
||||
loadMonaco: () => Promise<void>;
|
||||
scenarios: RouterOutputs["scenarios"]["list"]["scenarios"];
|
||||
updateScenariosModel: () => void;
|
||||
setScenarios: (scenarios: RouterOutputs["scenarios"]["list"]["scenarios"]) => void;
|
||||
};
|
||||
|
||||
export const createVariantEditorSlice: SliceCreator<SharedVariantEditorSlice> = (set, get) => ({
|
||||
monaco: loader.__getMonacoInstance(),
|
||||
loadMonaco: async () => {
|
||||
// We only want to run this client-side
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const [monaco, promptTypes] = await Promise.all([
|
||||
loader.init(),
|
||||
get().api?.client.experiments.promptTypes.query(),
|
||||
]);
|
||||
|
||||
monaco.editor.defineTheme("customTheme", {
|
||||
base: "vs",
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: {
|
||||
"editor.background": editorBackground,
|
||||
},
|
||||
});
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||
allowNonTsExtensions: true,
|
||||
strictNullChecks: true,
|
||||
lib: ["esnext"],
|
||||
});
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
promptTypes ?? "",
|
||||
"file:///PromptTypes.d.ts",
|
||||
);
|
||||
|
||||
monaco.languages.registerDocumentFormattingEditProvider("typescript", {
|
||||
provideDocumentFormattingEdits: async (model) => {
|
||||
return [
|
||||
{
|
||||
range: model.getFullModelRange(),
|
||||
text: await formatPromptConstructor(model.getValue()),
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
set((state) => {
|
||||
state.sharedVariantEditor.monaco = monaco;
|
||||
});
|
||||
get().sharedVariantEditor.updateScenariosModel();
|
||||
},
|
||||
scenarios: [],
|
||||
setScenarios: (scenarios) => {
|
||||
set((state) => {
|
||||
state.sharedVariantEditor.scenarios = scenarios;
|
||||
});
|
||||
|
||||
get().sharedVariantEditor.updateScenariosModel();
|
||||
},
|
||||
|
||||
updateScenariosModel: () => {
|
||||
const monaco = get().sharedVariantEditor.monaco;
|
||||
if (!monaco) return;
|
||||
|
||||
const modelContents = `
|
||||
const scenarios = ${JSON.stringify(
|
||||
get().sharedVariantEditor.scenarios.map((s) => s.variableValues),
|
||||
null,
|
||||
2,
|
||||
)} as const;
|
||||
|
||||
type Scenario = typeof scenarios[number];
|
||||
declare var scenario: Scenario | { [key: string]: string };
|
||||
`;
|
||||
|
||||
const scenariosModel = monaco.editor.getModel(monaco.Uri.parse("file:///scenarios.ts"));
|
||||
|
||||
if (scenariosModel) {
|
||||
scenariosModel.setValue(modelContents);
|
||||
} else {
|
||||
monaco.editor.createModel(
|
||||
modelContents,
|
||||
"typescript",
|
||||
monaco.Uri.parse("file:///scenarios.ts"),
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
45
app/src/state/store.ts
Normal file
45
app/src/state/store.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { type StateCreator, create } from "zustand";
|
||||
import { immer } from "zustand/middleware/immer";
|
||||
import { createSelectors } from "./createSelectors";
|
||||
import {
|
||||
type SharedVariantEditorSlice,
|
||||
createVariantEditorSlice,
|
||||
} from "./sharedVariantEditor.slice";
|
||||
import { type APIClient } from "~/utils/api";
|
||||
|
||||
export type State = {
|
||||
drawerOpen: boolean;
|
||||
openDrawer: () => void;
|
||||
closeDrawer: () => void;
|
||||
api: APIClient | null;
|
||||
setApi: (api: APIClient) => void;
|
||||
sharedVariantEditor: SharedVariantEditorSlice;
|
||||
};
|
||||
|
||||
export type SliceCreator<T> = StateCreator<State, [["zustand/immer", never]], [], T>;
|
||||
|
||||
export type SetFn = Parameters<SliceCreator<unknown>>[0];
|
||||
export type GetFn = Parameters<SliceCreator<unknown>>[1];
|
||||
|
||||
const useBaseStore = create<State, [["zustand/immer", never]]>(
|
||||
immer((set, get, ...rest) => ({
|
||||
api: null,
|
||||
setApi: (api) =>
|
||||
set((state) => {
|
||||
state.api = api;
|
||||
}),
|
||||
|
||||
drawerOpen: false,
|
||||
openDrawer: () =>
|
||||
set((state) => {
|
||||
state.drawerOpen = true;
|
||||
}),
|
||||
closeDrawer: () =>
|
||||
set((state) => {
|
||||
state.drawerOpen = false;
|
||||
}),
|
||||
sharedVariantEditor: createVariantEditorSlice(set, get, ...rest),
|
||||
})),
|
||||
);
|
||||
|
||||
export const useAppStore = createSelectors(useBaseStore);
|
||||
26
app/src/state/sync.tsx
Normal file
26
app/src/state/sync.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useEffect } from "react";
|
||||
import { api } from "~/utils/api";
|
||||
import { useScenarios } from "~/utils/hooks";
|
||||
import { useAppStore } from "./store";
|
||||
|
||||
export function useSyncVariantEditor() {
|
||||
const scenarios = useScenarios();
|
||||
|
||||
useEffect(() => {
|
||||
if (scenarios.data) {
|
||||
useAppStore.getState().sharedVariantEditor.setScenarios(scenarios.data.scenarios);
|
||||
}
|
||||
}, [scenarios.data]);
|
||||
}
|
||||
|
||||
export function SyncAppStore() {
|
||||
const utils = api.useContext();
|
||||
|
||||
const setApi = useAppStore((state) => state.setApi);
|
||||
|
||||
useEffect(() => {
|
||||
setApi(utils);
|
||||
}, [utils, setApi]);
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user