mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 11:52:19 +00:00
Better epoch selection
This commit is contained in:
parent
a5eaba9ab3
commit
43e2f2f091
10 changed files with 341 additions and 166 deletions
|
@ -1,60 +1,28 @@
|
|||
import clsx from "clsx";
|
||||
import { EventEpoch } from "../../engine/engine";
|
||||
import { HashLink } from "react-router-hash-link";
|
||||
|
||||
export enum EpochCellView {
|
||||
Events,
|
||||
Graph,
|
||||
}
|
||||
|
||||
function invert(cell: EpochCellView): EpochCellView {
|
||||
if (cell === EpochCellView.Events) {
|
||||
return EpochCellView.Graph;
|
||||
}
|
||||
return EpochCellView.Events;
|
||||
}
|
||||
|
||||
function asStr(cell: EpochCellView): string {
|
||||
switch (cell) {
|
||||
case EpochCellView.Events:
|
||||
return "events";
|
||||
case EpochCellView.Graph:
|
||||
return "graph";
|
||||
}
|
||||
}
|
||||
|
||||
interface EpochCellProps {
|
||||
view: EpochCellView;
|
||||
epoch: EventEpoch;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
focus?: boolean;
|
||||
}
|
||||
|
||||
const EPOCH_STYLES_ARRAY = [
|
||||
"text-slate-900",
|
||||
"font-mono",
|
||||
"bg-slate-200",
|
||||
"p-1",
|
||||
"py-0",
|
||||
"rounded-sm",
|
||||
"ring-1",
|
||||
"ring-slate-500",
|
||||
"text-sm",
|
||||
];
|
||||
|
||||
export const EPOCH_STYLES = clsx(...EPOCH_STYLES_ARRAY);
|
||||
|
||||
export default function EpochCell({ epoch, className, view }: EpochCellProps) {
|
||||
const invertedView = invert(view);
|
||||
export const EPOCH_STYLES =
|
||||
"text-slate-900 font-mono bg-slate-200 p-1 py-0 rounded-sm text-sm transition ease-in-out duration-700 mr-2";
|
||||
|
||||
export default function EpochCell({
|
||||
className,
|
||||
children,
|
||||
focus,
|
||||
}: EpochCellProps) {
|
||||
return (
|
||||
<HashLink smooth to={`#${asStr(invertedView)}-${epoch}`}>
|
||||
<div
|
||||
id={`${asStr(view)}-${epoch}`}
|
||||
className={clsx(EPOCH_STYLES, className)}
|
||||
>
|
||||
{view === EpochCellView.Graph ? "Epoch " : ""}
|
||||
{epoch}
|
||||
</div>
|
||||
</HashLink>
|
||||
<span
|
||||
className={clsx(
|
||||
EPOCH_STYLES,
|
||||
className,
|
||||
focus === true ? "ring-2 ring-blue-500" : "ring-1 ring-slate-500"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ interface VariableProps extends CommonProps {
|
|||
|
||||
export function VariableEl({
|
||||
engine,
|
||||
toggleVariableVis,
|
||||
epoch,
|
||||
variable,
|
||||
graphEe,
|
||||
}: VariableProps): JSX.Element {
|
||||
engine.stepTo(epoch);
|
||||
return (
|
||||
|
@ -20,7 +20,7 @@ export function VariableEl({
|
|||
variable={variable}
|
||||
subs={engine.subs}
|
||||
onClick={(variable: Variable) => {
|
||||
toggleVariableVis(variable);
|
||||
graphEe.emit("focusVariable", variable);
|
||||
}}
|
||||
></VariableElPretty>
|
||||
);
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import type { Engine, EventEpoch } from "../../engine/engine";
|
||||
import type { Variable } from "../../schema";
|
||||
import { GraphMessage } from "../../utils/events";
|
||||
|
||||
export interface CommonProps {
|
||||
currentEpoch: EventEpoch;
|
||||
engine: Engine;
|
||||
toggleVariableVis: (variable: Variable) => void;
|
||||
graphEe: TypedEmitter<GraphMessage>;
|
||||
}
|
||||
|
|
|
@ -1,27 +1,30 @@
|
|||
import clsx from "clsx";
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import { EventEpoch } from "../engine/engine";
|
||||
import { lastSubEvent } from "../engine/event_util";
|
||||
import { UnificationMode, Event } from "../schema";
|
||||
import { EventListMessage, GraphMessage } from "../utils/events";
|
||||
import { Refine } from "../utils/refine";
|
||||
import EpochCell, { EpochCellView } from "./Common/EpochCell";
|
||||
import EpochCell from "./Common/EpochCell";
|
||||
import { CommonProps } from "./EventItem/types";
|
||||
import { VariableEl } from "./EventItem/Variable";
|
||||
|
||||
interface EventListProps extends CommonProps {
|
||||
events: Event[];
|
||||
eventListEe: TypedEmitter<EventListMessage>;
|
||||
root?: boolean;
|
||||
}
|
||||
|
||||
const MT = "mt-2.5";
|
||||
const UNFOCUSED = "opacity-40";
|
||||
const MT = "my-2.5";
|
||||
const LOWER_OPACITY = "opacity-40";
|
||||
|
||||
export default function EventList(props: EventListProps): JSX.Element {
|
||||
const { events, root } = props;
|
||||
return (
|
||||
<ul className={clsx(MT, root ? "ml-2" : "ml-[1.5em]")}>
|
||||
<ul className={clsx(MT, "space-y-2.5", root ? "" : "ml-[1em]")}>
|
||||
{events.map((event, i) => (
|
||||
<li key={i} className={MT}>
|
||||
<li key={i}>
|
||||
<OneEvent {...props} event={event} />
|
||||
</li>
|
||||
))}
|
||||
|
@ -31,6 +34,7 @@ export default function EventList(props: EventListProps): JSX.Element {
|
|||
|
||||
interface OneEventProps extends CommonProps {
|
||||
event: Event;
|
||||
eventListEe: TypedEmitter<EventListMessage>;
|
||||
}
|
||||
|
||||
function OneEvent(props: OneEventProps): JSX.Element {
|
||||
|
@ -61,10 +65,27 @@ function epochInRange(
|
|||
|
||||
interface UnificationProps extends CommonProps {
|
||||
event: Refine<Event, "Unification">;
|
||||
eventListEe: TypedEmitter<EventListMessage>;
|
||||
}
|
||||
|
||||
const COL_1_P = "pl-1.5";
|
||||
|
||||
const COL_1_OUTLINE_STYLES = clsx(COL_1_P, "outline-event-col-1");
|
||||
const COL_3_OUTLINE_STYLES = clsx("outline-event-col-3");
|
||||
|
||||
const COL_1_ROUNDED = "rounded-l-md";
|
||||
const COL_3_ROUNDED = "rounded-r-md";
|
||||
|
||||
const UN_EXPANDED_OUTLINE_STYLES = clsx(
|
||||
COL_1_P,
|
||||
"ring-inset ring-2 ring-blue-500"
|
||||
);
|
||||
|
||||
const TRANSITION_SHADOW = "transition-shadow ease-in-out duration-500";
|
||||
const TRANSITION_OPACITY = "transition-opacity ease-in-out duration-150";
|
||||
|
||||
function Unification(props: UnificationProps): JSX.Element {
|
||||
const { engine, event, currentEpoch } = props;
|
||||
const { engine, event, currentEpoch, graphEe, eventListEe } = props;
|
||||
const { mode, subevents, success } = event;
|
||||
|
||||
const beforeUnificationEpoch = engine.getEventIndex(event);
|
||||
|
@ -87,6 +108,10 @@ function Unification(props: UnificationProps): JSX.Element {
|
|||
);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const isOutlined = useFocusOutlineEvent({
|
||||
ee: eventListEe,
|
||||
epoch: currentEpoch,
|
||||
});
|
||||
|
||||
const modeIcon = useMemo(() => <UnificationModeIcon mode={mode} />, [mode]);
|
||||
|
||||
|
@ -95,14 +120,8 @@ function Unification(props: UnificationProps): JSX.Element {
|
|||
|
||||
const epochCell = useMemo(() => {
|
||||
if (!containsCurrentEpoch) return null;
|
||||
return (
|
||||
<EpochCell
|
||||
view={EpochCellView.Events}
|
||||
epoch={currentEpoch}
|
||||
className="inline-block align-middle mr-2"
|
||||
></EpochCell>
|
||||
);
|
||||
}, [containsCurrentEpoch, currentEpoch]);
|
||||
return <EventListEpochCell epoch={currentEpoch} graphEe={graphEe} />;
|
||||
}, [containsCurrentEpoch, currentEpoch, graphEe]);
|
||||
|
||||
const getHeadline = useCallback(
|
||||
({
|
||||
|
@ -154,13 +173,34 @@ function Unification(props: UnificationProps): JSX.Element {
|
|||
includeEpochIfInRange: true,
|
||||
});
|
||||
return (
|
||||
<div className={clsx(!containsCurrentEpoch && UNFOCUSED)}>{headLine}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
TRANSITION_OPACITY,
|
||||
!containsCurrentEpoch && LOWER_OPACITY
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"rounded-md",
|
||||
TRANSITION_SHADOW,
|
||||
containsCurrentEpoch && isOutlined
|
||||
? UN_EXPANDED_OUTLINE_STYLES
|
||||
: COL_1_P
|
||||
)}
|
||||
>
|
||||
{headLine}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const optEpochCellAfter =
|
||||
afterUnificationEpoch === currentEpoch && epochCell;
|
||||
const optEpochCellBefore =
|
||||
beforeUnificationEpoch === currentEpoch && epochCell;
|
||||
const beforeIsCurrentEpoch = beforeUnificationEpoch === currentEpoch;
|
||||
const afterIsCurrentEpoch = afterUnificationEpoch === currentEpoch;
|
||||
|
||||
const epochCellBefore = beforeIsCurrentEpoch && epochCell;
|
||||
const epochCellAfter = afterIsCurrentEpoch && epochCell;
|
||||
|
||||
const outlineEpochCellAfter = afterIsCurrentEpoch && isOutlined;
|
||||
const outlineEpochCellBefore = beforeIsCurrentEpoch && isOutlined;
|
||||
|
||||
const headlineBefore = getHeadline({
|
||||
epoch: beforeUnificationEpoch,
|
||||
|
@ -180,14 +220,37 @@ function Unification(props: UnificationProps): JSX.Element {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="grid gap-0 grid-cols-[min-content_min-content_auto]">
|
||||
<div
|
||||
className={clsx(
|
||||
"grid gap-0 grid-cols-[min-content_min-content_auto] opacity-100",
|
||||
TRANSITION_OPACITY
|
||||
)}
|
||||
>
|
||||
{/* Row 1: unification start */}
|
||||
<div className="row-start-1 col-start-1">{optEpochCellBefore}</div>
|
||||
<div className="row-start-1 col-start-3">{headlineBefore}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"row-start-1 col-start-1",
|
||||
TRANSITION_SHADOW,
|
||||
COL_1_ROUNDED,
|
||||
outlineEpochCellBefore ? COL_1_OUTLINE_STYLES : COL_1_P
|
||||
)}
|
||||
>
|
||||
{epochCellBefore}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"row-start-1 col-start-3",
|
||||
TRANSITION_SHADOW,
|
||||
COL_3_ROUNDED,
|
||||
outlineEpochCellBefore && COL_3_OUTLINE_STYLES
|
||||
)}
|
||||
>
|
||||
{headlineBefore}
|
||||
</div>
|
||||
|
||||
{/* Row 2: inner traces */}
|
||||
<div className="row-start-2 col-start-1"></div>
|
||||
<div className="row-start-2 col-start-3">
|
||||
<div className={clsx("row-start-2 col-start-1")}></div>
|
||||
<div className={clsx("row-start-2 col-start-3")}>
|
||||
<EventList
|
||||
{...props}
|
||||
root={false}
|
||||
|
@ -197,8 +260,26 @@ function Unification(props: UnificationProps): JSX.Element {
|
|||
</div>
|
||||
|
||||
{/* Row 3: inner traces */}
|
||||
<div className="row-start-3 col-start-1">{optEpochCellAfter}</div>
|
||||
<div className="row-start-3 col-start-3">{headlineAfter}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"row-start-3 col-start-1",
|
||||
TRANSITION_SHADOW,
|
||||
COL_1_ROUNDED,
|
||||
outlineEpochCellAfter ? COL_1_OUTLINE_STYLES : COL_1_P
|
||||
)}
|
||||
>
|
||||
{epochCellAfter}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"row-start-3 col-start-3",
|
||||
TRANSITION_SHADOW,
|
||||
COL_3_ROUNDED,
|
||||
outlineEpochCellAfter && COL_3_OUTLINE_STYLES
|
||||
)}
|
||||
>
|
||||
{headlineAfter}
|
||||
</div>
|
||||
|
||||
{/* Col 2: dropdown line */}
|
||||
<div
|
||||
|
@ -232,3 +313,56 @@ function UnificationModeIcon({ mode }: { mode: UnificationMode }): JSX.Element {
|
|||
return <>|~|</>;
|
||||
}
|
||||
}
|
||||
|
||||
interface EventListEpochCellProps {
|
||||
epoch: EventEpoch;
|
||||
graphEe: TypedEmitter<GraphMessage>;
|
||||
}
|
||||
|
||||
function EventListEpochCell({
|
||||
epoch,
|
||||
graphEe,
|
||||
}: EventListEpochCellProps): JSX.Element {
|
||||
return (
|
||||
<span
|
||||
id={`events-${epoch}`}
|
||||
className={clsx("cursor-pointer rounded-md")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
graphEe.emit("focusEpoch", epoch);
|
||||
}}
|
||||
>
|
||||
<EpochCell>{epoch}</EpochCell>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function useFocusOutlineEvent({
|
||||
epoch,
|
||||
ee,
|
||||
}: {
|
||||
epoch: EventEpoch;
|
||||
ee: TypedEmitter<EventListMessage>;
|
||||
}) {
|
||||
const [isOutlined, setIsOutlined] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
ee.on("focusEpoch", (focusEpoch: EventEpoch) => {
|
||||
if (focusEpoch !== epoch) return;
|
||||
setIsOutlined(true);
|
||||
});
|
||||
}, [ee, epoch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOutlined) return;
|
||||
const timer = setTimeout(() => {
|
||||
setIsOutlined(false);
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [isOutlined]);
|
||||
|
||||
return isOutlined;
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
import { Variable } from "../../schema";
|
||||
|
||||
export type ToggleVariableHandler = (variable: Variable) => void;
|
||||
export type KeydownHandler = (key: string) => Promise<void>;
|
|
@ -8,6 +8,7 @@ import { SubsSnapshot, TypeDescriptor } from "../../engine/subs";
|
|||
import { useEffect, useState } from "react";
|
||||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import { VariableLink } from "../Common/VariableLink";
|
||||
import { VariableMessage } from "../../utils/events";
|
||||
|
||||
type AddSubVariableLink = ({
|
||||
from,
|
||||
|
@ -17,17 +18,13 @@ type AddSubVariableLink = ({
|
|||
variable: Variable;
|
||||
}) => void;
|
||||
|
||||
export interface VariableMessageEvents {
|
||||
focus: (variable: Variable) => void;
|
||||
}
|
||||
|
||||
export interface VariableNodeProps {
|
||||
data: {
|
||||
subs: SubsSnapshot;
|
||||
rawVariable: Variable;
|
||||
addSubVariableLink: AddSubVariableLink;
|
||||
isOutlined: boolean;
|
||||
ee: TypedEmitter<VariableMessageEvents>;
|
||||
ee: TypedEmitter<VariableMessage>;
|
||||
};
|
||||
targetPosition?: Position;
|
||||
sourcePosition?: Position;
|
||||
|
@ -46,25 +43,11 @@ export default function VariableNode({
|
|||
ee: eeProp,
|
||||
} = data;
|
||||
|
||||
const [isOutlined, setIsOutlined] = useState(isOutlinedProp);
|
||||
|
||||
useEffect(() => {
|
||||
eeProp.on("focus", (focusVar: Variable) => {
|
||||
if (focusVar !== rawVariable) return;
|
||||
setIsOutlined(true);
|
||||
});
|
||||
}, [eeProp, rawVariable]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOutlined) return;
|
||||
const timer = setTimeout(() => {
|
||||
setIsOutlined(false);
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [isOutlined]);
|
||||
const isOutlined = useFocusOutlineEvent({
|
||||
ee: eeProp,
|
||||
isOutlinedProp,
|
||||
variable: rawVariable,
|
||||
});
|
||||
|
||||
const varType = subs.get(rawVariable);
|
||||
if (!varType) throw new Error("VariableNode: no entry for variable");
|
||||
|
@ -140,7 +123,7 @@ export default function VariableNode({
|
|||
<div
|
||||
className={clsx(
|
||||
bgStyles,
|
||||
"bg-opacity-50 rounded-lg transition ease-in-out duration-700",
|
||||
"bg-opacity-50 rounded-md transition ease-in-out duration-700",
|
||||
isContent ? "py-2 px-4 border" : "p-0",
|
||||
isOutlined && "ring-2 ring-blue-500",
|
||||
"text-center font-mono"
|
||||
|
@ -163,6 +146,38 @@ export default function VariableNode({
|
|||
);
|
||||
}
|
||||
|
||||
function useFocusOutlineEvent({
|
||||
variable,
|
||||
isOutlinedProp,
|
||||
ee,
|
||||
}: {
|
||||
variable: Variable;
|
||||
isOutlinedProp: boolean;
|
||||
ee: TypedEmitter<VariableMessage>;
|
||||
}) {
|
||||
const [isOutlined, setIsOutlined] = useState(isOutlinedProp);
|
||||
|
||||
useEffect(() => {
|
||||
ee.on("focus", (focusVar: Variable) => {
|
||||
if (focusVar !== variable) return;
|
||||
setIsOutlined(true);
|
||||
});
|
||||
}, [ee, variable]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOutlined) return;
|
||||
const timer = setTimeout(() => {
|
||||
setIsOutlined(false);
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [isOutlined]);
|
||||
|
||||
return isOutlined;
|
||||
}
|
||||
|
||||
function VariableNodeContent(
|
||||
variable: Variable,
|
||||
desc: TypeDescriptor | undefined,
|
||||
|
|
|
@ -26,19 +26,22 @@ import { Variable } from "../../schema";
|
|||
|
||||
import "reactflow/dist/style.css";
|
||||
import clsx from "clsx";
|
||||
import VariableNode, {
|
||||
VariableMessageEvents,
|
||||
VariableNodeProps,
|
||||
} from "./VariableNode";
|
||||
import VariableNode, { VariableNodeProps } from "./VariableNode";
|
||||
import { SubsSnapshot } from "../../engine/subs";
|
||||
import { KeydownHandler } from "../Events";
|
||||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import EpochCell, { EpochCellView } from "../Common/EpochCell";
|
||||
import EpochCell from "../Common/EpochCell";
|
||||
import { HashLink } from "react-router-hash-link";
|
||||
import { EventEpoch } from "../../engine/engine";
|
||||
import {
|
||||
EventListMessage,
|
||||
GraphMessage,
|
||||
VariableMessage,
|
||||
} from "../../utils/events";
|
||||
|
||||
export interface VariablesGraphProps {
|
||||
subs: SubsSnapshot;
|
||||
onVariable: (handler: (variable: Variable) => void) => void;
|
||||
onKeydown: (handler: KeydownHandler) => void;
|
||||
graphEe: TypedEmitter<GraphMessage>;
|
||||
eventListEe: TypedEmitter<EventListMessage>;
|
||||
}
|
||||
|
||||
function horizontalityToPositions(isHorizontal: boolean): {
|
||||
|
@ -285,11 +288,11 @@ function useAutoLayout(options: ComputeElkLayoutOptions) {
|
|||
function useKeydown({
|
||||
layoutConfig,
|
||||
setLayoutConfig,
|
||||
onKeydown,
|
||||
graphEe,
|
||||
}: {
|
||||
layoutConfig: LayoutConfiguration;
|
||||
setLayoutConfig: React.Dispatch<React.SetStateAction<LayoutConfiguration>>;
|
||||
onKeydown: (handler: KeydownHandler) => void;
|
||||
graphEe: TypedEmitter<GraphMessage>;
|
||||
}) {
|
||||
const redoLayout = useRedoLayout(layoutConfig);
|
||||
|
||||
|
@ -314,23 +317,64 @@ function useKeydown({
|
|||
},
|
||||
[redoLayout, setLayoutConfig]
|
||||
);
|
||||
onKeydown(async (key) => {
|
||||
await keyDownHandler(key);
|
||||
});
|
||||
graphEe.on("keydown", async (key) => await keyDownHandler(key));
|
||||
}
|
||||
|
||||
function useFocusOutlineEvent({
|
||||
epoch,
|
||||
ee,
|
||||
}: {
|
||||
epoch: EventEpoch;
|
||||
ee: TypedEmitter<GraphMessage>;
|
||||
}) {
|
||||
const [isOutlined, setIsOutlined] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
ee.on("focusEpoch", (focusEpoch: EventEpoch) => {
|
||||
if (focusEpoch !== epoch) return;
|
||||
setIsOutlined(true);
|
||||
});
|
||||
}, [ee, epoch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOutlined) return;
|
||||
const timer = setTimeout(() => {
|
||||
setIsOutlined(false);
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [isOutlined]);
|
||||
|
||||
return isOutlined;
|
||||
}
|
||||
|
||||
function Graph({
|
||||
subs,
|
||||
onVariable,
|
||||
onKeydown,
|
||||
graphEe,
|
||||
eventListEe,
|
||||
}: VariablesGraphProps): JSX.Element {
|
||||
const initialNodes: Node[] = [];
|
||||
const initialEdges: Edge[] = [];
|
||||
|
||||
const ee = useRef(new TypedEmitter<VariableMessageEvents>());
|
||||
const varEe = useRef(new TypedEmitter<VariableMessage>());
|
||||
// Allow an unbounded number of listeners since we attach a listener for each
|
||||
// variable.
|
||||
ee.current.setMaxListeners(Infinity);
|
||||
varEe.current.setMaxListeners(Infinity);
|
||||
|
||||
const isOutlined = useFocusOutlineEvent({
|
||||
epoch: subs.epoch,
|
||||
ee: graphEe,
|
||||
});
|
||||
|
||||
const [layoutConfig, setLayoutConfig] =
|
||||
useState<LayoutConfiguration>(LAYOUT_CONFIG_DOWN);
|
||||
|
||||
const [elements, setElements] = useState<LayoutedElements>({
|
||||
nodes: initialNodes,
|
||||
edges: initialEdges,
|
||||
});
|
||||
|
||||
const [variablesNeedingFocus, setVariablesNeedingFocus] = useState<
|
||||
Set<Variable>
|
||||
|
@ -341,24 +385,16 @@ function Graph({
|
|||
return;
|
||||
}
|
||||
for (const variable of variablesNeedingFocus) {
|
||||
ee.current.emit("focus", variable);
|
||||
varEe.current.emit("focus", variable);
|
||||
}
|
||||
setVariablesNeedingFocus(new Set());
|
||||
}, [variablesNeedingFocus]);
|
||||
|
||||
const [layoutConfig, setLayoutConfig] =
|
||||
useState<LayoutConfiguration>(LAYOUT_CONFIG_DOWN);
|
||||
|
||||
useAutoLayout(layoutConfig);
|
||||
useKeydown({
|
||||
layoutConfig,
|
||||
setLayoutConfig,
|
||||
onKeydown,
|
||||
});
|
||||
|
||||
const [elements, setElements] = useState<LayoutedElements>({
|
||||
nodes: initialNodes,
|
||||
edges: initialEdges,
|
||||
graphEe,
|
||||
});
|
||||
|
||||
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
||||
|
@ -405,7 +441,7 @@ function Graph({
|
|||
rawVariable: toVariable,
|
||||
addSubVariableLink: addNewVariable,
|
||||
isOutlined: true,
|
||||
ee: ee.current,
|
||||
ee: varEe.current,
|
||||
},
|
||||
layoutConfig.isHorizontal
|
||||
);
|
||||
|
@ -465,7 +501,7 @@ function Graph({
|
|||
[addNewVariable]
|
||||
);
|
||||
|
||||
onVariable(addNewVariableNode);
|
||||
graphEe.on("focusVariable", addNewVariableNode);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
|
@ -481,9 +517,21 @@ function Graph({
|
|||
// https://reactflow.dev/docs/guides/remove-attribution/
|
||||
hideAttribution: true,
|
||||
}}
|
||||
className={clsx(
|
||||
"ring-inset rounded-md transition ease-in-out duration-700",
|
||||
isOutlined && "ring-2 ring-blue-500"
|
||||
)}
|
||||
>
|
||||
<Panel position="top-left">
|
||||
<EpochCell view={EpochCellView.Graph} epoch={subs.epoch}></EpochCell>
|
||||
<HashLink
|
||||
smooth
|
||||
to={`#events-${subs.epoch}`}
|
||||
onClick={() => {
|
||||
eventListEe.emit("focusEpoch", subs.epoch);
|
||||
}}
|
||||
>
|
||||
<EpochCell>Epoch {subs.epoch}</EpochCell>
|
||||
</HashLink>
|
||||
</Panel>
|
||||
<Panel position="top-right">
|
||||
<LayoutPanel
|
||||
|
|
|
@ -1,32 +1,20 @@
|
|||
import React, { useState } from "react";
|
||||
import { AllEvents, Variable } from "../schema";
|
||||
import { AllEvents } from "../schema";
|
||||
import { Engine } from "../engine/engine";
|
||||
import EventList from "./EventList";
|
||||
import VariablesGraph from "./Graph/VariablesGraph";
|
||||
import { TypedEmitter } from "tiny-typed-emitter";
|
||||
import { KeydownHandler, ToggleVariableHandler } from "./Events";
|
||||
import { EventListMessage, GraphMessage } from "../utils/events";
|
||||
|
||||
interface UiProps {
|
||||
events: AllEvents;
|
||||
}
|
||||
|
||||
interface MessageEvents {
|
||||
toggleVariable: ToggleVariableHandler;
|
||||
keydown: KeydownHandler;
|
||||
}
|
||||
|
||||
export default function Ui({ events }: UiProps): JSX.Element {
|
||||
const engine = new Engine(events);
|
||||
|
||||
const ee = new TypedEmitter<MessageEvents>();
|
||||
const toggleVariableHandlers: ToggleVariableHandler[] = [];
|
||||
const keydownHandlers: KeydownHandler[] = [];
|
||||
ee.on("toggleVariable", (variable: Variable) => {
|
||||
toggleVariableHandlers.forEach((handler) => handler(variable));
|
||||
});
|
||||
ee.on("keydown", async (key: string) => {
|
||||
await Promise.all(keydownHandlers.map((handler) => handler(key)));
|
||||
});
|
||||
const graphEe = React.useRef(new TypedEmitter<GraphMessage>());
|
||||
const eventListEe = React.useRef(new TypedEmitter<EventListMessage>());
|
||||
|
||||
engine.stepTo(engine.lastEventIndex());
|
||||
const subs = engine.subsSnapshot();
|
||||
|
@ -38,7 +26,7 @@ export default function Ui({ events }: UiProps): JSX.Element {
|
|||
<div
|
||||
className="flex flex-col md:flex-row gap-0 w-full h-full"
|
||||
onKeyDown={(e) => {
|
||||
ee.emit("keydown", e.key);
|
||||
graphEe.current.emit("keydown", e.key);
|
||||
}}
|
||||
>
|
||||
<div className="font-mono mt-2 text-lg md:flex-1 overflow-scroll">
|
||||
|
@ -46,21 +34,16 @@ export default function Ui({ events }: UiProps): JSX.Element {
|
|||
engine={engine}
|
||||
root
|
||||
events={events}
|
||||
toggleVariableVis={(variable: Variable) =>
|
||||
ee.emit("toggleVariable", variable)
|
||||
}
|
||||
graphEe={graphEe.current}
|
||||
eventListEe={eventListEe.current}
|
||||
currentEpoch={epoch}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-h-[50%] h-full">
|
||||
<VariablesGraph
|
||||
subs={subs}
|
||||
onVariable={(handler) => {
|
||||
toggleVariableHandlers.push(handler);
|
||||
}}
|
||||
onKeydown={(handler) => {
|
||||
keydownHandlers.push(handler);
|
||||
}}
|
||||
graphEe={graphEe.current}
|
||||
eventListEe={eventListEe.current}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.outline-event-col-1 {
|
||||
box-shadow: inset 2px 0px 0px #3B82F6, /* Left */
|
||||
inset -2px 0px 0px transparent, /* Right */
|
||||
inset 0px 2px 0px #3B82F6, /* Bottom */
|
||||
inset 0px -2px 0px #3B82F6; /* Top */
|
||||
}
|
||||
|
||||
.outline-event-col-3 {
|
||||
box-shadow: inset 2px 0px 0px transparent, /* Left */
|
||||
inset -2px 0px 0px #3B82F6, /* Right */
|
||||
inset 0px 2px 0px #3B82F6, /* Bottom */
|
||||
inset 0px -2px 0px #3B82F6; /* Top */
|
||||
}
|
||||
|
|
16
crates/compiler/checkmate/www/src/utils/events.ts
Normal file
16
crates/compiler/checkmate/www/src/utils/events.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { EventEpoch } from "../engine/engine";
|
||||
import { Variable } from "../schema";
|
||||
|
||||
export interface VariableMessage {
|
||||
focus: (variable: Variable) => void;
|
||||
}
|
||||
|
||||
export interface GraphMessage {
|
||||
focusEpoch: (epoch: EventEpoch) => void;
|
||||
focusVariable: (variable: Variable) => void;
|
||||
keydown: (key: string) => void;
|
||||
}
|
||||
|
||||
export interface EventListMessage {
|
||||
focusEpoch: (epoch: EventEpoch) => void;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue