Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ describe("buildCaseArgsContentSignature", () => {
expect(
buildCaseArgsContentSignature("p", "c", [{ ...argument, input: "[1]" }]),
).not.toBe(base);
expect(
buildCaseArgsContentSignature("p", "c", [{ ...argument, label: "root" }]),
).not.toBe(base);
});

it("changes when parentName changes", () => {
Expand Down
1 change: 1 addition & 0 deletions src/entities/argument/lib/argumentObjectValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ArgumentType } from "../model/argumentObject";

export const argumentObjectValidator = z.object({
name: z.string(),
label: z.string().optional(),
type: z.nativeEnum(ArgumentType),
order: z.number(),
input: z.string(),
Expand Down
1 change: 1 addition & 0 deletions src/entities/argument/lib/caseArgsContentSignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const buildCaseArgsContentSignature = (
const normalizedArguments = [...argumentsList]
.map((argument) => ({
name: argument.name,
label: argument.label ?? null,
parentName: argument.parentName ?? null,
order: argument.order,
type: argument.type,
Expand Down
11 changes: 11 additions & 0 deletions src/entities/argument/lib/getArgumentDisplayLabel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ArgumentObject } from "#/entities/argument/model/types";

/**
* User-visible argument name. Multiple arguments may share the same label;
* `ArgumentObject.name` remains the unique store key.
*/
export const getArgumentDisplayLabel = (arg: ArgumentObject): string => {
const trimmed = arg.label?.trim();
if (trimmed) return trimmed;
return `arg-${arg.order + 1}`;
};
1 change: 1 addition & 0 deletions src/entities/argument/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { buildCaseArgsContentSignature } from "./caseArgsContentSignature";
export { isArgumentArrayType } from "./isArgumentArrayType";
export { isArgumentTreeType } from "./isArgumentTreeType";
export { isArgumentObjectValid } from "./isArgumentObjectValid";
export { getArgumentDisplayLabel } from "./getArgumentDisplayLabel";
14 changes: 14 additions & 0 deletions src/entities/argument/model/caseSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ export const caseSlice = createSlice({
isParsed: false,
};
},
updateArgumentLabel: (
state,
action: PayloadAction<{ name: string; label: string | undefined }>,
) => {
const { name, label } = action.payload;
if (!state.args.entities[name]) return;
const nextLabel =
label === undefined || label.trim() === "" ? undefined : label.trim();
argumentAdapter.updateOne(state.args, {
id: name,
changes: { label: nextLabel },
});
state.isEdited = true;
},
updateNodeData: (
state,
action: PayloadAction<ArgumentObject<ArgumentTreeType>>,
Expand Down
3 changes: 3 additions & 0 deletions src/entities/argument/model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ export type ArgumentArrayType =
| ArgumentType.OBJECT;

type BaseArgumentData = {
/** Stable unique id (Redux entity id, structure store key). */
name: string;
/** Optional display name; duplicates allowed. Falls back to `arg-${order + 1}`. */
label?: string;
parentName?: string;
order: number;
input: string;
Expand Down
22 changes: 19 additions & 3 deletions src/entities/dataStructures/array/model/arraySlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export type ArrayData = BaseStructureItem<ArrayItemData> & {
childNames?: string[];
colHeaders?: string[];
rowHeaders?: string[];
/** Inferred from user code (e.g. `const nums = [...]`); shown above the structure in the viewer. */
displayLabel?: string;
};

export type ArrayDataState = BaseStructureState<ArrayData>;
Expand All @@ -44,12 +46,14 @@ const getInitialData = (
argType: ArgumentArrayType,
parentName?: string,
childNames?: string[],
displayLabel?: string,
): ArrayData => ({
...getInitialDataBase(arrayDataAdapter),
order,
argType,
parentName,
childNames,
displayLabel,
});

const initialState: ArrayDataState = {};
Expand All @@ -74,10 +78,18 @@ export const arrayStructureSlice = createSlice({
childNames?: string[];
order: number;
argType: ArgumentArrayType;
displayLabel?: string;
}>,
) => {
const { name, order, argType, parentName, childNames } = action.payload;
state[name] = getInitialData(order, argType, parentName, childNames);
const { name, order, argType, parentName, childNames, displayLabel } =
action.payload;
state[name] = getInitialData(
order,
argType,
parentName,
childNames,
displayLabel,
);
},
create: (
state,
Expand All @@ -93,7 +105,7 @@ export const arrayStructureSlice = createSlice({
data: { argType, nodes, options },
},
} = action;
const { colorMap } = options ?? {};
const { colorMap, displayLabel } = options ?? {};
const treeState = {
...getInitialData(999, argType),
isRuntime: true,
Expand All @@ -106,6 +118,10 @@ export const arrayStructureSlice = createSlice({
treeState.colorMap = colorMap;
}

if (displayLabel !== undefined) {
treeState.displayLabel = displayLabel;
}

state[name] = treeState;
},
delete: (state, action: NamedPayload<void>) => {
Expand Down
40 changes: 32 additions & 8 deletions src/entities/dataStructures/array/model/arrayStructure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const ArrayBase = makeArrayBaseClass(Array);
export type ControlledArrayRuntimeOptions = {
length?: number;
colorMap?: Record<string, string>;
/** User source variable name (inferred by AST); shown as a viewer label, not the internal tree id. */
displayLabel?: string;
};

export function initControlledArray<T extends ArrayBaseType>(
Expand Down Expand Up @@ -269,22 +271,44 @@ export const getRuntimeArrayClass = (callstack: CallstackHelper) =>
class ArrayProxy<T extends string | number> extends ControlledArray<T> {
constructor(arrayLength: number);
constructor(...items: Array<T>) {
if (items.length === 1 && typeof items[0] === "number") {
const arrayLength = items[0];
items = new Array(arrayLength);
let runtimeOptions: ControlledArrayRuntimeOptions | undefined;
let elementItems = items as Array<T | number>;
const lastItem = elementItems.at(-1);
if (
elementItems.length >= 2 &&
lastItem !== null &&
lastItem !== undefined &&
typeof lastItem === "object" &&
!Array.isArray(lastItem) &&
"displayLabel" in lastItem
) {
runtimeOptions = lastItem as ControlledArrayRuntimeOptions;
elementItems = elementItems.slice(0, -1) as Array<T | number>;
}

if (elementItems.length === 1 && typeof elementItems[0] === "number") {
const arrayLength = elementItems[0];
elementItems = new Array(arrayLength) as Array<T | number>;
}

if (
items[0] &&
typeof items[0] !== "number" &&
typeof items[0] !== "string"
elementItems[0] &&
typeof elementItems[0] !== "number" &&
typeof elementItems[0] !== "string"
) {
throw new Error("ArrayProxy can only contain numbers or strings");
}

const data = generateArrayData(items);
const data = generateArrayData(elementItems as T[]);

super(items, generate(), data, callstack, true);
super(
elementItems as T[],
generate(),
data,
callstack,
true,
runtimeOptions,
);
}

static override from(
Expand Down
69 changes: 36 additions & 33 deletions src/entities/dataStructures/array/ui/ArrayStructureView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from "#/entities/dataStructures/array/model/arraySlice";

import { ArrayItem } from "./ArrayItem";
import { StructureDisplayLabel } from "./StructureDisplayLabel";

type ArrayBracketProps = {
argType: ArgumentType;
Expand Down Expand Up @@ -81,46 +82,48 @@ export const ArrayStructureView: React.FC<ArrayStructureViewProps> = ({
return arrayDataItemSelectors.selectAll(data.nodes);
}, [data.nodes]);

const { argType } = data;
const { argType, displayLabel } = data;

return (
<Stack
direction="row"
sx={{
width: "fit-content",
zIndex: 10,
"&:hover": {
".array-bracket": {
opacity: 1,
},
".array-item": {
"&::before": {
<Stack direction="column" sx={{ width: "fit-content", zIndex: 10 }}>
{displayLabel ? <StructureDisplayLabel label={displayLabel} /> : null}
<Stack
direction="row"
sx={{
width: "fit-content",
"&:hover": {
".array-bracket": {
opacity: 1,
},
".array-item": {
"&::before": {
opacity: 1,
},
},
},
},
}}
>
<ArrayBracket argType={argType} />
<Stack direction="row">
{items.length > 0 ? (
items.map((item) => (
<ArrayItem
key={item.id}
colorMap={data.colorMap ?? parentColorMap}
item={item}
}}
>
<ArrayBracket argType={argType} />
<Stack direction="row">
{items.length > 0 ? (
items.map((item) => (
<ArrayItem
key={item.id}
colorMap={data.colorMap ?? parentColorMap}
item={item}
/>
))
) : (
<Box
sx={{
width: 32,
height: 12,
}}
/>
))
) : (
<Box
sx={{
width: 32,
height: 12,
}}
/>
)}
)}
</Stack>
<ArrayBracket argType={argType} side="right" />
</Stack>
<ArrayBracket argType={argType} side="right" />
</Stack>
);
};
24 changes: 24 additions & 0 deletions src/entities/dataStructures/array/ui/StructureDisplayLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Typography } from "@mui/material";
import React from "react";

type StructureDisplayLabelProps = {
label: string;
};

/** Small caption above a runtime structure when we inferred a variable name from user code. */
export const StructureDisplayLabel: React.FC<StructureDisplayLabelProps> = ({
label,
}) => (
<Typography
component="div"
variant="caption"
color="text.secondary"
sx={{
fontFamily: "monospace",
lineHeight: 1.2,
mb: 0.25,
}}
>
{label}
</Typography>
);
Loading
Loading