Skip to content

Commit cb45869

Browse files
Copilotmikebarkmin
andcommitted
Implement Strings as Objects option with seamless conversion between primitive and object representations
Co-authored-by: mikebarkmin <2592379+mikebarkmin@users.noreply.github.com>
1 parent 46f91ec commit cb45869

4 files changed

Lines changed: 153 additions & 8 deletions

File tree

src/ConfigView.tsx

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,124 @@ export const ConfigView = () => {
4242
}, [klasses, options, memory.klasses, memory.options]);
4343

4444
const onSave = useCallback(() => {
45+
// Check if stringsAsObjects option has changed
46+
const stringsAsObjectsChanged = options.stringsAsObjects !== memory.options.stringsAsObjects;
47+
4548
// Update existing objects to match new class definitions
4649
const updatedObjects = { ...memory.objects };
50+
const updatedMethodCalls = { ...memory.methodCalls };
51+
52+
// Helper to generate unique object IDs
53+
const generateId = () => `@${Math.random().toString(36).slice(2, 18)}`;
54+
55+
// Handle conversion when stringsAsObjects option changes
56+
if (stringsAsObjectsChanged) {
57+
if (options.stringsAsObjects) {
58+
// Converting TO String objects mode
59+
// Create String objects for all String primitive values
60+
61+
// Convert String attributes in heap objects
62+
Object.entries(updatedObjects).forEach(([_, obj]) => {
63+
Object.entries(obj.attributes).forEach(([attrName, attr]) => {
64+
if (attr.dataType === "String" && attr.value && typeof attr.value === "string" && !attr.value.startsWith("@")) {
65+
// Create a new String object
66+
const stringObjId = generateId();
67+
updatedObjects[stringObjId] = {
68+
klass: "String",
69+
attributes: {
70+
value: {
71+
dataType: "String",
72+
value: attr.value,
73+
},
74+
},
75+
position: {
76+
x: obj.position.x + 250,
77+
y: obj.position.y,
78+
},
79+
};
80+
// Update the attribute to reference the String object
81+
obj.attributes[attrName] = {
82+
dataType: "String",
83+
value: stringObjId,
84+
};
85+
}
86+
});
87+
});
88+
89+
// Convert String variables in method calls
90+
Object.entries(updatedMethodCalls).forEach(([_, call]) => {
91+
Object.entries(call.localVariables).forEach(([varName, variable]) => {
92+
if (variable.dataType === "String" && variable.value && typeof variable.value === "string" && !variable.value.startsWith("@")) {
93+
// Create a new String object
94+
const stringObjId = generateId();
95+
updatedObjects[stringObjId] = {
96+
klass: "String",
97+
attributes: {
98+
value: {
99+
dataType: "String",
100+
value: variable.value,
101+
},
102+
},
103+
position: {
104+
x: call.position.x + 250,
105+
y: call.position.y,
106+
},
107+
};
108+
// Update the variable to reference the String object
109+
call.localVariables[varName] = {
110+
dataType: "String",
111+
value: stringObjId,
112+
};
113+
}
114+
});
115+
});
116+
} else {
117+
// Converting FROM String objects mode TO primitives
118+
// Find all String objects and extract their values
119+
const stringObjects: Record<string, string> = {};
120+
Object.entries(updatedObjects).forEach(([objId, obj]) => {
121+
if (obj.klass === "String" && obj.attributes.value) {
122+
stringObjects[objId] = String(obj.attributes.value.value || "");
123+
}
124+
});
125+
126+
// Convert String references in heap objects back to primitives
127+
Object.entries(updatedObjects).forEach(([_, obj]) => {
128+
Object.entries(obj.attributes).forEach(([attrName, attr]) => {
129+
if (attr.dataType === "String" && attr.value && typeof attr.value === "string" && attr.value.startsWith("@")) {
130+
const stringValue = stringObjects[attr.value] || "";
131+
obj.attributes[attrName] = {
132+
dataType: "String",
133+
value: stringValue,
134+
};
135+
}
136+
});
137+
});
138+
139+
// Convert String references in method calls back to primitives
140+
Object.entries(updatedMethodCalls).forEach(([_, call]) => {
141+
Object.entries(call.localVariables).forEach(([varName, variable]) => {
142+
if (variable.dataType === "String" && variable.value && typeof variable.value === "string" && variable.value.startsWith("@")) {
143+
const stringValue = stringObjects[variable.value] || "";
144+
call.localVariables[varName] = {
145+
dataType: "String",
146+
value: stringValue,
147+
};
148+
}
149+
});
150+
});
151+
152+
// Remove all String objects
153+
Object.keys(stringObjects).forEach(objId => {
154+
delete updatedObjects[objId];
155+
});
156+
}
157+
}
47158

48159
Object.entries(updatedObjects).forEach(([objId, obj]) => {
49160
const klassDefinition = klasses[obj.klass];
50161

51-
// Skip if class doesn't exist (e.g., Array) or object class is not in klasses
162+
// Skip if class doesn't exist (e.g., Array, String) or object class is not in klasses
52163
if (!klassDefinition) return;
53164

54165
const updatedAttributes = { ...obj.attributes };
@@ -83,6 +194,7 @@ export const ConfigView = () => {
83194
klasses,
84195
options,
85196
objects: updatedObjects,
197+
methodCalls: updatedMethodCalls,
86198
});
87199
setHasUnsavedChanges(false);
88200
setShowSaveSuccess(true);
@@ -422,6 +534,28 @@ export const ConfigView = () => {
422534
/>
423535
Create New On Edge Drop
424536
</label>
537+
<label style={{
538+
display: "flex",
539+
alignItems: "center",
540+
gap: "8px",
541+
fontSize: "14px",
542+
color: "#374151",
543+
cursor: "pointer"
544+
}}>
545+
<input
546+
type="checkbox"
547+
checked={options.stringsAsObjects || false}
548+
onChange={(e) =>
549+
handleOptionChange("stringsAsObjects", e.target.checked)
550+
}
551+
style={{
552+
width: "16px",
553+
height: "16px",
554+
cursor: "pointer"
555+
}}
556+
/>
557+
Strings as Objects
558+
</label>
425559
</div>
426560
</div>
427561

src/ObjectNode.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import { CustomEdgeType, CustomNodeType } from "./types";
1414
import useStore, { RFState } from "./store";
1515
import { shallow } from "zustand/shallow";
1616

17+
const selector = (state: RFState) => ({
18+
memory: state.memory,
19+
...state.memory.options,
20+
});
21+
1722
function AttributeHandle({
1823
name,
1924
value,
@@ -29,6 +34,8 @@ function AttributeHandle({
2934
isConnectable: boolean;
3035
}) {
3136
const { setNodes } = useReactFlow<CustomNodeType, CustomEdgeType>();
37+
const { memory } = useStore(selector, shallow);
38+
const stringsAsObjects = memory.options.stringsAsObjects || false;
3239

3340
const onChange: ChangeEventHandler<HTMLInputElement> = (e) => {
3441
let value: any = e.target.value;
@@ -57,7 +64,7 @@ function AttributeHandle({
5764
);
5865
};
5966

60-
return !primitveDataTypes.includes(value.dataType) ? (
67+
return !primitveDataTypes.includes(value.dataType) || (value.dataType === "String" && stringsAsObjects) ? (
6168
<div className="object-node__attribute">
6269
<div className="object-node__attribute-name">{name} =</div>
6370
<Handle
@@ -110,10 +117,6 @@ export function isObjectNode(node: CustomNodeType): node is ObjectNodeType {
110117
return node.type === "object";
111118
}
112119

113-
const selector = (state: RFState) => ({
114-
...state.memory.options,
115-
});
116-
117120
function ObjectNode({ id, data }: NodeProps<ObjectNodeType>) {
118121
const { disableGarbageCollector } = useStore(selector, shallow);
119122
const nodes = useNodes();

src/getEdgesAndNodes.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ export const getEdgesAndNodes = (
1111
} => {
1212
const nodes: CustomNodeType[] = [];
1313
const edges: CustomEdgeType[] = [];
14+
const stringsAsObjects = memory.options.stringsAsObjects || false;
15+
16+
// Helper function to check if a dataType should be treated as an object reference
17+
const isObjectReference = (dataType: string) => {
18+
return !primitveDataTypes.includes(dataType) ||
19+
(dataType === "String" && stringsAsObjects);
20+
};
1421

1522
Object.entries(memory.variables).forEach(([id, data]) => {
1623
nodes.push({
@@ -40,7 +47,7 @@ export const getEdgesAndNodes = (
4047
const methodCallData = data as MethodCall;
4148

4249
Object.entries(methodCallData.localVariables).forEach(([name, value]) => {
43-
if (!primitveDataTypes.includes(value.dataType) && value.value != null) {
50+
if (isObjectReference(value.dataType) && value.value != null) {
4451
edges.push({
4552
id: `method-call-${id}+${name}`,
4653
type: "reference",
@@ -61,7 +68,7 @@ export const getEdgesAndNodes = (
6168
});
6269

6370
Object.entries(data.attributes).forEach(([name, value]) => {
64-
if (!primitveDataTypes.includes(value.dataType) && value.value != null) {
71+
if (isObjectReference(value.dataType) && value.value != null) {
6572
edges.push({
6673
id: `${id}+${name}`,
6774
source: id,

src/memory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export type Memory = {
9999
hideDeclareGlobalVariable?: boolean;
100100
hideNewArray?: boolean;
101101
createNewOnEdgeDrop?: boolean;
102+
stringsAsObjects?: boolean;
102103
};
103104
klasses: Klasses;
104105
objects: Objs;

0 commit comments

Comments
 (0)