Skip to content

Commit 0f95e8b

Browse files
committed
basic roundtrip OCIF import and export to code-flow-canvas without losing OCIF information that code-flow-canvas doesn't support
1 parent dde204d commit 0f95e8b

File tree

6 files changed

+335
-13
lines changed

6 files changed

+335
-13
lines changed

apps/vps-web/public/test.ocif.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"ocif": "https://canvasprotocol.org/ocif/0.2",
3+
"nodes": [
4+
{
5+
"id": "n1",
6+
"position": [100, 100],
7+
"resource": "r1"
8+
},
9+
{
10+
"id": "n2",
11+
"position": [100, 100],
12+
"data": [
13+
{
14+
"type": "circle-node",
15+
"radius": 50
16+
}
17+
]
18+
}
19+
],
20+
"resources": [
21+
{
22+
"id": "r1",
23+
"representations": [
24+
{ "mime-type": "text/plain", "content": "Hello, World!" }
25+
]
26+
}
27+
]
28+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"ocif": "https://canvasprotocol.org/ocif/0.2",
3+
"nodes": [
4+
{
5+
"id": "n1",
6+
"position": [100, 100],
7+
"resource": "r1"
8+
},
9+
{
10+
"id": "n2",
11+
"position": [100, 100],
12+
"data": [
13+
{
14+
"type": "circle-node",
15+
"radius": 50
16+
}
17+
]
18+
}
19+
],
20+
"relations": [
21+
{
22+
"type": "@ocwg/set",
23+
"members": ["n1", "n2"]
24+
}
25+
],
26+
"resources": [
27+
{
28+
"id": "r1",
29+
"representations": [
30+
{ "mime-type": "text/plain", "content": "Hello, World!" }
31+
]
32+
}
33+
]
34+
}

libs/app-canvas/src/app/components/navbar-components.tsx

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
setStopAnimations,
3939
} from '../follow-path/animate-path';
4040
import { OCWGExporter } from '../exporters/export-ocwg';
41+
import { clearOCIF, importOCIF, isValidOCIF } from '../importers/ocif-importer';
4142

4243
export class NavbarComponent extends Component<
4344
AppNavComponentsProps<NodeInfo>
@@ -143,13 +144,67 @@ export class NavbarComponent extends Component<
143144
textColorClasses="text-white"
144145
caption="Import"
145146
onClick={() => {
147+
clearOCIF();
146148
this.import();
147149
}}
148150
dropdownItems={[
149151
{
150152
caption: 'Import as OCIF',
151153
onClick: () => {
152-
//
154+
const input = document.createElement(
155+
'input'
156+
) as HTMLInputElement & {
157+
files: FileList;
158+
};
159+
160+
input.type = 'file';
161+
input.setAttribute('accept', 'application/JSON');
162+
input.onchange = () => {
163+
const files = Array.from(input.files);
164+
if (files && files.length > 0) {
165+
// const file = URL.createObjectURL(files[0]);
166+
// console.log(file);
167+
168+
const reader = new FileReader();
169+
reader.addEventListener('load', (event) => {
170+
const canvasApp = this.props.getCanvasApp();
171+
if (!canvasApp) {
172+
return;
173+
}
174+
if (event && event.target && event.target.result) {
175+
const ocifData = JSON.parse(
176+
event.target.result.toString()
177+
);
178+
if (!isValidOCIF(ocifData)) {
179+
alert('Invalid OCIF file');
180+
return;
181+
}
182+
const flow = importOCIF(ocifData);
183+
console.log('IMPORT DATA OCIF', ocifData, flow);
184+
setStopAnimations();
185+
const interval = setInterval(() => {
186+
if (!getIsStopAnimations()) {
187+
clearInterval(interval);
188+
this.props.clearCanvas();
189+
this.props.importToCanvas(
190+
flow.flows.flow.nodes,
191+
canvasApp,
192+
this.props.canvasUpdated,
193+
undefined,
194+
0,
195+
getNodeTaskFactory,
196+
flow.compositions
197+
);
198+
this.props.getCanvasApp()?.centerCamera();
199+
this.props.initializeNodes();
200+
}
201+
}, 0);
202+
}
203+
});
204+
reader.readAsBinaryString(files[0]);
205+
}
206+
};
207+
input.click();
153208
},
154209
},
155210
]}
@@ -651,6 +706,7 @@ export class NavbarComponent extends Component<
651706
event.preventDefault();
652707
const example = (event.target as HTMLSelectElement).value;
653708
if (example && confirm(`Are you sure you want to load ${example}?`)) {
709+
clearOCIF();
654710
fetch(`/example-flows/${example}`)
655711
.then((response) => response.json())
656712
.then((data) => {

libs/app-canvas/src/app/exporters/BaseExporter.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ export abstract class BaseExporter<T extends ExportFile, X> {
2525
protected createExportFile(): T {
2626
throw new Error('Method not implemented.');
2727
}
28+
protected mergeWithAdditionalIbfo(
29+
_elements: ElementNodeMap<BaseNodeInfo>
30+
): void {
31+
//
32+
}
2833
convertToExportFile(): T {
2934
this.file = this.createExportFile();
3035
this.exportNodes(undefined, this.exportInfo.canvasApp.elements);
36+
3137
return this.file;
3238
}
3339

@@ -137,6 +143,8 @@ export abstract class BaseExporter<T extends ExportFile, X> {
137143
}
138144
this.exportMultiPortConnection(connectionContextInfo, nodeInfo, node);
139145
});
146+
147+
this.mergeWithAdditionalIbfo(elements);
140148
}
141149
protected exportConnection(
142150
_contextInfo: X,

libs/app-canvas/src/app/exporters/export-ocwg.ts

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@ import {
44
BaseNodeInfo,
55
IThumbNodeComponent,
66
GetNodeTaskFactory,
7+
ElementNodeMap,
78
} from '@devhelpr/visual-programming-system';
89
import { Exporter } from './Exporter';
910

1011
import { BaseExporter } from './BaseExporter';
1112
import { OCWGFile, OCWGNode } from './ocwg/ocwg-schema';
1213
import { ocwgEmptyFile } from './ocwg/ocwg-empty-file';
1314
import { NodeInfo } from '@devhelpr/web-flow-executor';
15+
import { getCurrentOCIF } from '../importers/ocif-importer';
1416

1517
interface OCWGInfo {
1618
index: number;
1719
}
18-
const nodeInfoPropertyName = '@code-flow-canvas/node-properties';
19-
const connectionNodeInfoPropertyName =
20+
export const nodeInfoPropertyName = '@code-flow-canvas/node-properties';
21+
export const connectionNodeInfoPropertyName =
2022
'@code-flow-canvas/connection-properties';
2123

2224
export class OCWGExporter extends BaseExporter<OCWGFile, OCWGInfo> {
@@ -31,6 +33,67 @@ export class OCWGExporter extends BaseExporter<OCWGFile, OCWGInfo> {
3133
return structuredClone(ocwgEmptyFile);
3234
}
3335

36+
isValidCodeFlowCanvasNode(node: any): boolean {
37+
if (node.data && Array.isArray(node.data)) {
38+
return node.data.some(
39+
(d: any) =>
40+
d.type === nodeInfoPropertyName ||
41+
d.type === connectionNodeInfoPropertyName
42+
);
43+
}
44+
return false;
45+
}
46+
47+
doesRootOCIFNodeExistInFlow(
48+
id: string,
49+
elements: ElementNodeMap<BaseNodeInfo>
50+
): boolean {
51+
return elements.has(`${id}`);
52+
}
53+
54+
override mergeWithAdditionalIbfo(
55+
elements: ElementNodeMap<BaseNodeInfo>
56+
): void {
57+
const rootOCIF = getCurrentOCIF();
58+
if (!this.file || !rootOCIF) {
59+
return;
60+
}
61+
if (rootOCIF.resources) {
62+
this.file.resources = rootOCIF.resources;
63+
}
64+
if (rootOCIF.nodes) {
65+
rootOCIF.nodes.forEach((node: any) => {
66+
if (
67+
!this.isValidCodeFlowCanvasNode(node) &&
68+
!this.doesRootOCIFNodeExistInFlow(node.id, elements)
69+
) {
70+
this.file?.nodes.push(node);
71+
}
72+
});
73+
}
74+
if (rootOCIF.relations) {
75+
rootOCIF.relations.forEach((relation: any) => {
76+
if (!this.file?.relations.find((r) => r.id === relation.id)) {
77+
this.file?.relations.push(relation);
78+
}
79+
});
80+
}
81+
if (rootOCIF.schemas) {
82+
rootOCIF.schemas.forEach((schema: any) => {
83+
if (!this.file?.schemas.find((s) => s.uri === schema.uri)) {
84+
this.file?.schemas.push(schema);
85+
}
86+
});
87+
}
88+
if (rootOCIF.resources) {
89+
rootOCIF.resources.forEach((resource: any) => {
90+
if (!this.file?.resources.find((r) => r.uri === resource.uri)) {
91+
this.file?.resources.push(resource);
92+
}
93+
});
94+
}
95+
}
96+
3497
override createExportInfoContext(): OCWGInfo {
3598
return {
3699
index: 1,
@@ -70,7 +133,7 @@ export class OCWGExporter extends BaseExporter<OCWGFile, OCWGInfo> {
70133
});
71134
}
72135
const ocwgNode: OCWGNode = {
73-
id: `shape:${node.id}`,
136+
id: `${node.id}`,
74137
position: [node.x, node.y],
75138
size: [node.width ?? 0, node.height ?? 0],
76139
data: [
@@ -116,7 +179,7 @@ export class OCWGExporter extends BaseExporter<OCWGFile, OCWGInfo> {
116179
return;
117180
}
118181
const ocwgNode: OCWGNode = {
119-
id: `shape:${node.id}`,
182+
id: `connection:${node.id}`,
120183
position: [node.x, node.y],
121184
size: [node.width ?? 0, node.height ?? 0],
122185
data: [
@@ -125,11 +188,11 @@ export class OCWGExporter extends BaseExporter<OCWGFile, OCWGInfo> {
125188
type: connectionNodeInfoPropertyName,
126189
nodeType: nodeInfo.type,
127190
start: {
128-
connected_to: `shape:${node.startNode.id}`,
191+
connected_to: `${node.startNode.id}`,
129192
port_name: 'output',
130193
},
131194
end: {
132-
connected_to: `shape:${node.endNode.id}`,
195+
connected_to: `${node.endNode.id}`,
133196
port_name: 'input',
134197
},
135198
},
@@ -143,8 +206,8 @@ export class OCWGExporter extends BaseExporter<OCWGFile, OCWGInfo> {
143206
const relation = {
144207
type: '@ocwg/rel/edge' as const,
145208
id: `${node.id}-edge`,
146-
from: `shape:${node.startNode.id}`,
147-
to: `shape:${node.endNode.id}`,
209+
from: `${node.startNode.id}`,
210+
to: `${node.endNode.id}`,
148211
directed: true,
149212
};
150213
this.file.relations.push(relation);
@@ -170,11 +233,11 @@ export class OCWGExporter extends BaseExporter<OCWGFile, OCWGInfo> {
170233
type: connectionNodeInfoPropertyName,
171234
nodeType: nodeInfo.type,
172235
start: {
173-
connected_to: `shape:${node.startNode.id}`,
236+
connected_to: `${node.startNode.id}`,
174237
port_name: node.startNodeThumb?.thumbName,
175238
},
176239
end: {
177-
connected_to: `shape:${node.endNode.id}`,
240+
connected_to: `${node.endNode.id}`,
178241
port_name: node.endNodeThumb?.thumbName,
179242
},
180243
},
@@ -189,8 +252,8 @@ export class OCWGExporter extends BaseExporter<OCWGFile, OCWGInfo> {
189252
id: `${node.id}-edge`,
190253
type: '@ocwg/rel/edge' as const,
191254
directed: true,
192-
from: `shape:${node.startNode.id}`,
193-
to: `shape:${node.endNode.id}`,
255+
from: `${node.startNode.id}`,
256+
to: `${node.endNode.id}`,
194257
};
195258
this.file.relations.push(relation);
196259
}

0 commit comments

Comments
 (0)