diff --git a/README.md b/README.md index 75f53af1..b7f015a6 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,11 @@ ![logo64](./docs/assets/22-09-2023/logo64.png) -# Waypoint +# Visualiser -Waypoint is a visualiser and debugging tool for pathfinding -search. +Visualise pathfinding search and more. -[Open Waypoint](https://path-visualiser.github.io/app) +[Open Visualiser](https://path-visualiser.github.io/app) ## Start Development Server diff --git a/adapter-iron-harvest/build.sh b/adapter-iron-harvest/build.sh index 44c4b305..5cb24289 100644 --- a/adapter-iron-harvest/build.sh +++ b/adapter-iron-harvest/build.sh @@ -31,10 +31,10 @@ npx --yes resedit-cli \ --in dist/main-win.exe \ --out dist/main-win.exe \ --icon "1,icon.ico" \ - --product-name "Waypoint" \ + --product-name "Visualiser" \ --product-version "0.1.0.0" \ --file-version "0.1.0.0" \ - --file-description "Waypoint Adapter Server" + --file-description "Visualiser Adapter Server" # Sign Windows executable @@ -52,7 +52,7 @@ if which osslsigncode >/dev/null; then -in private/cert.pem fi osslsigncode sign -pkcs12 private/cert.p12 \ - -n "Waypoint Adapter Server" \ + -n "Visualiser Adapter Server" \ -i "https://github.com/path-visualiser" \ -in dist/main-win.exe \ -out dist/main-win-signed.exe diff --git a/adapter-iron-harvest/package.json b/adapter-iron-harvest/package.json index 58e1c40a..6aeafd7c 100644 --- a/adapter-iron-harvest/package.json +++ b/adapter-iron-harvest/package.json @@ -1,6 +1,6 @@ { "name": "adapter-iron-harvest", - "version": "1.0.4", + "version": "1.0.5", "description": "", "main": "index.ts", "scripts": { diff --git a/adapter-iron-harvest/src/index.ts b/adapter-iron-harvest/src/index.ts index 5ee21bd6..d94ac61f 100644 --- a/adapter-iron-harvest/src/index.ts +++ b/adapter-iron-harvest/src/index.ts @@ -21,7 +21,7 @@ const mainText = `Adapter started on ${chalk.blueBright( `http://localhost:${port}/` )}`; -const hintText = chalk.dim(`(Hint: Add it to Waypoint's connections list)`); +const hintText = chalk.dim(`(Hint: Add it to Visualiser's connections list)`); const server = createAdapter(port); diff --git a/adapter-warthog-wasm/package.json b/adapter-warthog-wasm/package.json index a55d9e2a..589e5388 100644 --- a/adapter-warthog-wasm/package.json +++ b/adapter-warthog-wasm/package.json @@ -1,6 +1,6 @@ { "name": "adapter-warthog-wasm", - "version": "1.0.4", + "version": "1.0.5", "description": "", "main": "index.js", "scripts": { diff --git a/adapter-warthog-wasm/src/core/templates.ts b/adapter-warthog-wasm/src/core/templates.ts index e9edff9a..939250ad 100644 --- a/adapter-warthog-wasm/src/core/templates.ts +++ b/adapter-warthog-wasm/src/core/templates.ts @@ -1,7 +1,7 @@ import { Trace } from "protocol"; export const gridTemplate: Partial = { - version: "1.0.4", + version: "1.0.5", render: { context: {}, components: { @@ -33,7 +33,7 @@ export const gridTemplate: Partial = { }; export const xyTemplate: Partial = { - version: "1.0.4", + version: "1.0.5", render: { components: { node: [ diff --git a/adapter-warthog-wasm/src/methods/general.ts b/adapter-warthog-wasm/src/methods/general.ts index da223f7f..ae50f004 100644 --- a/adapter-warthog-wasm/src/methods/general.ts +++ b/adapter-warthog-wasm/src/methods/general.ts @@ -6,7 +6,7 @@ export const general = [ */ createMethod("about", async () => ({ name: "Warthog (WebAssembly)", - version: "1.0.4", + version: "1.0.5", description: "Solver Adapter for Warthog & Roadhog", })), ]; diff --git a/adapter-warthog-websocket/adapter.config.yaml b/adapter-warthog-websocket/adapter.config.yaml index 6de4838c..243f17bd 100644 --- a/adapter-warthog-websocket/adapter.config.yaml +++ b/adapter-warthog-websocket/adapter.config.yaml @@ -1,4 +1,4 @@ name: Warthog (Socket.io) -version: 1.0.4 +version: 1.0.5 description: Solver Adapter for Warthog & Roadhog port: 8221 diff --git a/adapter-warthog-websocket/build.sh b/adapter-warthog-websocket/build.sh index 44c4b305..5cb24289 100644 --- a/adapter-warthog-websocket/build.sh +++ b/adapter-warthog-websocket/build.sh @@ -31,10 +31,10 @@ npx --yes resedit-cli \ --in dist/main-win.exe \ --out dist/main-win.exe \ --icon "1,icon.ico" \ - --product-name "Waypoint" \ + --product-name "Visualiser" \ --product-version "0.1.0.0" \ --file-version "0.1.0.0" \ - --file-description "Waypoint Adapter Server" + --file-description "Visualiser Adapter Server" # Sign Windows executable @@ -52,7 +52,7 @@ if which osslsigncode >/dev/null; then -in private/cert.pem fi osslsigncode sign -pkcs12 private/cert.p12 \ - -n "Waypoint Adapter Server" \ + -n "Visualiser Adapter Server" \ -i "https://github.com/path-visualiser" \ -in dist/main-win.exe \ -out dist/main-win-signed.exe diff --git a/adapter-warthog-websocket/package.json b/adapter-warthog-websocket/package.json index 118e3376..f2b750be 100644 --- a/adapter-warthog-websocket/package.json +++ b/adapter-warthog-websocket/package.json @@ -1,6 +1,6 @@ { "name": "adapter-warthog-websocket", - "version": "1.0.4", + "version": "1.0.5", "description": "", "main": "index.js", "scripts": { diff --git a/adapter-warthog-websocket/src/core/templates.ts b/adapter-warthog-websocket/src/core/templates.ts index e9edff9a..939250ad 100644 --- a/adapter-warthog-websocket/src/core/templates.ts +++ b/adapter-warthog-websocket/src/core/templates.ts @@ -1,7 +1,7 @@ import { Trace } from "protocol"; export const gridTemplate: Partial = { - version: "1.0.4", + version: "1.0.5", render: { context: {}, components: { @@ -33,7 +33,7 @@ export const gridTemplate: Partial = { }; export const xyTemplate: Partial = { - version: "1.0.4", + version: "1.0.5", render: { components: { node: [ diff --git a/adapter-warthog-websocket/src/index.ts b/adapter-warthog-websocket/src/index.ts index 62edee91..4f911a6d 100644 --- a/adapter-warthog-websocket/src/index.ts +++ b/adapter-warthog-websocket/src/index.ts @@ -21,7 +21,7 @@ const mainText = `Adapter started on ${chalk.blueBright( `http://localhost:${port}/` )}`; -const hintText = chalk.dim(`(Hint: Add it to Waypoint's connections list)`); +const hintText = chalk.dim(`(Hint: Add it to Visualiser's connections list)`); const server = createAdapter(port); diff --git a/client/package-lock.json b/client/package-lock.json index 1884dc02..9eba98e2 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -47,6 +47,7 @@ "socket.io-client": "^4.7.2", "string-template-parser": "^1.2.6", "typescript": "^5.2.2", + "unique-username-generator": "^1.2.0", "url-parse": "^1.5.3", "vite": "^4.4.9", "vite-tsconfig-paths": "^4.2.1" @@ -8682,6 +8683,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unique-username-generator": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unique-username-generator/-/unique-username-generator-1.2.0.tgz", + "integrity": "sha512-aQB5mNOZGeZqQWku15xZeTaD0spV48GmlSmNrabYrx/5DcNDNYgSiwY2cQ0TglkO7Raz+VCUTCERe+CRZf7OLg==" + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", diff --git a/client/package.json b/client/package.json index a43b8b23..6f5b58d5 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "client", - "version": "1.0.4", + "version": "1.0.5", "private": true, "dependencies": { "@devbookhq/splitter": "^1.4.2", @@ -42,6 +42,7 @@ "socket.io-client": "^4.7.2", "string-template-parser": "^1.2.6", "typescript": "^5.2.2", + "unique-username-generator": "^1.2.0", "url-parse": "^1.5.3", "vite": "^4.4.9", "vite-tsconfig-paths": "^4.2.1" diff --git a/client/src/components/Editor.tsx b/client/src/components/Editor.tsx index cbe4b15d..d4748a7c 100644 --- a/client/src/components/Editor.tsx +++ b/client/src/components/Editor.tsx @@ -2,3 +2,7 @@ export type EditorProps = { value?: T; onChange?: (key: T) => void; }; +export type EditorSetterProps = { + value?: T; + onChange?: (key: (value: T) => T) => void; +}; diff --git a/client/src/components/app-bar/Playback.tsx b/client/src/components/app-bar/Playback.tsx index f7e98275..05f36719 100644 --- a/client/src/components/app-bar/Playback.tsx +++ b/client/src/components/app-bar/Playback.tsx @@ -7,7 +7,7 @@ import { useSnackbar } from "components/generic/Snackbar"; import { useBreakpoints } from "hooks/useBreakpoints"; import { usePlaybackState } from "hooks/usePlaybackState"; import { useSettings } from "slices/settings"; -import { Layer, UploadedTrace } from "slices/UIState"; +import { UploadedTrace } from "slices/UIState"; import { SkipNextOutlined as ForwardIcon, PauseOutlined as PauseIcon, @@ -15,6 +15,7 @@ import { SkipPreviousOutlined as PreviousIcon, StopOutlined as StopIcon, } from "@mui/icons-material"; +import { Layer } from "slices/layers"; function cancellable(f: () => Promise, g: (result: T) => void) { let cancelled = false; @@ -47,7 +48,7 @@ export function Playback({ stepBackward, stepForward, stop, - } = usePlaybackState(layer); + } = usePlaybackState(layer?.key); useRaf(); const notify = useSnackbar(); diff --git a/client/src/components/breakpoint-editor/BreakpointListEditor.tsx b/client/src/components/breakpoint-editor/BreakpointListEditor.tsx index 8e07c301..b571c36d 100644 --- a/client/src/components/breakpoint-editor/BreakpointListEditor.tsx +++ b/client/src/components/breakpoint-editor/BreakpointListEditor.tsx @@ -34,7 +34,10 @@ export function BreakpointListEditor() { type: undefined, reference: 0, })} - onChange={debounce((v) => setUIState({ breakpoints: v }), 1000)} + onChange={debounce( + (v) => setUIState(() => ({ breakpoints: v })), + 1000 + )} addItemLabel="Breakpoint" placeholderText="Click the button below to add a breakpoint." /> diff --git a/client/src/components/generic/Snackbar.tsx b/client/src/components/generic/Snackbar.tsx index b2e9f4c7..69a5441f 100644 --- a/client/src/components/generic/Snackbar.tsx +++ b/client/src/components/generic/Snackbar.tsx @@ -1,8 +1,8 @@ -import { CloseOutlined as CloseIcon } from "@mui/icons-material"; -import { IconButton, Snackbar } from "@mui/material"; -import { filter, noop } from "lodash"; -import { Label } from "./Label"; -import { useLog } from "slices/log"; +import { CloseOutlined as CloseIcon } from "@mui/icons-material"; +import { IconButton, Snackbar } from "@mui/material"; +import { filter, noop } from "lodash"; +import { Label } from "./Label"; +import { useLog } from "slices/log"; import { ReactNode, createContext, @@ -12,9 +12,6 @@ import { useState, } from "react"; - - - const SnackbarContext = createContext< (message?: string, secondary?: string) => () => void >(() => noop); @@ -62,10 +59,10 @@ export function SnackbarProvider({ children }: { children?: ReactNode }) { key: new Date().getTime(), }, ]); - appendLog({ + appendLog(() => ({ content: filter([message, secondary]).join(", "), timestamp: `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`, - }); + })); return () => handleClose(""); }, [setSnackPack] @@ -105,4 +102,4 @@ export function SnackbarProvider({ children }: { children?: ReactNode }) { /> ); -} \ No newline at end of file +} diff --git a/client/src/components/inspector/EventInspector.tsx b/client/src/components/inspector/EventInspector.tsx index faff0f94..27d7170b 100644 --- a/client/src/components/inspector/EventInspector.tsx +++ b/client/src/components/inspector/EventInspector.tsx @@ -1,9 +1,3 @@ -import { pick } from "lodash"; -import { TraceEvent } from "protocol/Trace"; -import { EventLabel } from "./EventLabel"; -import { PropertyList } from "./PropertyList"; -import { getColorHex } from "components/renderer/colors"; -import { usePlayback } from "slices/playback"; import { Divider, ListItem, @@ -11,11 +5,16 @@ import { ListItemButtonProps, ListItemIcon, ListItemText, - Typography as Type, - useTheme, Skeleton as Placeholder, Tooltip, + Typography as Type, + useTheme, } from "@mui/material"; +import { getColorHex } from "components/renderer/colors"; +import { pick } from "lodash"; +import { TraceEvent } from "protocol/Trace"; +import { EventLabel } from "./EventLabel"; +import { PropertyList } from "./PropertyList"; type EventInspectorProps = { event?: TraceEvent; @@ -30,7 +29,6 @@ export function EventInspector({ ...props }: EventInspectorProps) { const { spacing } = useTheme(); - const [, setPlayback] = usePlayback(); // const cardStyles = selected // ? { @@ -55,7 +53,6 @@ export function EventInspector({ borderLeft: `${spacing(0.5)} solid ${getColorHex(event?.type)}`, ...props.sx, }} - onClick={() => setPlayback({ step: index })} > {index} diff --git a/client/src/components/inspector/SelectionMenu.tsx b/client/src/components/inspector/SelectionMenu.tsx index d0ee04c3..bfc303f5 100644 --- a/client/src/components/inspector/SelectionMenu.tsx +++ b/client/src/components/inspector/SelectionMenu.tsx @@ -17,6 +17,7 @@ import { SelectionInfoProvider, getLayerHandler, } from "components/layer-editor/layers/LayerSource"; +import { useLayers } from "slices/layers"; type Props = { selection?: RendererSelectEvent; @@ -127,21 +128,21 @@ export function SelectionMenu({ selection, onClose }: Props) { ); } -type SelectionInfoProviderProps = ComponentProps>; +type SelectionInfoProviderProps = ComponentProps; -const identity = ({ children }: SelectionInfoProviderProps) => ( +const identity = ({ children }: SelectionInfoProviderProps) => ( <>{children?.({})} ); function useSelectionMenu() { - const [{ layers }] = useUIState(); + const [{ layers: layers }] = useLayers(); return useMemo( () => chain(layers) .reduce((A, l) => { const B = getLayerHandler(l)?.getSelectionInfo ?? identity; - return ({ children, event }: SelectionInfoProviderProps) => ( - + return ({ children, event }: SelectionInfoProviderProps) => ( + {(a) => {(b) => children?.(merge(a, b))}} ); diff --git a/client/src/components/inspector/index.tsx b/client/src/components/inspector/index.tsx index 42b197ea..96608554 100644 --- a/client/src/components/inspector/index.tsx +++ b/client/src/components/inspector/index.tsx @@ -17,7 +17,7 @@ export function Inspector(props: SpecimenInspectorProps) { root={view} - onChange={(v) => setView({ view: v })} + onChange={(v) => setView(() => ({ view: v }))} renderLeaf={({ content }) => ( diff --git a/client/src/components/layer-editor/LayerEditor.tsx b/client/src/components/layer-editor/LayerEditor.tsx index 3bebf083..91e3eb5f 100644 --- a/client/src/components/layer-editor/LayerEditor.tsx +++ b/client/src/components/layer-editor/LayerEditor.tsx @@ -24,8 +24,8 @@ import { useMemo, useState, } from "react"; -import { Layer } from "slices/UIState"; import { inferLayerName, layerHandlers } from "./layers/LayerSource"; +import { Layer } from "slices/layers"; type LayerEditorProps = { value: Layer; @@ -157,7 +157,7 @@ function Component( )} {draft.source?.type && createElement(layerHandlers[draft.source.type].editor, { - onChange: setDraft, + onChange: (e) => setDraft(e(draft)), value: draft, })} diff --git a/client/src/components/layer-editor/LayerListEditor.tsx b/client/src/components/layer-editor/LayerListEditor.tsx index d38f19f6..408ec276 100644 --- a/client/src/components/layer-editor/LayerListEditor.tsx +++ b/client/src/components/layer-editor/LayerListEditor.tsx @@ -1,10 +1,10 @@ import { Box } from "@mui/material"; -import { LayerEditor } from "./LayerEditor"; import { ListEditor } from "components/generic/ListEditor"; -import { Layer, useUIState } from "slices/UIState"; +import { useLayers, Layer } from "slices/layers"; +import { LayerEditor } from "./LayerEditor"; export function LayerListEditor() { - const [{ layers = [] }, setUIState] = useUIState(); + const [{ layers: layers = [] }, setLayers] = useLayers(); // const [{ specimen }] = useSpecimen(); return ( @@ -20,7 +20,7 @@ export function LayerListEditor() { create={() => ({ source: { type: "trace", trace: {} }, })} - onChange={(v) => setUIState({ layers: v })} + onChange={(v) => setLayers(() => ({ layers: v }))} addItemLabel="Layer" placeholderText={ Click the button below to add a layer. @@ -29,4 +29,4 @@ export function LayerListEditor() { ); -} \ No newline at end of file +} diff --git a/client/src/components/layer-editor/layers/LayerSource.tsx b/client/src/components/layer-editor/layers/LayerSource.tsx index 7b4e5ba6..b96a9460 100644 --- a/client/src/components/layer-editor/layers/LayerSource.tsx +++ b/client/src/components/layer-editor/layers/LayerSource.tsx @@ -4,23 +4,23 @@ import { createElement, FC, ReactNode } from "react"; import { mapLayerSource } from "./mapLayerSource"; import { queryLayerSource } from "./queryLayerSource"; import { traceLayerSource } from "./traceLayerSource"; -import { EditorProps } from "components/Editor"; +import { EditorProps, EditorSetterProps } from "components/Editor"; import { SelectionMenuContent } from "components/inspector/SelectionMenu"; import { SelectEvent } from "components/renderer/Renderer"; import { PlaybackStateType } from "slices/playback"; -import { Layer } from "slices/UIState"; +import { Layer } from "slices/layers"; -export type SelectionInfoProvider = FC<{ - layer?: Layer; +export type SelectionInfoProvider = FC<{ + layer?: string; event?: SelectEvent; children?: (menu: SelectionMenuContent) => ReactNode; }>; export type LayerSource = { key: K; - editor: FC>>; + editor: FC>>; renderer: FC<{ layer?: Layer }>; - service?: FC>>; + service?: FC>>; inferName: (layer: Layer) => string; steps: FC<{ layer?: Layer; @@ -28,7 +28,7 @@ export type LayerSource = { }>; currentStep?: number; playback?: PlaybackStateType; - getSelectionInfo?: SelectionInfoProvider; + getSelectionInfo?: SelectionInfoProvider; }; export function RenderLayer({ layer }: { layer?: Layer }) { diff --git a/client/src/components/layer-editor/layers/mapLayerSource.tsx b/client/src/components/layer-editor/layers/mapLayerSource.tsx index 4480d57d..ba37eea1 100644 --- a/client/src/components/layer-editor/layers/mapLayerSource.tsx +++ b/client/src/components/layer-editor/layers/mapLayerSource.tsx @@ -2,19 +2,6 @@ import { PlaceOutlined as DestinationIcon, TripOriginOutlined as StartIcon, } from "@mui/icons-material"; -import { - filter, - isUndefined, - map, - reduce, - round, - set, - startCase, -} from "lodash"; -import { useMemo } from "react"; -import { inferLayerName, LayerSource } from "./LayerSource"; -import { Option } from "./Option"; -import { QueryLayerData } from "./queryLayerSource"; import { MapPicker } from "components/app-bar/Input"; import { NodeList } from "components/render/renderer/generic/NodeList"; import { getParser } from "components/renderer"; @@ -22,14 +9,22 @@ import { ParsedMap } from "components/renderer/map-parser/Parser"; import { useEffectWhen } from "hooks/useEffectWhen"; import { useMapContent } from "hooks/useMapContent"; import { useParsedMap } from "hooks/useParsedMap"; +import { filter, isUndefined, reduce, round, set, startCase } from "lodash"; import { produce, withProduce } from "produce"; -import { Layer, Map, useUIState } from "slices/UIState"; +import { useMemo } from "react"; +import { Map } from "slices/UIState"; +import { useLayer, Layer } from "slices/layers"; +import { LayerSource, inferLayerName } from "./LayerSource"; +import { Option } from "./Option"; +import { QueryLayerData } from "./queryLayerSource"; export type MapLayerData = { map?: Map; parsedMap?: ParsedMap; }; +export type MapLayer = Layer; + export const mapLayerSource: LayerSource<"map", MapLayerData> = { key: "map", inferName: (layer) => @@ -67,9 +62,9 @@ export const mapLayerSource: LayerSource<"map", MapLayerData> = { ); return <>; }), - getSelectionInfo: ({ children, event, layer }) => { + getSelectionInfo: ({ children, event, layer: key }) => { + const { layer, setLayer, layers } = useLayer(key); const { parsedMap } = layer?.source ?? {}; - const [{ layers }, setUIState] = useUIState(); const { point, node } = useMemo(() => { if (parsedMap && event) { const hydratedMap = getParser(layer?.source?.map?.format)?.hydrate?.( @@ -108,36 +103,28 @@ export const mapLayerSource: LayerSource<"map", MapLayerData> = { primary: `Set as source`, secondary: inferLayerName(next), action: () => - setUIState({ - layers: map(layers, (l2) => - l2.key === next.key - ? produce(l2, (l) => { - set(l, "source.start", node); - set(l, "source.query", undefined); - set(l, "source.mapLayerKey", layer.key); - set(l, "source.trace", undefined); - }) - : l2 - ), - }), + setLayer( + produce(layer, (l) => { + set(l, "source.start", node); + set(l, "source.query", undefined); + set(l, "source.mapLayerKey", layer.key); + set(l, "source.trace", undefined); + }) + ), icon: , }, [`${next.key}-b`]: { primary: `Set as destination`, secondary: inferLayerName(next), action: () => - setUIState({ - layers: map(layers, (l2) => - l2.key === next.key - ? produce(l2, (l) => { - set(l, "source.end", node); - set(l, "source.query", undefined); - set(l, "source.mapLayerKey", layer.key); - set(l, "source.trace", undefined); - }) - : l2 - ), - }), + setLayer( + produce(layer, (l) => { + set(l, "source.end", node); + set(l, "source.query", undefined); + set(l, "source.mapLayerKey", layer.key); + set(l, "source.trace", undefined); + }) + ), icon: , }, }), @@ -147,7 +134,7 @@ export const mapLayerSource: LayerSource<"map", MapLayerData> = { }, }), }; - }, [point, node, layer, layers, setUIState]); + }, [point, node, layer, layers, setLayer]); return <>{children?.(menu)}; }, }; diff --git a/client/src/components/layer-editor/layers/queryLayerSource.tsx b/client/src/components/layer-editor/layers/queryLayerSource.tsx index bd6bee20..ada7165d 100644 --- a/client/src/components/layer-editor/layers/queryLayerSource.tsx +++ b/client/src/components/layer-editor/layers/queryLayerSource.tsx @@ -1,19 +1,19 @@ +import { CodeOutlined, LayersOutlined } from "@mui/icons-material"; import { Box, Typography as Type } from "@mui/material"; import { FeaturePicker } from "components/app-bar/FeaturePicker"; import { useSnackbar } from "components/generic/Snackbar"; -import { filter, find, set } from "lodash"; +import { find, set } from "lodash"; import { withProduce } from "produce"; import { useMemo } from "react"; -import { Layer, useUIState } from "slices/UIState"; import { Connection, useConnections } from "slices/connections"; import { useFeatures } from "slices/features"; +import { useLayer, useLayers } from "slices/layers"; import { useEffectWhenAsync } from "../../../hooks/useEffectWhen"; import { LayerSource, inferLayerName } from "./LayerSource"; import { Heading, Option } from "./Option"; -import { MapLayerData } from "./mapLayerSource"; -import { TraceLayerData, traceLayerSource } from "./traceLayerSource"; import { TracePreview } from "./TracePreview"; -import { CodeOutlined, LayersOutlined } from "@mui/icons-material"; +import { MapLayer } from "./mapLayerSource"; +import { TraceLayerData, traceLayerSource } from "./traceLayerSource"; async function findConnection( connections: Connection[], @@ -39,12 +39,14 @@ export type QueryLayerData = { export const queryLayerSource: LayerSource<"query", QueryLayerData> = { key: "query", editor: withProduce(({ value, produce }) => { - const { algorithm, mapLayerKey } = value?.source ?? {}; - const [{ layers }] = useUIState(); + const { algorithm } = value?.source ?? {}; + const { + layers, + layer: selectedLayer, + key: mapLayerKey, + } = useLayer(undefined, (c): c is MapLayer => c.source?.type === "map"); const [{ algorithms }] = useFeatures(); const [connections] = useConnections(); - const filteredLayers = filter(layers, (c) => c.source?.type === "map"); - const selectedLayer = find(filteredLayers, { key: mapLayerKey }); return ( <> diff --git a/client/src/pages/SettingsPage.tsx b/client/src/pages/SettingsPage.tsx index cd67581d..0e222071 100644 --- a/client/src/pages/SettingsPage.tsx +++ b/client/src/pages/SettingsPage.tsx @@ -76,7 +76,7 @@ export function SettingsPage() { valueLabelDisplay="auto" defaultValue={playbackRate} onChangeCommitted={(_, v) => - setSettings({ playbackRate: v as number }) + setSettings(() => ({ playbackRate: v as number })) } /> @@ -86,7 +86,7 @@ export function SettingsPage() { setSettings({ acrylic: v })} + onChange={(_, v) => setSettings(() => ({ acrylic: v }))} /> @@ -95,7 +95,7 @@ export function SettingsPage() { - setSettings({ theme: v ? "dark" : "light" }) + setSettings(() => ({ theme: v ? "dark" : "light" })) } /> @@ -110,7 +110,7 @@ export function SettingsPage() { }))} showArrow onChange={(v) => - setSettings({ accentColor: v as AccentColor }) + setSettings(() => ({ accentColor: v as AccentColor })) } /> diff --git a/client/src/pages/StepsPage.tsx b/client/src/pages/StepsPage.tsx index b8ada106..2ae21c12 100644 --- a/client/src/pages/StepsPage.tsx +++ b/client/src/pages/StepsPage.tsx @@ -24,19 +24,12 @@ import { inferLayerName, layerHandlers, } from "components/layer-editor/layers/LayerSource"; -import { delay, find, head, map } from "lodash"; +import { usePlaybackState } from "hooks/usePlaybackState"; +import { delay, map } from "lodash"; import { Page } from "pages/Page"; import { TraceEvent } from "protocol"; -import { - cloneElement, - createElement, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import { useUIState } from "slices/UIState"; -import { usePlayback } from "slices/playback"; +import { cloneElement, createElement, useEffect, useMemo, useRef } from "react"; +import { useLayer } from "slices/layers"; const divider = ; @@ -45,16 +38,9 @@ const pxToInt = (s: string) => Number(s.replace(/px$/, "")); export function StepsPage() { const { spacing } = useTheme(); const { controls, onChange, state } = useViewTreeContext(); - const [{ step = 0, playback }, setPlayback] = usePlayback(); - const [{ layers }] = useUIState(); const ref = useRef(null); - - const [key, setKey] = useState(); - const layer = find(layers, { key }); - - useEffect(() => { - if (!key) setKey(head(layers)?.key); - }, [key, setKey, layers]); + const { key, setKey, layers, layer } = useLayer(); + const { step, playing, pause, stepTo } = usePlaybackState(key); const steps = useMemo(() => { if (layer) { @@ -65,7 +51,7 @@ export function StepsPage() { }, [layer]); useEffect(() => { - if (playback === "paused") { + if (!playing) { delay( () => ref?.current?.scrollToIndex?.({ @@ -77,14 +63,14 @@ export function StepsPage() { 150 ); } - }, [step, playback, spacing]); + }, [step, playing, spacing]); return ( {steps ? ( - playback !== "playing" ? ( + !playing ? ( cloneElement(steps, { children: (steps: TraceEvent[]) => layer ? ( @@ -113,6 +99,7 @@ export function StepsPage() { index={i} selected={i === step} sx={{ height: "100%" }} + onClick={() => stepTo(i)} /> @@ -136,9 +123,7 @@ export function StepsPage() { label={ <> Running - + } /> diff --git a/client/src/pages/TreePage.tsx b/client/src/pages/TreePage.tsx index 2962462f..ef6a30bb 100644 --- a/client/src/pages/TreePage.tsx +++ b/client/src/pages/TreePage.tsx @@ -21,9 +21,10 @@ import { Placeholder } from "components/inspector/Placeholder"; import { useViewTreeContext } from "components/inspector/ViewTree"; import { inferLayerName } from "components/layer-editor/layers/LayerSource"; import { getColorHex } from "components/renderer/colors"; -import { delay, entries, find, findLast, head, map, startCase } from "lodash"; +import { delay, entries, find, findLast, map, set, startCase } from "lodash"; import PopupState, { bindMenu } from "material-ui-popup-state"; import { Page } from "pages/Page"; +import { produce } from "produce"; import { FC, useCallback, useEffect, useState } from "react"; import { CustomNodeElementProps, @@ -32,8 +33,7 @@ import { } from "react-d3-tree"; import { useCss, useThrottle } from "react-use"; import AutoSize from "react-virtualized-auto-sizer"; -import { Layer, useUIState } from "slices/UIState"; -import { usePlayback } from "slices/playback"; +import { useLayer } from "slices/layers"; import { PanelState } from "slices/view"; import { useTreeMemo } from "./TreeWorker"; import { EventTree } from "./tree.worker"; @@ -76,17 +76,11 @@ const radius2 = { }; export function TreePage() { - const [{ step = 0 }] = usePlayback(); - const throttledStep = useThrottle(step, 600); const { palette } = useTheme(); - const [{ layers }] = useUIState(); - const [key, setKey] = useState(); - useEffect(() => { - if (!key) setKey(head(layers)?.key); - }, [key, setKey, layers]); + const { key, setKey, layer, setLayer, layers } = useLayer(); - const layer = find(layers, { key }) as Layer; + const throttledStep = useThrottle(layer?.source?.step ?? 0, 600); const { controls, onChange, state } = useViewTreeContext(); const [radius, setRadius] = useState("small"); @@ -136,6 +130,14 @@ export function TreePage() { onNodeClick?.({} as any)} + step={layer?.source?.step} + onStep={(s) => + setLayer( + produce(layer, (l) => { + set(l, "source.step", s); + }) + ) + } /> ); }} @@ -181,12 +183,20 @@ export function TreePage() { const width = 16; const height = 4; -function Node({ onClick, node }: { onClick?: () => void; node?: EventTree }) { - const [{ step = 0 }, setPlayback] = usePlayback(); - const throttledStep = useThrottle(step, 1000 / 24); +function Node({ + onClick, + node, + step = 0, + onStep, +}: { + onClick?: () => void; + node?: EventTree; + step?: number; + onStep?: (s: number) => void; +}) { const { palette, spacing, shape } = useTheme(); - const a = findLast(node?.events, (e) => e.step <= throttledStep); - const isSelected = !!find(node?.events, (e) => e.step === throttledStep); + const a = findLast(node?.events, (e) => e.step <= step); + const isSelected = !!find(node?.events, (e) => e.step === step); const color = getColorHex(a?.data?.type); return ( @@ -295,20 +305,14 @@ function Node({ onClick, node }: { onClick?: () => void; node?: EventTree }) { {map(node?.events, (e) => ( { state.close(); onClick?.(); - delay( - () => - setPlayback({ - step: e.step, - }), - 150 - ); + delay(() => onStep?.(e.step), 150); }} >