mirror of
https://github.com/morten-olsen/bob.git
synced 2026-02-08 01:46:29 +01:00
docs: improved webpage
This commit is contained in:
1
.npmrc
1
.npmrc
@@ -1,4 +1,3 @@
|
|||||||
node-linker=hoisted
|
|
||||||
public-hoist-pattern[]=*@nextui-org/*
|
public-hoist-pattern[]=*@nextui-org/*
|
||||||
store-dir=.pnpm-store
|
store-dir=.pnpm-store
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ export {
|
|||||||
type CalulationResult,
|
type CalulationResult,
|
||||||
type PlanableWithPlugins,
|
type PlanableWithPlugins,
|
||||||
} from './algorithm/calulation';
|
} from './algorithm/calulation';
|
||||||
export { plugins } from './plugins/index';
|
export { plugins, type AllPlugins } from './plugins';
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ const plugins = {
|
|||||||
capabilities,
|
capabilities,
|
||||||
} satisfies Record<string, (...args: any[]) => Plugin>;
|
} satisfies Record<string, (...args: any[]) => Plugin>;
|
||||||
|
|
||||||
|
type AllPlugins = {
|
||||||
|
[K in keyof typeof plugins]: ReturnType<(typeof plugins)[K]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type { AllPlugins };
|
||||||
export { plugins };
|
export { plugins };
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Expand, UnionToIntersection } from '../types/utils';
|
||||||
import { GraphNode } from './node';
|
import { GraphNode } from './node';
|
||||||
import { Planable } from './planable';
|
import { Planable } from './planable';
|
||||||
|
|
||||||
@@ -20,16 +21,20 @@ type Plugin<TAttributes = any, TContext = any> = {
|
|||||||
|
|
||||||
type Plugins = Record<string, Plugin>;
|
type Plugins = Record<string, Plugin>;
|
||||||
|
|
||||||
type PluginAttributes<TPlugins extends Plugins> = {
|
type PluginAttributes<TPlugins extends Plugins> = MergeRecords<{
|
||||||
[K in keyof TPlugins]: TPlugins[K] extends Plugin<infer TAttributes, any>
|
[K in keyof TPlugins]: TPlugins[K] extends Plugin<infer TAttributes, any>
|
||||||
? TAttributes
|
? TAttributes
|
||||||
: never;
|
: never;
|
||||||
}[keyof TPlugins];
|
}>;
|
||||||
|
|
||||||
type PluginContext<TPlugins extends Plugins> = {
|
type MergeRecords<T extends Record<string, any>> = Expand<
|
||||||
|
UnionToIntersection<T[keyof T]>
|
||||||
|
>;
|
||||||
|
|
||||||
|
type PluginContext<TPlugins extends Plugins> = MergeRecords<{
|
||||||
[K in keyof TPlugins]: TPlugins[K] extends Plugin<any, infer TContext>
|
[K in keyof TPlugins]: TPlugins[K] extends Plugin<any, infer TContext>
|
||||||
? TContext
|
? TContext
|
||||||
: never;
|
: never;
|
||||||
}[keyof TPlugins];
|
}>;
|
||||||
|
|
||||||
export type { Plugin, Plugins, PluginAttributes, PluginContext };
|
export type { Plugin, Plugins, PluginAttributes, PluginContext };
|
||||||
|
|||||||
17
packages/algorithm/src/types/utils.ts
Normal file
17
packages/algorithm/src/types/utils.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// expands object types one level deep
|
||||||
|
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
||||||
|
|
||||||
|
// expands object types recursively
|
||||||
|
type ExpandRecursively<T> = T extends object
|
||||||
|
? T extends infer O
|
||||||
|
? { [K in keyof O]: ExpandRecursively<O[K]> }
|
||||||
|
: never
|
||||||
|
: T;
|
||||||
|
|
||||||
|
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
||||||
|
k: infer I,
|
||||||
|
) => void
|
||||||
|
? I
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type { Expand, ExpandRecursively, UnionToIntersection };
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link href="/src/index.css" rel="stylesheet" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Vite + React + TS</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="dark text-foreground bg-background">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
"framer-motion": "^10.16.4",
|
"framer-motion": "^10.16.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-json-tree": "^0.18.0",
|
||||||
|
"react-router-dom": "^6.16.0",
|
||||||
"tailwindcss": "^3.3.3"
|
"tailwindcss": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { NextUIProvider } from '@nextui-org/react';
|
import { NextUIProvider } from '@nextui-org/react';
|
||||||
import { Page } from './containers/experiment';
|
import { Router } from './router';
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
|
<div className="dark text-foreground bg-background">
|
||||||
<NextUIProvider>
|
<NextUIProvider>
|
||||||
<Page slug=".capabilities" />
|
<Router />
|
||||||
</NextUIProvider>
|
</NextUIProvider>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
13
packages/playground/src/components/content/index.tsx
Normal file
13
packages/playground/src/components/content/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
type ContentProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Content: React.FC<ContentProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div className="max-w-3xl mx-auto w-full">
|
||||||
|
<div className="flex-1 p-8">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Content };
|
||||||
41
packages/playground/src/components/frame/index.tsx
Normal file
41
packages/playground/src/components/frame/index.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
Navbar,
|
||||||
|
NavbarBrand,
|
||||||
|
NavbarContent,
|
||||||
|
NavbarItem,
|
||||||
|
Link,
|
||||||
|
} from '@nextui-org/react';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
type FrameProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Frame = ({ children }: FrameProps) => {
|
||||||
|
return (
|
||||||
|
<div className="h-screen flex flex-col">
|
||||||
|
<Navbar shouldHideOnScroll isBordered isBlurred>
|
||||||
|
<NavbarBrand>
|
||||||
|
<RouterLink to="/">
|
||||||
|
<p className="font-bold text-inherit">Bob</p>
|
||||||
|
</RouterLink>
|
||||||
|
</NavbarBrand>
|
||||||
|
<NavbarContent className="hidden sm:flex gap-4" justify="center">
|
||||||
|
<NavbarItem>
|
||||||
|
<RouterLink to="/">
|
||||||
|
<Link color="foreground">Home</Link>
|
||||||
|
</RouterLink>
|
||||||
|
</NavbarItem>
|
||||||
|
<NavbarItem>
|
||||||
|
<RouterLink to="/experiments">
|
||||||
|
<Link color="foreground">Experiments</Link>
|
||||||
|
</RouterLink>
|
||||||
|
</NavbarItem>
|
||||||
|
</NavbarContent>
|
||||||
|
</Navbar>
|
||||||
|
<div className="flex flex-col flex-1">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Frame };
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
useDisclosure,
|
||||||
|
Badge,
|
||||||
|
Checkbox,
|
||||||
|
Input,
|
||||||
|
} from '@nextui-org/react';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { useExperimentResult } from '../../../features/experiment';
|
||||||
|
import { useSelectNode } from '../../../features/experiment/hooks';
|
||||||
|
import { GraphNode } from '@bob-the-algorithm/core';
|
||||||
|
|
||||||
|
const FindNodeView = () => {
|
||||||
|
const data = useExperimentResult();
|
||||||
|
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||||
|
const [completed, setCompleted] = useState(true);
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
const selectNode = useSelectNode();
|
||||||
|
|
||||||
|
const nodes = useMemo(() => {
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let result = [...data.nodes];
|
||||||
|
if (completed) {
|
||||||
|
result = result.filter((node) => node.completed);
|
||||||
|
}
|
||||||
|
if (text) {
|
||||||
|
result = result.filter((node) => node.id === text);
|
||||||
|
}
|
||||||
|
return result.sort((a, b) => b.score - a.score).slice(0, 10);
|
||||||
|
}, [data, completed, text]);
|
||||||
|
|
||||||
|
const getColor = (node: GraphNode) => {
|
||||||
|
if (node.completed) {
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
if (node.deadEnd) {
|
||||||
|
return 'danger';
|
||||||
|
}
|
||||||
|
return 'primary';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onPress={onOpen}>Find node</Button>
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
scrollBehavior="inside"
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
{(close) => (
|
||||||
|
<>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">Nodes</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Input
|
||||||
|
placeholder="Node ID"
|
||||||
|
value={text}
|
||||||
|
onValueChange={setText}
|
||||||
|
/>
|
||||||
|
<Checkbox isSelected={completed} onValueChange={setCompleted}>
|
||||||
|
Completed
|
||||||
|
</Checkbox>
|
||||||
|
<div className="flex gap-4 flex-wrap">
|
||||||
|
{nodes.map((node) => (
|
||||||
|
<Badge
|
||||||
|
content={node.score}
|
||||||
|
color={getColor(node)}
|
||||||
|
key={node.id}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
selectNode(node);
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
key={node.id}
|
||||||
|
>
|
||||||
|
{node.id}
|
||||||
|
</Button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { FindNodeView };
|
||||||
32
packages/playground/src/containers/experiment/graph.tsx
Normal file
32
packages/playground/src/containers/experiment/graph.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalBody,
|
||||||
|
Button,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@nextui-org/react';
|
||||||
|
import { Graph } from '../../presenters/graph';
|
||||||
|
|
||||||
|
const GraphView = () => {
|
||||||
|
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onPress={onOpen}>Show graph</Button>
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="full">
|
||||||
|
<ModalContent>
|
||||||
|
{() => (
|
||||||
|
<>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">Graph</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Graph />
|
||||||
|
</ModalBody>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { GraphView };
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { experiments } from '../../utils/experiments';
|
|
||||||
import { ExperimentProvider } from '../../features/experiment/context';
|
import { ExperimentProvider } from '../../features/experiment/context';
|
||||||
import { ExperimentView } from './view';
|
import { ExperimentView } from './view';
|
||||||
|
import { ExperimentInfo } from '../../features/experiment/types';
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
slug: string;
|
content: () => Promise<{ default: Experiment }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Experiment = {
|
type Experiment = {
|
||||||
worker: () => Worker;
|
info: ExperimentInfo;
|
||||||
view: React.ReactElement;
|
view: React.ComponentType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Page: React.FC<PageProps> = ({ slug }) => {
|
const ExperimentPage: React.FC<PageProps> = ({ content }) => {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<unknown>();
|
const [error, setError] = useState<unknown>();
|
||||||
const [experiment, setExperiment] = useState<Experiment>();
|
const [experiment, setExperiment] = useState<Experiment>();
|
||||||
@@ -22,11 +22,9 @@ const Page: React.FC<PageProps> = ({ slug }) => {
|
|||||||
setError(undefined);
|
setError(undefined);
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
try {
|
try {
|
||||||
const page = experiments.find((page) => page.slug === slug);
|
const { default: next } = (await content()) as {
|
||||||
if (!page) {
|
default: Experiment;
|
||||||
throw new Error(`Page not found: ${slug}`);
|
};
|
||||||
}
|
|
||||||
const next = (await page.loader()) as Experiment;
|
|
||||||
console.log('n', next);
|
console.log('n', next);
|
||||||
setExperiment(next);
|
setExperiment(next);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -37,7 +35,7 @@ const Page: React.FC<PageProps> = ({ slug }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
load();
|
load();
|
||||||
}, [slug]);
|
}, [content]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>Error: {error.toString()}</div>;
|
return <div>Error: {error.toString()}</div>;
|
||||||
@@ -48,10 +46,22 @@ const Page: React.FC<PageProps> = ({ slug }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExperimentProvider worker={experiment.worker}>
|
<ExperimentProvider experimentInfo={experiment.info}>
|
||||||
<ExperimentView>{experiment.view}</ExperimentView>
|
<ExperimentView>
|
||||||
|
<experiment.view />
|
||||||
|
</ExperimentView>
|
||||||
</ExperimentProvider>
|
</ExperimentProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Page };
|
const pageImports = import.meta.glob('../../experiments/*/index.tsx');
|
||||||
|
|
||||||
|
const experiments: {
|
||||||
|
path: string;
|
||||||
|
element: JSX.Element;
|
||||||
|
}[] = Object.entries(pageImports).map(([path, page]) => ({
|
||||||
|
path: path.replace('../../experiments/', '').replace('/index.tsx', ''),
|
||||||
|
element: <ExperimentPage content={page as any} />,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export { experiments };
|
||||||
|
|||||||
116
packages/playground/src/containers/experiment/input/index.tsx
Normal file
116
packages/playground/src/containers/experiment/input/index.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalBody,
|
||||||
|
Button,
|
||||||
|
useDisclosure,
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
CardHeader,
|
||||||
|
} from '@nextui-org/react';
|
||||||
|
import { useExperimentResult } from '../../../features/experiment';
|
||||||
|
import { formatTime } from '../../../utils/time';
|
||||||
|
|
||||||
|
const InputView = () => {
|
||||||
|
const data = useExperimentResult();
|
||||||
|
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onPress={onOpen}>Show input</Button>
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
scrollBehavior="inside"
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">Input</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
{data.planables.map((item) => {
|
||||||
|
const description: [string, string][] = [];
|
||||||
|
|
||||||
|
if (item.start) {
|
||||||
|
if (item.start.min === item.start.max) {
|
||||||
|
description.push(['start', formatTime(item.start.min)]);
|
||||||
|
} else {
|
||||||
|
const min = formatTime(item.start.min);
|
||||||
|
const max = formatTime(item.start.max);
|
||||||
|
description.push(['start', `${min} - ${max}`]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.count || 0 > 1) {
|
||||||
|
description.push(['count', item.count?.toString() || '']);
|
||||||
|
}
|
||||||
|
if (item.attributes.locations) {
|
||||||
|
description.push([
|
||||||
|
'locations',
|
||||||
|
item.attributes.locations.join(', '),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (item.attributes.capabilities?.requires) {
|
||||||
|
description.push([
|
||||||
|
'requires',
|
||||||
|
item.attributes.capabilities.requires.join(', '),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.attributes.capabilities?.perhibits) {
|
||||||
|
description.push([
|
||||||
|
'prohibits',
|
||||||
|
item.attributes.capabilities.perhibits.join(', '),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.attributes.capabilities?.provides) {
|
||||||
|
description.push([
|
||||||
|
'provides',
|
||||||
|
item.attributes.capabilities.provides.join(', '),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.attributes.capabilities?.consumes) {
|
||||||
|
description.push([
|
||||||
|
'consumes',
|
||||||
|
item.attributes.capabilities.consumes.join(', '),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
description.push([
|
||||||
|
'duration',
|
||||||
|
`${item.duration / 1000 / 60} minutes`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>{item.id}</CardHeader>
|
||||||
|
<CardBody>
|
||||||
|
<div>
|
||||||
|
{description.map((d) => (
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<span className="font-bold">{d[0]}: </span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{d[1]}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { InputView };
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { Accordion, AccordionItem } from '@nextui-org/react';
|
|
||||||
import { useSelectedNode } from '../../features/experiment/hooks';
|
|
||||||
import { Plan } from '../../presenters/plan';
|
|
||||||
|
|
||||||
const NodeView = () => {
|
|
||||||
const node = useSelectedNode();
|
|
||||||
if (!node) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>{node.id}</h1>
|
|
||||||
<Accordion>
|
|
||||||
<AccordionItem title="Plan" key="plan">
|
|
||||||
<Plan node={node} />
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem title="Context" key="context">
|
|
||||||
<pre>{JSON.stringify(node.context, null, 2)}</pre>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { NodeView };
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { GraphNode } from '@bob-the-algorithm/core';
|
||||||
|
import { Modal, ModalBody, ModalContent, ModalHeader } from '@nextui-org/react';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import { JSONTree } from 'react-json-tree';
|
||||||
|
import { useExperimentResult } from '../../../features/experiment';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
type NodeDetailsViewProps = {
|
||||||
|
node?: GraphNode;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NodeDetailsInfo = ({ node }: { node: GraphNode }) => {
|
||||||
|
const data = useExperimentResult();
|
||||||
|
const start = format(new Date(node.time), 'HH:mm');
|
||||||
|
const planable = useMemo(() => {
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return node.planable
|
||||||
|
? data.planables.find((n) => n.id === node.planable)
|
||||||
|
: null;
|
||||||
|
}, [node, data]);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>{node.id}</h1>
|
||||||
|
<p>{start}</p>
|
||||||
|
<pre></pre>
|
||||||
|
<JSONTree data={node.context} />
|
||||||
|
{planable && <JSONTree data={planable} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NodeDetailsView = ({ node, onClose }: NodeDetailsViewProps) => {
|
||||||
|
return (
|
||||||
|
<Modal size="3xl" isOpen={!!node} onClose={onClose}>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Item details</ModalHeader>
|
||||||
|
<ModalBody>{node && <NodeDetailsInfo node={node} />}</ModalBody>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { NodeDetailsView };
|
||||||
44
packages/playground/src/containers/experiment/node/index.tsx
Normal file
44
packages/playground/src/containers/experiment/node/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Modal, ModalBody, ModalContent, ModalHeader } from '@nextui-org/react';
|
||||||
|
import { GraphNode } from '@bob-the-algorithm/core';
|
||||||
|
import {
|
||||||
|
useSelectNode,
|
||||||
|
useSelectedNode,
|
||||||
|
} from '../../../features/experiment/hooks';
|
||||||
|
import { Plan } from '../../../presenters/plan';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { NodeDetailsView } from './details';
|
||||||
|
import { HorizontalPlan } from '../../../presenters/horizontal-plan';
|
||||||
|
|
||||||
|
const NodeView = () => {
|
||||||
|
const node = useSelectedNode();
|
||||||
|
const selectNode = useSelectNode();
|
||||||
|
const [selectedItem, setSelectedItem] = useState<GraphNode<any>>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
isOpen={!!node}
|
||||||
|
scrollBehavior="inside"
|
||||||
|
onClose={() => selectNode(undefined)}
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Node</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
{node && (
|
||||||
|
<>
|
||||||
|
<HorizontalPlan node={node} />
|
||||||
|
<Plan node={node} onSelect={setSelectedItem} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
<NodeDetailsView
|
||||||
|
node={selectedItem}
|
||||||
|
onClose={() => setSelectedItem(undefined)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { NodeView };
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { useExperimentResult } from '../../features/experiment';
|
|
||||||
import { useSelectNode } from '../../features/experiment/hooks';
|
|
||||||
|
|
||||||
const NodesView = () => {
|
|
||||||
const data = useExperimentResult();
|
|
||||||
const selectNode = useSelectNode();
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{data.completed.map((node) => (
|
|
||||||
<div onClick={() => selectNode(node)} key={node.id}>
|
|
||||||
{node.id} {node.score}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { NodesView };
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useExperimentResult } from '../../../features/experiment';
|
||||||
|
import { useExperimentDuration } from '../../../features/experiment/hooks';
|
||||||
|
|
||||||
|
const Stats = () => {
|
||||||
|
const data = useExperimentResult();
|
||||||
|
const duration = useExperimentDuration();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div>Nodes: {data?.nodes.length}</div>
|
||||||
|
<div>Completed: {data?.completed.length}</div>
|
||||||
|
<div>Duration: {duration}ms</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Stats };
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import { Graph } from '../../presenters/graph';
|
import { Content } from '../../components/content';
|
||||||
|
import { Frame } from '../../components/frame';
|
||||||
|
import { FindNodeView } from './find-node';
|
||||||
|
import { GraphView } from './graph';
|
||||||
|
import { InputView } from './input';
|
||||||
import { NodeView } from './node';
|
import { NodeView } from './node';
|
||||||
import { NodesView } from './nodes';
|
import { Stats } from './stats';
|
||||||
|
|
||||||
type ExperimentViewProps = {
|
type ExperimentViewProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -8,11 +12,23 @@ type ExperimentViewProps = {
|
|||||||
|
|
||||||
const ExperimentView: React.FC<ExperimentViewProps> = ({ children }) => {
|
const ExperimentView: React.FC<ExperimentViewProps> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<Frame>
|
||||||
<Graph />
|
<div className="flex flex-row h-full">
|
||||||
<NodesView />
|
<div className="flex flex-col flex-1 h-full">
|
||||||
|
<div className="flex-1">
|
||||||
|
<Content>{children}</Content>
|
||||||
|
</div>
|
||||||
|
<div className="flex-initial p-2 flex gap-2 items-center">
|
||||||
|
<GraphView />
|
||||||
|
<InputView />
|
||||||
|
<FindNodeView />
|
||||||
|
<div className="flex-1" />
|
||||||
|
<Stats />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<NodeView />
|
<NodeView />
|
||||||
</>
|
</div>
|
||||||
|
</Frame>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
15
packages/playground/src/containers/page/index.tsx
Normal file
15
packages/playground/src/containers/page/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Page } from './page';
|
||||||
|
|
||||||
|
const pageImports = import.meta.glob('../../pages/**/*.tsx');
|
||||||
|
|
||||||
|
const pages: any = Object.entries(pageImports).map(([path, page]) => ({
|
||||||
|
path: path
|
||||||
|
.replace('../../pages/', '')
|
||||||
|
.replace('index.tsx', '')
|
||||||
|
.replace('.tsx', ''),
|
||||||
|
element: <Page content={page as any} />,
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('pages', pages);
|
||||||
|
|
||||||
|
export { pages };
|
||||||
38
packages/playground/src/containers/page/page.tsx
Normal file
38
packages/playground/src/containers/page/page.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Frame } from '../../components/frame';
|
||||||
|
import { Skeleton } from '@nextui-org/react';
|
||||||
|
import { Content } from '../../components/content';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
content: () => Promise<{ Page: (props: any) => JSX.Element }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = ({ content }: Props) => {
|
||||||
|
const [Component, setComponent] = useState<React.ComponentType>();
|
||||||
|
useEffect(() => {
|
||||||
|
const run = async () => {
|
||||||
|
const component = await content();
|
||||||
|
setComponent(() => component.Page);
|
||||||
|
};
|
||||||
|
run();
|
||||||
|
}, [content]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Frame>
|
||||||
|
<Content>
|
||||||
|
{!!Component ? (
|
||||||
|
<article className="my-10">
|
||||||
|
<Component />
|
||||||
|
</article>
|
||||||
|
) : (
|
||||||
|
<div className="w-full flex flex-col gap-2">
|
||||||
|
<Skeleton className="h-3 w-3/5 rounded-lg" />
|
||||||
|
<Skeleton className="h-3 w-4/5 rounded-lg" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Content>
|
||||||
|
</Frame>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Page };
|
||||||
163
packages/playground/src/experiments/capabilities/experiment.ts
Normal file
163
packages/playground/src/experiments/capabilities/experiment.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { ExperimentInfo } from '../../features/experiment/types';
|
||||||
|
|
||||||
|
const MIN = 1000 * 60;
|
||||||
|
const HOUR = 1000 * 60 * 60;
|
||||||
|
|
||||||
|
const info: ExperimentInfo = {
|
||||||
|
start: 0,
|
||||||
|
planables: [
|
||||||
|
{
|
||||||
|
id: `Brush teeth`,
|
||||||
|
duration: 2 * MIN,
|
||||||
|
start: {
|
||||||
|
min: 7 * HOUR,
|
||||||
|
max: 8 * HOUR,
|
||||||
|
},
|
||||||
|
attributes: {
|
||||||
|
locations: ['home'],
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Drop off kids',
|
||||||
|
duration: 30 * MIN,
|
||||||
|
attributes: {
|
||||||
|
locations: ['daycare'],
|
||||||
|
capabilities: {
|
||||||
|
consumes: ['kids'],
|
||||||
|
requires: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
start: {
|
||||||
|
min: 7 * HOUR,
|
||||||
|
max: 9 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'visit zoo',
|
||||||
|
duration: 1 * HOUR,
|
||||||
|
attributes: {
|
||||||
|
locations: ['zoo'],
|
||||||
|
capabilities: {
|
||||||
|
requires: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
start: {
|
||||||
|
min: 10 * HOUR,
|
||||||
|
max: 14 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Pickup the kids',
|
||||||
|
duration: 30 * MIN,
|
||||||
|
attributes: {
|
||||||
|
locations: ['daycare'],
|
||||||
|
capabilities: {
|
||||||
|
provides: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
start: {
|
||||||
|
min: 10 * HOUR,
|
||||||
|
max: 15.5 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Do work',
|
||||||
|
duration: 1 * HOUR,
|
||||||
|
count: 5,
|
||||||
|
attributes: {
|
||||||
|
locations: ['work'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 10,
|
||||||
|
start: {
|
||||||
|
min: 8 * HOUR,
|
||||||
|
max: 18 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'put kids to bed',
|
||||||
|
duration: 30 * MIN,
|
||||||
|
attributes: {
|
||||||
|
locations: ['home'],
|
||||||
|
capabilities: {
|
||||||
|
consumes: ['kids'],
|
||||||
|
requires: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Read book',
|
||||||
|
duration: 0.5 * HOUR,
|
||||||
|
attributes: {
|
||||||
|
locations: ['home', 'work'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 3,
|
||||||
|
count: 2,
|
||||||
|
start: {
|
||||||
|
min: 8 * HOUR,
|
||||||
|
max: 22 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Meditate',
|
||||||
|
duration: 10 * MIN,
|
||||||
|
score: 1,
|
||||||
|
attributes: {
|
||||||
|
locations: ['home', 'work'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
min: 8 * HOUR,
|
||||||
|
max: 22 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Meeting 1',
|
||||||
|
duration: 1 * HOUR,
|
||||||
|
attributes: {
|
||||||
|
locations: ['work', 'work'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 10,
|
||||||
|
start: {
|
||||||
|
min: 10 * HOUR,
|
||||||
|
max: 10 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Play playstation',
|
||||||
|
duration: 1 * HOUR,
|
||||||
|
attributes: {
|
||||||
|
locations: ['home'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 10,
|
||||||
|
start: {
|
||||||
|
min: 16 * HOUR,
|
||||||
|
max: 24 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
context: {
|
||||||
|
location: 'home',
|
||||||
|
capabilities: ['kids'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { info };
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
const Foo = () => {
|
import { info } from './experiment';
|
||||||
return <div></div>;
|
|
||||||
|
export default {
|
||||||
|
info,
|
||||||
|
view: () => <>Hello</>,
|
||||||
};
|
};
|
||||||
|
|
||||||
const worker = () =>
|
|
||||||
new Worker(new URL('./script.ts', import.meta.url), {
|
|
||||||
type: 'module',
|
|
||||||
});
|
|
||||||
|
|
||||||
const view = <Foo />;
|
|
||||||
|
|
||||||
export { worker, view };
|
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
import { Bob, plugins } from '@bob-the-algorithm/core';
|
|
||||||
import { createWorker } from '../../features/experiment/worker';
|
|
||||||
|
|
||||||
const MIN = 1000 * 60;
|
|
||||||
const HOUR = 1000 * 60 * 60;
|
|
||||||
|
|
||||||
const getTravelTime = async () => 30 * MIN;
|
|
||||||
const transport = plugins.transport({
|
|
||||||
getTravelTime,
|
|
||||||
});
|
|
||||||
|
|
||||||
const realistic = async () => {
|
|
||||||
try {
|
|
||||||
const bob = new Bob({
|
|
||||||
plugins: { transport, capabilities: plugins.capabilities() },
|
|
||||||
});
|
|
||||||
const result = await bob.run({
|
|
||||||
context: {
|
|
||||||
location: 'home',
|
|
||||||
capabilities: ['kids'],
|
|
||||||
},
|
|
||||||
start: 0,
|
|
||||||
heuristic: ({ completed }) => completed.length >= 30,
|
|
||||||
planables: [
|
|
||||||
{
|
|
||||||
id: `Brush teeth`,
|
|
||||||
duration: 2 * MIN,
|
|
||||||
start: {
|
|
||||||
min: 7 * HOUR,
|
|
||||||
max: 8 * HOUR,
|
|
||||||
},
|
|
||||||
attributes: {
|
|
||||||
locations: ['home'],
|
|
||||||
},
|
|
||||||
score: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Drop off kids',
|
|
||||||
duration: 30 * MIN,
|
|
||||||
attributes: {
|
|
||||||
locations: ['daycare'],
|
|
||||||
capabilities: {
|
|
||||||
consumes: ['kids'],
|
|
||||||
requires: ['kids'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
score: 1,
|
|
||||||
start: {
|
|
||||||
min: 7 * HOUR,
|
|
||||||
max: 9 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'visit zoo',
|
|
||||||
duration: 1 * HOUR,
|
|
||||||
attributes: {
|
|
||||||
locations: ['zoo'],
|
|
||||||
capabilities: {
|
|
||||||
requires: ['kids'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
score: 1,
|
|
||||||
start: {
|
|
||||||
min: 10 * HOUR,
|
|
||||||
max: 14 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Pickup the kids',
|
|
||||||
duration: 30 * MIN,
|
|
||||||
attributes: {
|
|
||||||
locations: ['daycare'],
|
|
||||||
capabilities: {
|
|
||||||
provides: ['kids'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
score: 1,
|
|
||||||
start: {
|
|
||||||
min: 10 * HOUR,
|
|
||||||
max: 15.5 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Do work',
|
|
||||||
duration: 1 * HOUR,
|
|
||||||
count: 5,
|
|
||||||
attributes: {
|
|
||||||
locations: ['work'],
|
|
||||||
capabilities: {
|
|
||||||
perhibits: ['kids'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
score: 10,
|
|
||||||
start: {
|
|
||||||
min: 8 * HOUR,
|
|
||||||
max: 18 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'put kids to bed',
|
|
||||||
duration: 30 * MIN,
|
|
||||||
attributes: {
|
|
||||||
locations: ['home'],
|
|
||||||
capabilities: {
|
|
||||||
consumes: ['kids'],
|
|
||||||
requires: ['kids'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
score: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Read book',
|
|
||||||
duration: 0.5 * HOUR,
|
|
||||||
attributes: {
|
|
||||||
locations: ['home', 'work'],
|
|
||||||
capabilities: {
|
|
||||||
perhibits: ['kids'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
score: 3,
|
|
||||||
count: 2,
|
|
||||||
start: {
|
|
||||||
min: 8 * HOUR,
|
|
||||||
max: 22 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Meditate',
|
|
||||||
duration: 10 * MIN,
|
|
||||||
score: 1,
|
|
||||||
attributes: {
|
|
||||||
locations: ['home', 'work'],
|
|
||||||
capabilities: {
|
|
||||||
perhibits: ['kids'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
start: {
|
|
||||||
min: 8 * HOUR,
|
|
||||||
max: 22 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Meeting 1',
|
|
||||||
duration: 1 * HOUR,
|
|
||||||
attributes: {
|
|
||||||
locations: ['work', 'work'],
|
|
||||||
capabilities: {
|
|
||||||
perhibits: ['kids'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
score: 10,
|
|
||||||
start: {
|
|
||||||
min: 10 * HOUR,
|
|
||||||
max: 10 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Play playstation',
|
|
||||||
duration: 1 * HOUR,
|
|
||||||
attributes: {
|
|
||||||
locations: ['home'],
|
|
||||||
capabilities: {
|
|
||||||
perhibits: ['kids'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
score: 10,
|
|
||||||
start: {
|
|
||||||
min: 16 * HOUR,
|
|
||||||
max: 24 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createWorker(realistic);
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Hello
|
||||||
|
|
||||||
|
world
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
const Foo = () => {
|
|
||||||
return <div></div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const worker = () =>
|
|
||||||
new Worker(new URL('./script.ts', import.meta.url), {
|
|
||||||
type: 'module',
|
|
||||||
});
|
|
||||||
|
|
||||||
const view = <Foo />;
|
|
||||||
|
|
||||||
export { worker, view };
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
import { Bob, plugins } from '@bob-the-algorithm/core';
|
|
||||||
import { createWorker } from '../../features/experiment/worker';
|
|
||||||
|
|
||||||
const MIN = 1000 * 60;
|
|
||||||
const HOUR = 1000 * 60 * 60;
|
|
||||||
|
|
||||||
const getTravelTime = async () => 30 * MIN;
|
|
||||||
const transport = plugins.transport({
|
|
||||||
getTravelTime,
|
|
||||||
});
|
|
||||||
|
|
||||||
const realistic = async () => {
|
|
||||||
try {
|
|
||||||
const bob = new Bob({
|
|
||||||
plugins: { transport, capabilities: plugins.capabilities() },
|
|
||||||
});
|
|
||||||
const result = await bob.run({
|
|
||||||
context: {
|
|
||||||
location: 'home',
|
|
||||||
},
|
|
||||||
start: 0,
|
|
||||||
heuristic: ({ completed }) => completed.length >= 3,
|
|
||||||
planables: [
|
|
||||||
{
|
|
||||||
id: `Brush teeth`,
|
|
||||||
duration: 2 * MIN,
|
|
||||||
start: {
|
|
||||||
min: 7 * HOUR,
|
|
||||||
max: 8 * HOUR,
|
|
||||||
},
|
|
||||||
attributes: {
|
|
||||||
locations: ['home'],
|
|
||||||
},
|
|
||||||
score: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Drop off kids',
|
|
||||||
duration: 30 * MIN,
|
|
||||||
attributes: {
|
|
||||||
locations: ['daycare'],
|
|
||||||
},
|
|
||||||
score: 1,
|
|
||||||
start: {
|
|
||||||
min: 7 * HOUR,
|
|
||||||
max: 9 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Pickup the kids',
|
|
||||||
duration: 30 * MIN,
|
|
||||||
attributes: {
|
|
||||||
locations: ['daycare'],
|
|
||||||
},
|
|
||||||
score: 1,
|
|
||||||
start: {
|
|
||||||
min: 15 * HOUR,
|
|
||||||
max: 15.5 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: `Eat breakfast`,
|
|
||||||
duration: 15 * MIN,
|
|
||||||
start: {
|
|
||||||
min: 7 * HOUR,
|
|
||||||
max: 9 * HOUR,
|
|
||||||
},
|
|
||||||
attributes: {
|
|
||||||
locations: ['home'],
|
|
||||||
},
|
|
||||||
score: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Do work',
|
|
||||||
duration: 1 * HOUR,
|
|
||||||
count: 5,
|
|
||||||
attributes: {
|
|
||||||
locations: ['work'],
|
|
||||||
},
|
|
||||||
score: 10,
|
|
||||||
start: {
|
|
||||||
min: 8 * HOUR,
|
|
||||||
max: 18 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Read book',
|
|
||||||
duration: 0.5 * HOUR,
|
|
||||||
attributes: {
|
|
||||||
locations: ['home', 'work'],
|
|
||||||
},
|
|
||||||
score: 3,
|
|
||||||
count: 2,
|
|
||||||
start: {
|
|
||||||
min: 8 * HOUR,
|
|
||||||
max: 22 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Meditate',
|
|
||||||
duration: 10 * MIN,
|
|
||||||
score: 1,
|
|
||||||
attributes: {},
|
|
||||||
start: {
|
|
||||||
min: 8 * HOUR,
|
|
||||||
max: 22 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Meeting 1',
|
|
||||||
duration: 1 * HOUR,
|
|
||||||
attributes: {
|
|
||||||
locations: ['work', 'work'],
|
|
||||||
},
|
|
||||||
score: 10,
|
|
||||||
start: {
|
|
||||||
min: 10 * HOUR,
|
|
||||||
max: 10 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Meeting 2',
|
|
||||||
duration: 1 * HOUR,
|
|
||||||
attributes: {
|
|
||||||
locations: ['work', 'work'],
|
|
||||||
},
|
|
||||||
score: 10,
|
|
||||||
start: {
|
|
||||||
min: 12 * HOUR,
|
|
||||||
max: 12 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Play playstation',
|
|
||||||
duration: 1 * HOUR,
|
|
||||||
attributes: {
|
|
||||||
locations: ['home'],
|
|
||||||
},
|
|
||||||
score: 10,
|
|
||||||
start: {
|
|
||||||
min: 16 * HOUR,
|
|
||||||
max: 24 * HOUR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createWorker(realistic);
|
|
||||||
204
packages/playground/src/experiments/realistic/experiment.ts
Normal file
204
packages/playground/src/experiments/realistic/experiment.ts
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import { ExperimentInfo } from '../../features/experiment/types';
|
||||||
|
|
||||||
|
const MIN = 1000 * 60;
|
||||||
|
const HOUR = 1000 * 60 * 60;
|
||||||
|
|
||||||
|
const info: ExperimentInfo = {
|
||||||
|
context: {
|
||||||
|
location: 'home',
|
||||||
|
capabilities: ['kids'],
|
||||||
|
},
|
||||||
|
start: 0,
|
||||||
|
planables: [
|
||||||
|
{
|
||||||
|
id: `Brush teeth`,
|
||||||
|
duration: 2 * MIN,
|
||||||
|
start: {
|
||||||
|
min: 7 * HOUR,
|
||||||
|
max: 8 * HOUR,
|
||||||
|
},
|
||||||
|
attributes: {
|
||||||
|
locations: ['home'],
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Drop off kids',
|
||||||
|
duration: 30 * MIN,
|
||||||
|
attributes: {
|
||||||
|
locations: ['daycare'],
|
||||||
|
capabilities: {
|
||||||
|
requires: ['kids'],
|
||||||
|
consumes: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
start: {
|
||||||
|
min: 7 * HOUR,
|
||||||
|
max: 9 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'put kids to bed',
|
||||||
|
duration: 30 * MIN,
|
||||||
|
attributes: {
|
||||||
|
locations: ['home'],
|
||||||
|
capabilities: {
|
||||||
|
consumes: ['kids'],
|
||||||
|
requires: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
min: 18.5 * HOUR,
|
||||||
|
max: 19.5 * HOUR,
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Pickup the kids',
|
||||||
|
duration: 30 * MIN,
|
||||||
|
attributes: {
|
||||||
|
locations: ['daycare'],
|
||||||
|
capabilities: {
|
||||||
|
provides: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
start: {
|
||||||
|
min: 15 * HOUR,
|
||||||
|
max: 16 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: `Eat breakfast`,
|
||||||
|
duration: 15 * MIN,
|
||||||
|
start: {
|
||||||
|
min: 7 * HOUR,
|
||||||
|
max: 9 * HOUR,
|
||||||
|
},
|
||||||
|
attributes: {
|
||||||
|
locations: ['home'],
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Eat dinner',
|
||||||
|
duration: 60 * MIN,
|
||||||
|
attributes: {
|
||||||
|
locations: ['home'],
|
||||||
|
capabilities: {
|
||||||
|
requires: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 1,
|
||||||
|
start: {
|
||||||
|
min: 17 * HOUR,
|
||||||
|
max: 22 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Have lunch',
|
||||||
|
duration: 30 * MIN,
|
||||||
|
attributes: {},
|
||||||
|
score: 1,
|
||||||
|
start: {
|
||||||
|
min: 11 * HOUR,
|
||||||
|
max: 13.5 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Do work',
|
||||||
|
duration: 1 * HOUR,
|
||||||
|
count: 5,
|
||||||
|
attributes: {
|
||||||
|
locations: ['work'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 10,
|
||||||
|
start: {
|
||||||
|
min: 8 * HOUR,
|
||||||
|
max: 22 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Read book',
|
||||||
|
duration: 0.5 * HOUR,
|
||||||
|
attributes: {
|
||||||
|
locations: ['home', 'work'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 3,
|
||||||
|
count: 2,
|
||||||
|
start: {
|
||||||
|
min: 8 * HOUR,
|
||||||
|
max: 22 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Meditate',
|
||||||
|
duration: 10 * MIN,
|
||||||
|
score: 1,
|
||||||
|
attributes: {
|
||||||
|
locations: ['home', 'work'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
min: 8 * HOUR,
|
||||||
|
max: 22 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Meeting 1',
|
||||||
|
duration: 1 * HOUR,
|
||||||
|
attributes: {
|
||||||
|
locations: ['work'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 10,
|
||||||
|
start: {
|
||||||
|
min: 10 * HOUR,
|
||||||
|
max: 10 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Meeting 2',
|
||||||
|
duration: 1 * HOUR,
|
||||||
|
attributes: {
|
||||||
|
locations: ['work'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 10,
|
||||||
|
start: {
|
||||||
|
min: 12 * HOUR,
|
||||||
|
max: 12 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Play playstation',
|
||||||
|
duration: 1 * HOUR,
|
||||||
|
attributes: {
|
||||||
|
locations: ['home'],
|
||||||
|
capabilities: {
|
||||||
|
perhibits: ['kids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
score: 10,
|
||||||
|
start: {
|
||||||
|
min: 16 * HOUR,
|
||||||
|
max: 24 * HOUR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export { info };
|
||||||
9
packages/playground/src/experiments/realistic/index.tsx
Normal file
9
packages/playground/src/experiments/realistic/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { info } from './experiment';
|
||||||
|
const Foo = () => {
|
||||||
|
return <div></div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
info,
|
||||||
|
view: Foo,
|
||||||
|
};
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
import { CalulationResult, GraphNode } from '@bob-the-algorithm/core';
|
import {
|
||||||
|
AllPlugins,
|
||||||
|
CalulationResult,
|
||||||
|
GraphNode,
|
||||||
|
} from '@bob-the-algorithm/core';
|
||||||
import {
|
import {
|
||||||
ReactNode,
|
ReactNode,
|
||||||
createContext,
|
createContext,
|
||||||
@@ -7,9 +11,11 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { ExperimentInfo } from './types';
|
||||||
|
|
||||||
type ExperimentResult = {
|
type ExperimentResult = {
|
||||||
payload: CalulationResult<any>;
|
payload: CalulationResult<AllPlugins>;
|
||||||
|
duration: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ExperimentContextValue = {
|
type ExperimentContextValue = {
|
||||||
@@ -22,9 +28,14 @@ type ExperimentContextValue = {
|
|||||||
|
|
||||||
type ExperimentProviderProps = {
|
type ExperimentProviderProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
worker: () => Worker;
|
experimentInfo: ExperimentInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createWorker = () =>
|
||||||
|
new Worker(new URL('./worker.ts', import.meta.url), {
|
||||||
|
type: 'module',
|
||||||
|
});
|
||||||
|
|
||||||
const ExperimentContext = createContext<ExperimentContextValue>({
|
const ExperimentContext = createContext<ExperimentContextValue>({
|
||||||
loading: false,
|
loading: false,
|
||||||
selectNode: () => { },
|
selectNode: () => { },
|
||||||
@@ -32,7 +43,7 @@ const ExperimentContext = createContext<ExperimentContextValue>({
|
|||||||
|
|
||||||
const ExperimentProvider: React.FC<ExperimentProviderProps> = ({
|
const ExperimentProvider: React.FC<ExperimentProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
worker,
|
experimentInfo,
|
||||||
}) => {
|
}) => {
|
||||||
const [result, setResult] = useState<ExperimentResult>();
|
const [result, setResult] = useState<ExperimentResult>();
|
||||||
const [error, setError] = useState<any>();
|
const [error, setError] = useState<any>();
|
||||||
@@ -49,7 +60,7 @@ const ExperimentProvider: React.FC<ExperimentProviderProps> = ({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setResult(undefined);
|
setResult(undefined);
|
||||||
const workerInstance = worker();
|
const workerInstance = createWorker();
|
||||||
workerInstance.onmessage = (e) => {
|
workerInstance.onmessage = (e) => {
|
||||||
switch (e.data.type) {
|
switch (e.data.type) {
|
||||||
case 'error': {
|
case 'error': {
|
||||||
@@ -69,13 +80,14 @@ const ExperimentProvider: React.FC<ExperimentProviderProps> = ({
|
|||||||
};
|
};
|
||||||
workerInstance.postMessage({
|
workerInstance.postMessage({
|
||||||
type: 'run',
|
type: 'run',
|
||||||
|
payload: experimentInfo,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
run();
|
run();
|
||||||
return () => {
|
return () => {
|
||||||
workerInstance?.terminate();
|
workerInstance?.terminate();
|
||||||
};
|
};
|
||||||
}, [worker]);
|
}, [experimentInfo]);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
import { ExperimentProvider } from './context';
|
|
||||||
|
|
||||||
type CreateExperimentInput = {
|
|
||||||
worker: () => Worker;
|
|
||||||
view: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createExperiment = (input: CreateExperimentInput) => {
|
|
||||||
return (
|
|
||||||
<ExperimentProvider worker={input.worker}>{input.view}</ExperimentProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { createExperiment };
|
|
||||||
@@ -6,6 +6,11 @@ const useExperimentResult = () => {
|
|||||||
return result?.payload;
|
return result?.payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useExperimentDuration = () => {
|
||||||
|
const { result } = useContext(ExperimentContext);
|
||||||
|
return result?.duration;
|
||||||
|
};
|
||||||
|
|
||||||
const useSelectNode = () => {
|
const useSelectNode = () => {
|
||||||
const { selectNode } = useContext(ExperimentContext);
|
const { selectNode } = useContext(ExperimentContext);
|
||||||
return selectNode;
|
return selectNode;
|
||||||
@@ -16,4 +21,9 @@ const useSelectedNode = () => {
|
|||||||
return selectedNode;
|
return selectedNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { useExperimentResult, useSelectNode, useSelectedNode };
|
export {
|
||||||
|
useExperimentResult,
|
||||||
|
useSelectNode,
|
||||||
|
useSelectedNode,
|
||||||
|
useExperimentDuration,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
export { createWorker } from './worker';
|
|
||||||
export { ExperimentProvider as Experminent } from './context';
|
export { ExperimentProvider as Experminent } from './context';
|
||||||
export { createExperiment } from './create';
|
|
||||||
export { useExperimentResult } from './hooks';
|
export { useExperimentResult } from './hooks';
|
||||||
|
|||||||
9
packages/playground/src/features/experiment/types.ts
Normal file
9
packages/playground/src/features/experiment/types.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { AllPlugins, PlanableWithPlugins } from '@bob-the-algorithm/core';
|
||||||
|
|
||||||
|
type ExperimentInfo = {
|
||||||
|
planables: PlanableWithPlugins<AllPlugins>[];
|
||||||
|
context: any;
|
||||||
|
start: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type { ExperimentInfo };
|
||||||
@@ -1,23 +1,38 @@
|
|||||||
type WorkerFn = (...args: any[]) => any;
|
import { Bob, plugins } from '@bob-the-algorithm/core';
|
||||||
|
import { ExperimentInfo } from './types';
|
||||||
|
|
||||||
const createWorker = (fn: WorkerFn) => {
|
const MIN = 1000 * 60;
|
||||||
const run = async () => {
|
|
||||||
|
const getTravelTime = async () => 30 * MIN;
|
||||||
|
const transport = plugins.transport({
|
||||||
|
getTravelTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run = async (payload: ExperimentInfo) => {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
try {
|
try {
|
||||||
const result = await fn();
|
const bob = new Bob({
|
||||||
|
plugins: { transport, capabilities: plugins.capabilities() },
|
||||||
|
});
|
||||||
|
const result = await bob.run({
|
||||||
|
planables: payload.planables,
|
||||||
|
context: payload.context,
|
||||||
|
start: payload.start,
|
||||||
|
heuristic: ({ completed }) => completed.length >= 3,
|
||||||
|
});
|
||||||
|
|
||||||
const endTime = performance.now();
|
const endTime = performance.now();
|
||||||
const duration = endTime - startTime;
|
const duration = endTime - startTime;
|
||||||
self.postMessage({ type: 'output', payload: result, duration });
|
self.postMessage({ type: 'output', payload: result, duration });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
self.postMessage({ type: 'error', payload: error });
|
self.postMessage({ type: 'error', payload: error });
|
||||||
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.addEventListener('message', (event) => {
|
|
||||||
const { type } = event.data;
|
|
||||||
if (type === 'run') {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export { createWorker };
|
self.addEventListener('message', (event) => {
|
||||||
|
const { type, payload } = event.data;
|
||||||
|
if (type === 'run') {
|
||||||
|
run(payload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#root,
|
||||||
|
#root > div {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import './main.css';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { App } from './app.tsx';
|
import { App } from './app.tsx';
|
||||||
|
|||||||
53
packages/playground/src/pages/experiments.tsx
Normal file
53
packages/playground/src/pages/experiments.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Card, CardBody, Button } from '@nextui-org/react';
|
||||||
|
|
||||||
|
type Experiment = {
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const experiments: Experiment[] = [
|
||||||
|
{
|
||||||
|
title: 'Capabilities',
|
||||||
|
slug: 'capabilities',
|
||||||
|
description: 'Explore the capabilities of Bob.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Packed day',
|
||||||
|
slug: 'realistic',
|
||||||
|
description: 'Explore the capabilities of Bob.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const Experiments = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<h1 className="text-2xl font-bold">Experiments</h1>
|
||||||
|
{experiments.map(({ title, slug, description }) => (
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<p className="text-md">{title}</p>
|
||||||
|
<p className="text-sm">{description}</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
radius="full"
|
||||||
|
size="sm"
|
||||||
|
variant="solid"
|
||||||
|
onPress={() => navigate(`/experiments/${slug}`)}
|
||||||
|
>
|
||||||
|
Show
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Experiments as Page };
|
||||||
5
packages/playground/src/pages/index.tsx
Normal file
5
packages/playground/src/pages/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const Experiments = () => {
|
||||||
|
return <div>Welcome</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Experiments as Page };
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { GraphCanvas } from 'reagraph';
|
import { GraphCanvas } from 'reagraph';
|
||||||
import { useExperimentResult } from '../../features/experiment';
|
import { useExperimentResult } from '../../features/experiment';
|
||||||
import { convertResult } from '../../utils/graph';
|
import { convertResult } from '../../utils/graph';
|
||||||
@@ -17,7 +17,6 @@ const Graph: React.FC = () => {
|
|||||||
}
|
}
|
||||||
return convertResult(data);
|
return convertResult(data);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
const [visualize, setVisualize] = useState(false);
|
|
||||||
const selectedPath = useMemo(() => {
|
const selectedPath = useMemo(() => {
|
||||||
if (!selectedNode) {
|
if (!selectedNode) {
|
||||||
return [];
|
return [];
|
||||||
@@ -45,10 +44,6 @@ const Graph: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
Nodes count: {output.nodes.length}
|
Nodes count: {output.nodes.length}
|
||||||
<button onClick={() => setVisualize(!visualize)}>
|
|
||||||
{visualize ? 'Hide' : 'Show'} Visualize
|
|
||||||
</button>
|
|
||||||
{visualize && (
|
|
||||||
<div style={{ position: 'relative', height: '70vh' }}>
|
<div style={{ position: 'relative', height: '70vh' }}>
|
||||||
<GraphCanvas
|
<GraphCanvas
|
||||||
{...output}
|
{...output}
|
||||||
@@ -90,7 +85,6 @@ const Graph: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
import { GraphNode } from '@bob-the-algorithm/core';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { ConvertedResult } from '../../utils/graph';
|
|
||||||
import { format } from 'date-fns';
|
|
||||||
|
|
||||||
type PlanProps = {
|
|
||||||
id: string;
|
|
||||||
output: ConvertedResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
type NodeProps = {
|
|
||||||
node: GraphNode;
|
|
||||||
output: ConvertedResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Node = ({ node, output }: NodeProps) => {
|
|
||||||
const planable = useMemo(() => {
|
|
||||||
return node.planable
|
|
||||||
? output.result.planables.find((n) => n.id === node.planable)
|
|
||||||
: null;
|
|
||||||
}, [node, output]);
|
|
||||||
|
|
||||||
const time = useMemo(() => {
|
|
||||||
const start = new Date(node.time);
|
|
||||||
const end = new Date(start.getTime() + node.duration);
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
{format(start, 'HH:mm')} - {format(end, 'HH:mm')}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}, [node.duration, node.time]);
|
|
||||||
|
|
||||||
if (planable) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{time} Planable: {planable!.id}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (node.type === 'travel') {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{time} Travel: {node.context.location}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Plan: React.FC<PlanProps> = ({ id, output }) => {
|
|
||||||
const nodes = useMemo(() => {
|
|
||||||
const result: GraphNode[] = [];
|
|
||||||
let current = output.result.nodes.find((n) => n.id === id);
|
|
||||||
|
|
||||||
while (current) {
|
|
||||||
result.push(current);
|
|
||||||
if (!current.parent) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
current = output.result.nodes.find((n) => n.id === current?.parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, [id, output]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{nodes.map((n) => (
|
|
||||||
<Node key={n.id} node={n} output={output} />
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { Plan };
|
|
||||||
90
packages/playground/src/presenters/horizontal-plan/index.tsx
Normal file
90
packages/playground/src/presenters/horizontal-plan/index.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { GraphNode } from '@bob-the-algorithm/core';
|
||||||
|
import { Popover, PopoverTrigger, PopoverContent } from '@nextui-org/react';
|
||||||
|
import { useExperimentResult } from '../../features/experiment';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { formatTime } from '../../utils/time';
|
||||||
|
|
||||||
|
type PlanProps = {
|
||||||
|
node: GraphNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const timespan = 24 * 60 * 60 * 1000;
|
||||||
|
const randomColor = () => Math.floor(Math.random() * 16777215).toString(16);
|
||||||
|
|
||||||
|
const HorizontalPlan: React.FC<PlanProps> = ({ node }) => {
|
||||||
|
const data = useExperimentResult();
|
||||||
|
const nodes = useMemo(() => {
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: GraphNode[] = [];
|
||||||
|
let current = node;
|
||||||
|
if (!current) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (current) {
|
||||||
|
result.push(current);
|
||||||
|
if (!current.parent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current = data.nodes.find((n) => n.id === current?.parent)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, [data, node]);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full min-h-unit-6 rounded-lg bg-gray-900 relative">
|
||||||
|
{nodes.map((node) => {
|
||||||
|
const time = (
|
||||||
|
<span>
|
||||||
|
{formatTime(node.time)} - {formatTime(node.time + node.duration)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
let title = '';
|
||||||
|
if (node.planable) {
|
||||||
|
const planable = data!.planables.find((n) => n.id === node.planable);
|
||||||
|
title = `Planable: ${planable?.id}`;
|
||||||
|
}
|
||||||
|
if (node.type === 'travel') {
|
||||||
|
title = `Travel: ${node.context.location}`;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="absolute top-0 bottom-0 h-full left-0 right-0 p-[2px] flex items-stretch justify-stretch"
|
||||||
|
style={{
|
||||||
|
left: `${(node.time / timespan) * 100}%`,
|
||||||
|
width: `${(node.duration / timespan) * 100}%`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Popover placement="bottom" showArrow>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<div
|
||||||
|
className="flex-1 rounded-[2px]"
|
||||||
|
style={{
|
||||||
|
left: `${(node.time / timespan) * 100}%`,
|
||||||
|
width: `${(node.duration / timespan) * 100}%`,
|
||||||
|
backgroundColor: `#${randomColor()}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<div className="px-1 py-2">
|
||||||
|
<div className="text-small font-bold">{title}</div>
|
||||||
|
<div className="text-tiny">{time}</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { HorizontalPlan };
|
||||||
@@ -1,60 +1,18 @@
|
|||||||
import { GraphNode } from '@bob-the-algorithm/core';
|
import { GraphNode } from '@bob-the-algorithm/core';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { convertResult } from '../../utils/graph';
|
import { Listbox, ListboxItem, cn } from '@nextui-org/react';
|
||||||
import { format } from 'date-fns';
|
|
||||||
import { useExperimentResult } from '../../features/experiment';
|
import { useExperimentResult } from '../../features/experiment';
|
||||||
import { useSelectNode } from '../../features/experiment/hooks';
|
import { formatTime } from '../../utils/time';
|
||||||
|
|
||||||
type PlanProps = {
|
type PlanProps = {
|
||||||
node: GraphNode;
|
node: GraphNode;
|
||||||
|
onSelect: (node: GraphNode) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NodeProps = {
|
const Plan: React.FC<PlanProps> = ({ node, onSelect }) => {
|
||||||
node: GraphNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Node = ({ node }: NodeProps) => {
|
|
||||||
const selectNode = useSelectNode();
|
|
||||||
const data = useExperimentResult();
|
const data = useExperimentResult();
|
||||||
const planable = useMemo(() => {
|
|
||||||
return node.planable
|
|
||||||
? data?.planables.find((n) => n.id === node.planable)
|
|
||||||
: null;
|
|
||||||
}, [node, data]);
|
|
||||||
|
|
||||||
const time = useMemo(() => {
|
|
||||||
const start = new Date(node.time);
|
|
||||||
const end = new Date(start.getTime() + node.duration);
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
{format(start, 'HH:mm')} - {format(end, 'HH:mm')}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}, [node.duration, node.time]);
|
|
||||||
|
|
||||||
if (planable) {
|
|
||||||
return (
|
|
||||||
<div onClick={() => selectNode(node)}>
|
|
||||||
{time} Planable: {planable!.id}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (node.type === 'travel') {
|
|
||||||
return (
|
|
||||||
<div onClick={() => selectNode(node)}>
|
|
||||||
{time} Travel: {node.context.location}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Plan: React.FC<PlanProps> = ({ node }) => {
|
|
||||||
const data = useExperimentResult();
|
|
||||||
const output = useMemo(() => (data ? convertResult(data) : null), [data]);
|
|
||||||
const nodes = useMemo(() => {
|
const nodes = useMemo(() => {
|
||||||
if (!output) {
|
if (!data) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const result: GraphNode[] = [];
|
const result: GraphNode[] = [];
|
||||||
@@ -68,22 +26,48 @@ const Plan: React.FC<PlanProps> = ({ node }) => {
|
|||||||
if (!current.parent) {
|
if (!current.parent) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
current = output.result.nodes.find((n) => n.id === current?.parent)!;
|
current = data.nodes.find((n) => n.id === current?.parent)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result.reverse();
|
||||||
}, [output, node]);
|
}, [data, node]);
|
||||||
|
|
||||||
if (!output) {
|
if (!data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Listbox
|
||||||
{nodes.map((n) => (
|
variant="flat"
|
||||||
<Node key={n.id} node={n} />
|
aria-label="Listbox menu with descriptions"
|
||||||
))}
|
items={nodes}
|
||||||
</>
|
>
|
||||||
|
{(node) => {
|
||||||
|
const time = (
|
||||||
|
<span>
|
||||||
|
{formatTime(node.time)} - {formatTime(node.time + node.duration)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
let title = '';
|
||||||
|
if (node.planable) {
|
||||||
|
const planable = data!.planables.find((n) => n.id === node.planable);
|
||||||
|
title = `Planable: ${planable?.id}`;
|
||||||
|
}
|
||||||
|
if (node.type === 'travel') {
|
||||||
|
title = `Travel: ${node.context.location}`;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ListboxItem
|
||||||
|
key={node.id}
|
||||||
|
className={cn({ selected: node.id === node.id })}
|
||||||
|
onClick={() => onSelect(node)}
|
||||||
|
description={time}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</ListboxItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Listbox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
18
packages/playground/src/router/index.tsx
Normal file
18
packages/playground/src/router/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { createHashRouter, RouterProvider } from 'react-router-dom';
|
||||||
|
import { pages } from '../containers/page';
|
||||||
|
import { experiments } from '../containers/experiment';
|
||||||
|
|
||||||
|
const router = createHashRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
children: pages,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/experiments',
|
||||||
|
children: experiments,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const Router = () => <RouterProvider router={router} />;
|
||||||
|
|
||||||
|
export { Router };
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
const imports = import.meta.glob('../experiments/*/index.tsx');
|
|
||||||
|
|
||||||
const experiments = Object.entries(imports).map(([path, loader]) => {
|
|
||||||
const slug = path.replace('./experiments/', '').replace('/index.tsx', '');
|
|
||||||
return { slug, loader };
|
|
||||||
});
|
|
||||||
|
|
||||||
export { experiments };
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CalulationResult } from '@bob-the-algorithm/core';
|
import { AllPlugins, CalulationResult } from '@bob-the-algorithm/core';
|
||||||
|
|
||||||
function msToHMS(ms: number) {
|
function msToHMS(ms: number) {
|
||||||
// 1- Convert to seconds:
|
// 1- Convert to seconds:
|
||||||
@@ -12,13 +12,13 @@ function msToHMS(ms: number) {
|
|||||||
seconds = seconds % 60;
|
seconds = seconds % 60;
|
||||||
return hours + ':' + minutes + ':' + seconds;
|
return hours + ':' + minutes + ':' + seconds;
|
||||||
}
|
}
|
||||||
const convertResult = (result: CalulationResult<any>) => {
|
const convertResult = (result: CalulationResult<AllPlugins>) => {
|
||||||
const nodes = result.nodes.map((node) => {
|
const nodes = result.nodes.map((node) => {
|
||||||
let label = `root (${node.location})`;
|
let label = `root (${node.context.location})`;
|
||||||
if (node.type === 'planable') {
|
if (node.type === 'planable') {
|
||||||
label = `task: ${node.planable!.toString()}`;
|
label = `task: ${node.planable!.toString()}`;
|
||||||
} else if (node.type === 'travel') {
|
} else if (node.type === 'travel') {
|
||||||
label = `travel->${node.location}`;
|
label = `travel->${node.context.location}`;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
|
|||||||
6
packages/playground/src/utils/time.ts
Normal file
6
packages/playground/src/utils/time.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const formatTime = (time: number) => {
|
||||||
|
const toUtc = new Date(time).toUTCString();
|
||||||
|
return toUtc.slice(17, 22);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { formatTime };
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const { nextui } = require('@nextui-org/react');
|
const { nextui } = require('@nextui-org/react');
|
||||||
|
const themePath = require.resolve('@nextui-org/theme/package.json');
|
||||||
|
const themeDir = path.dirname(themePath);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
content: [
|
content: [
|
||||||
'./index.html',
|
'./index.html',
|
||||||
'./src/**/*.{js,ts,jsx,tsx}',
|
'./src/**/*.{js,ts,jsx,tsx}',
|
||||||
'./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}',
|
`${themeDir}/dist/**/*.{js,ts,jsx,tsx}`,
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
2
packages/playground/vite.config.d.ts
vendored
2
packages/playground/vite.config.d.ts
vendored
@@ -1,2 +0,0 @@
|
|||||||
declare const _default: import("vite").UserConfig;
|
|
||||||
export default _default;
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react-swc';
|
import react from '@vitejs/plugin-react-swc';
|
||||||
import mdx from '@mdx-js/rollup';
|
import mdx from '@mdx-js/rollup';
|
||||||
var ASSET_URL = process.env.ASSET_URL || '';
|
|
||||||
|
const ASSET_URL = process.env.ASSET_URL || '';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: ASSET_URL,
|
base: ASSET_URL,
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { defineConfig } from 'vite';
|
|
||||||
import react from '@vitejs/plugin-react-swc';
|
|
||||||
import mdx from '@mdx-js/rollup';
|
|
||||||
|
|
||||||
const ASSET_URL = process.env.ASSET_URL || '';
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
base: ASSET_URL,
|
|
||||||
plugins: [mdx(), react()],
|
|
||||||
});
|
|
||||||
78
pnpm-lock.yaml
generated
78
pnpm-lock.yaml
generated
@@ -69,6 +69,12 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
|
react-json-tree:
|
||||||
|
specifier: ^0.18.0
|
||||||
|
version: 0.18.0(@types/react@18.2.15)(react@18.2.0)
|
||||||
|
react-router-dom:
|
||||||
|
specifier: ^6.16.0
|
||||||
|
version: 6.16.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.3.3
|
||||||
version: 3.3.3(ts-node@10.9.1)
|
version: 3.3.3(ts-node@10.9.1)
|
||||||
@@ -3168,6 +3174,11 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@remix-run/router@1.9.0:
|
||||||
|
resolution: {integrity: sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@rollup/pluginutils@5.0.4(rollup@3.28.1):
|
/@rollup/pluginutils@5.0.4(rollup@3.28.1):
|
||||||
resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==}
|
resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@@ -3350,6 +3361,10 @@ packages:
|
|||||||
'@types/estree': 1.0.1
|
'@types/estree': 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/base16@1.0.3:
|
||||||
|
resolution: {integrity: sha512-rjrIWFr73ylMjEQuU1OQjkoIDcLR2/dIwiopZe2S5ASo5eoSYBxaAnGtwTUhWc5oWefQXxHRFmGDelYR5yMcgA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/color-convert@2.0.1:
|
/@types/color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-GwXanrvq/tBHJtudbl1lSy9Ybt7KS9+rA+YY3bcuIIM+d6jSHUr+5yjO83gtiRpuaPiBccwFjSnAK2qSrIPA7w==}
|
resolution: {integrity: sha512-GwXanrvq/tBHJtudbl1lSy9Ybt7KS9+rA+YY3bcuIIM+d6jSHUr+5yjO83gtiRpuaPiBccwFjSnAK2qSrIPA7w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4018,6 +4033,10 @@ packages:
|
|||||||
/balanced-match@1.0.2:
|
/balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
|
/base16@1.0.0:
|
||||||
|
resolution: {integrity: sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/better-path-resolve@1.0.0:
|
/better-path-resolve@1.0.0:
|
||||||
resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==}
|
resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -4240,6 +4259,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==}
|
resolution: {integrity: sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/color@3.2.1:
|
||||||
|
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
|
||||||
|
dependencies:
|
||||||
|
color-convert: 1.9.3
|
||||||
|
color-string: 1.9.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/color@4.2.3:
|
/color@4.2.3:
|
||||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||||
engines: {node: '>=12.5.0'}
|
engines: {node: '>=12.5.0'}
|
||||||
@@ -5856,6 +5882,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==}
|
resolution: {integrity: sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lodash.curry@4.1.1:
|
||||||
|
resolution: {integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lodash.foreach@4.5.0:
|
/lodash.foreach@4.5.0:
|
||||||
resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==}
|
resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -6814,6 +6844,18 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-base16-styling@0.9.1:
|
||||||
|
resolution: {integrity: sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.15
|
||||||
|
'@types/base16': 1.0.3
|
||||||
|
'@types/lodash': 4.14.199
|
||||||
|
base16: 1.0.0
|
||||||
|
color: 3.2.1
|
||||||
|
csstype: 3.1.2
|
||||||
|
lodash.curry: 4.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-composer@5.0.3(react@18.2.0):
|
/react-composer@5.0.3(react@18.2.0):
|
||||||
resolution: {integrity: sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==}
|
resolution: {integrity: sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -6835,6 +6877,19 @@ packages:
|
|||||||
/react-is@16.13.1:
|
/react-is@16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
|
|
||||||
|
/react-json-tree@0.18.0(@types/react@18.2.15)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-Qe6HKSXrr++n9Y31nkRJ3XvQMATISpqigH1vEKhLwB56+nk5thTP0ITThpjxY6ZG/ubpVq/aEHIcyLP/OPHxeA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.15
|
||||||
|
'@types/lodash': 4.14.199
|
||||||
|
'@types/react': 18.2.15
|
||||||
|
react: 18.2.0
|
||||||
|
react-base16-styling: 0.9.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-merge-refs@1.1.0:
|
/react-merge-refs@1.1.0:
|
||||||
resolution: {integrity: sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==}
|
resolution: {integrity: sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6885,6 +6940,29 @@ packages:
|
|||||||
use-sidecar: 1.1.2(@types/react@18.2.15)(react@18.2.0)
|
use-sidecar: 1.1.2(@types/react@18.2.15)(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-router-dom@6.16.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8'
|
||||||
|
react-dom: '>=16.8'
|
||||||
|
dependencies:
|
||||||
|
'@remix-run/router': 1.9.0
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
react-router: 6.16.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/react-router@6.16.0(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8'
|
||||||
|
dependencies:
|
||||||
|
'@remix-run/router': 1.9.0
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-style-singleton@2.2.1(@types/react@18.2.15)(react@18.2.0):
|
/react-style-singleton@2.2.1(@types/react@18.2.15)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|||||||
Reference in New Issue
Block a user