Skip to content

Commit 0eacfbc

Browse files
authored
Merge pull request #296 from XpressAI/fahreza/reload-update
✨ Add Loading Display for Workflow Operations
2 parents f0688c9 + 29ec6d9 commit 0eacfbc

File tree

10 files changed

+167
-45
lines changed

10 files changed

+167
-45
lines changed

src/XircuitsFactory.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ export class XircuitsFactory extends ABCWidgetFactory<DocumentWidget> {
4343
runXircuitSignal: Signal<this, any>;
4444
runTypeXircuitSignal: Signal<this, any>;
4545
lockNodeSignal: Signal<this, any>;
46+
triggerLoadingAnimationSignal: Signal<this, any>;
4647
reloadAllNodesSignal: Signal<this, any>;
4748
toggleAllLinkAnimationSignal: Signal<this, any>;
4849

49-
50+
5051
constructor(options: any) {
5152
super(options);
5253
this.app = options.app;
@@ -59,6 +60,7 @@ export class XircuitsFactory extends ABCWidgetFactory<DocumentWidget> {
5960
this.runXircuitSignal = new Signal<this, any>(this);
6061
this.runTypeXircuitSignal = new Signal<this, any>(this);
6162
this.lockNodeSignal = new Signal<this, any>(this);
63+
this.triggerLoadingAnimationSignal = new Signal<this, any>(this);
6264
this.reloadAllNodesSignal = new Signal<this, any>(this);
6365
this.toggleAllLinkAnimationSignal = new Signal<this, any>(this);
6466
}
@@ -77,6 +79,7 @@ export class XircuitsFactory extends ABCWidgetFactory<DocumentWidget> {
7779
runXircuitSignal: this.runXircuitSignal,
7880
runTypeXircuitSignal: this.runTypeXircuitSignal,
7981
lockNodeSignal: this.lockNodeSignal,
82+
triggerLoadingAnimationSignal: this.triggerLoadingAnimationSignal,
8083
reloadAllNodesSignal: this.reloadAllNodesSignal,
8184
toggleAllLinkAnimationSignal: this.toggleAllLinkAnimationSignal,
8285

src/XircuitsWidget.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class XircuitsPanel extends ReactWidget {
2323
runXircuitSignal: Signal<this, any>;
2424
runTypeXircuitSignal: Signal<this, any>;
2525
lockNodeSignal: Signal<this, any>;
26+
triggerLoadingAnimationSignal: Signal<this, any>;
2627
reloadAllNodesSignal: Signal<this, any>;
2728
toggleAllLinkAnimationSignal: Signal<this, any>;
2829

@@ -42,6 +43,7 @@ export class XircuitsPanel extends ReactWidget {
4243
this.runXircuitSignal = options.runXircuitSignal;
4344
this.runTypeXircuitSignal = options.runTypeXircuitSignal;
4445
this.lockNodeSignal = options.lockNodeSignal;
46+
this.triggerLoadingAnimationSignal = options.triggerLoadingAnimationSignal;
4547
this.reloadAllNodesSignal = options.reloadAllNodesSignal;
4648
this.toggleAllLinkAnimationSignal = options.toggleAllLinkAnimationSignal;
4749
this.xircuitsApp = new XircuitsApplication(this.app, this.shell);
@@ -92,6 +94,7 @@ export class XircuitsPanel extends ReactWidget {
9294
runXircuitSignal={this.runXircuitSignal}
9395
runTypeXircuitSignal={this.runTypeXircuitSignal}
9496
lockNodeSignal={this.lockNodeSignal}
97+
triggerLoadingAnimationSignal={this.triggerLoadingAnimationSignal}
9598
reloadAllNodesSignal={this.reloadAllNodesSignal}
9699
toggleAllLinkAnimationSignal={this.toggleAllLinkAnimationSignal}
97100
/>

src/commands/NodeActionCommands.tsx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { PointModel } from '@projectstorm/react-diagrams';
2323
import { Point } from '@projectstorm/geometry';
2424
import { handleLiteralInput } from '../tray_library/GeneralComponentLib';
2525
import { CustomDynaPortModel } from '../components/port/CustomDynaPortModel';
26+
import { fetchComponents } from '../tray_library/Component';
27+
import { BaseComponentLibrary } from '../tray_library/BaseComponentLib';
2628

2729
/**
2830
* Add the commands for node actions.
@@ -204,6 +206,9 @@ export function addNodeActionCommands(
204206
// Add command to reload selected node
205207
commands.addCommand(commandIDs.reloadNode, {
206208
execute: async () => {
209+
210+
await fetchComponents();
211+
207212
const widget = tracker.currentWidget?.content as XircuitsPanel;
208213
const engine = widget.xircuitsApp.getDiagramEngine()
209214
const model = engine.getModel()
@@ -218,28 +223,31 @@ export function addNodeActionCommands(
218223
if (
219224
selected_node.name.startsWith("Literal") ||
220225
selected_node.name.startsWith("Argument") ||
221-
selected_node.name.startsWith("Start") ||
222-
selected_node.name.startsWith("Finish")
226+
selected_node.name.startsWith("Start")
223227
) {
224228
console.info(selected_node.name + " cannot be reloaded.");
225229
continue;
226230
}
227-
228-
let current_node = await fetchNodeByName(selected_node.name)
229-
231+
230232
let node;
231-
232-
try {
233-
node = AdvancedComponentLibrary({ model: current_node });
234-
} catch (error) {
235-
let path = selected_node.getOptions()["extras"].path;
236-
console.log(`Error reloading component from path: ${path}. Error: ${error.message}`);
237-
selected_node.getOptions().extras["tip"] = `Component could not be loaded from path: \`${path}\`.\nPlease ensure that the component exists!`;
238-
selected_node.getOptions().extras["borderColor"]="red";
239-
nodesToHighlight.push(selected_node)
240-
continue;
241-
}
242-
233+
234+
if (selected_node.name.startsWith("Finish")) {
235+
node = BaseComponentLibrary('Finish');
236+
} else {
237+
// For other nodes, fetch from AdvancedComponentLibrary
238+
try {
239+
let current_node = await fetchNodeByName(selected_node.name);
240+
node = AdvancedComponentLibrary({ model: current_node });
241+
} catch (error) {
242+
let path = selected_node.getOptions()["extras"].path;
243+
console.log(`Error reloading component from path: ${path}. Error: ${error.message}`);
244+
selected_node.getOptions().extras["tip"] = `Component could not be loaded from path: \`${path}\`.\nPlease ensure that the component exists!`;
245+
selected_node.getOptions().extras["borderColor"]="red";
246+
nodesToHighlight.push(selected_node);
247+
continue;
248+
}
249+
}
250+
243251
let nodePositionX = selected_node.getX();
244252
let nodePositionY = selected_node.getY();
245253

src/components/XircuitsApp.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ParameterLinkModel, TriangleLinkModel } from './link/CustomLinkModel';
99
import { ParameterLinkFactory, TriangleLinkFactory } from './link/CustomLinkFactory';
1010
import { PointModel } from '@projectstorm/react-diagrams';
1111
import { Point } from '@projectstorm/geometry';
12+
import { BaseComponentLibrary } from '../tray_library/BaseComponentLib';
1213

1314
export class XircuitsApplication {
1415

@@ -26,17 +27,13 @@ export class XircuitsApplication {
2627
this.diagramEngine.getActionEventBus().registerAction(new ZoomCanvasAction({ inverseZoom: true }))
2728
this.diagramEngine.getActionEventBus().registerAction(new CustomActionEvent({ app }));
2829
this.diagramEngine.getStateMachine().pushState(new CustomDiagramState());
29-
30-
let startNode = new CustomNodeModel({ name: 'Start', color: 'rgb(255,102,102)', extras: { "type": "Start" } });
31-
startNode.addOutPortEnhance({label: '▶', name: 'out-0'});
30+
31+
let startNode = BaseComponentLibrary('Start')
3232
startNode.setPosition(100, 100);
33+
let finishNode = BaseComponentLibrary('Finish')
34+
finishNode.setPosition(700, 100);
3335

34-
let finishedNode = new CustomNodeModel({ name: 'Finish', color: 'rgb(255,102,102)', extras: { "type": "Finish" } });
35-
finishedNode.addInPortEnhance({label: '▶', name: 'in-0'});
36-
finishedNode.addInPortEnhance({label: 'outputs', name: 'parameter-dynalist-outputs', varName: 'outputs', dataType: 'dynalist'});
37-
finishedNode.setPosition(700, 100);
38-
39-
this.activeModel.addAll(startNode, finishedNode);
36+
this.activeModel.addAll(startNode, finishNode);
4037
this.diagramEngine.setModel(this.activeModel);
4138
}
4239

src/components/XircuitsBodyWidget.tsx

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface BodyWidgetProps {
4444
runXircuitSignal: Signal<XircuitsPanel, any>;
4545
runTypeXircuitSignal: Signal<XircuitsPanel, any>;
4646
lockNodeSignal: Signal<XircuitsPanel, any>;
47+
triggerLoadingAnimationSignal: Signal<XircuitsPanel, any>;
4748
reloadAllNodesSignal: Signal<XircuitsPanel, any>;
4849
toggleAllLinkAnimationSignal: Signal<XircuitsPanel, any>;
4950
}
@@ -82,6 +83,7 @@ export const commandIDs = {
8283
cutNode: 'Xircuit-editor:cut-node',
8384
copyNode: 'Xircuit-editor:copy-node',
8485
pasteNode: 'Xircuit-editor:paste-node',
86+
triggerLoadingAnimation: 'Xircuit-editor:trigger-loading-animation',
8587
reloadNode: 'Xircuit-editor:reload-node',
8688
reloadAllNodes: 'Xircuit-editor:reload-all-nodes',
8789
toggleAllLinkAnimation: 'Xircuit-editor:toggle-all-link-animation',
@@ -111,6 +113,7 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
111113
runXircuitSignal,
112114
runTypeXircuitSignal,
113115
lockNodeSignal,
116+
triggerLoadingAnimationSignal,
114117
reloadAllNodesSignal,
115118
toggleAllLinkAnimationSignal,
116119
}) => {
@@ -126,6 +129,8 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
126129
const [floatNodes, setFloatNodes] = useState<string[]>([]);
127130
const [boolNodes, setBoolNodes] = useState<string[]>([]);
128131
const [componentList, setComponentList] = useState([]);
132+
const [isLoading, setIsLoading] = useState(false);
133+
const [loadingMessage, setLoadingMessage] = useState('Xircuits loading...');
129134
const [inDebugMode, setInDebugMode] = useState<boolean>(false);
130135
const [currentIndex, setCurrentIndex] = useState<number>(-1);
131136
const [runType, setRunType] = useState<string>("run");
@@ -506,6 +511,42 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
506511
return true;
507512
}
508513

514+
const triggerLoadingAnimation = async (
515+
operationPromise,
516+
{ loadingMessage = 'Xircuits loading...',
517+
loadingDisplayDuration = 1000,
518+
showLoadingAfter = 100 } = {}
519+
) => {
520+
if (shell.currentWidget?.id !== widgetId) {
521+
return;
522+
}
523+
524+
let shouldSetLoading = false;
525+
526+
setLoadingMessage(loadingMessage);
527+
528+
// Start a timer that will check if the operation exceeds showLoadingAfter
529+
const startTimer = setTimeout(() => {
530+
shouldSetLoading = true;
531+
setIsLoading(true);
532+
}, showLoadingAfter);
533+
534+
await operationPromise;
535+
536+
// Clear the start timer as the operation has completed
537+
clearTimeout(startTimer);
538+
539+
if (shouldSetLoading) {
540+
// If loading was started, ensure it stays for the minimum loading time
541+
const minTimer = setTimeout(() => setIsLoading(false), loadingDisplayDuration);
542+
// Clear the minimum timer to prevent memory leaks in case the component unmounts
543+
return () => clearTimeout(minTimer);
544+
} else {
545+
// If loading was not started, just ensure loading state is set to false
546+
setIsLoading(false);
547+
}
548+
};
549+
509550
const handleSaveClick = async () => {
510551
// Only save xircuit if it is currently in focus
511552
// This must be first to avoid unnecessary complication
@@ -636,17 +677,18 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
636677
});
637678
}
638679

639-
const handleReloadAll = () => {
640-
// This must be first to avoid unnecessary complication
680+
const handleReloadAll = async () => {
641681
if (shell.currentWidget?.id !== widgetId) {
642-
return;
682+
return;
643683
}
644-
645-
let allNodes = xircuitsApp.getDiagramEngine().getModel().getNodes()
646-
allNodes.forEach(node => node.setSelected(true));
647-
app.commands.execute(commandIDs.reloadNode);
648-
649-
}
684+
685+
const reloadPromise = app.commands.execute(commandIDs.reloadNode);
686+
687+
// Trigger loading animation
688+
await triggerLoadingAnimation(reloadPromise, { loadingMessage: 'Reloading all nodes...'});
689+
690+
console.log("Reload all complete.");
691+
};
650692

651693
const handleToggleAllLinkAnimation = () => {
652694
// This must be first to avoid unnecessary complication
@@ -801,6 +843,7 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
801843
[compileXircuitSignal, handleCompileClick],
802844
[runXircuitSignal, handleRunClick],
803845
[lockNodeSignal, handleLockClick],
846+
[triggerLoadingAnimationSignal, triggerLoadingAnimation],
804847
[reloadAllNodesSignal, handleReloadAll],
805848
[toggleAllLinkAnimationSignal, handleToggleAllLinkAnimation]
806849
];
@@ -1053,6 +1096,12 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
10531096
return (
10541097
<Body>
10551098
<Content>
1099+
{isLoading && (
1100+
<div className="loading-indicator">
1101+
<div className="loading-gif-wrapper"></div>
1102+
<div className="loading-text">{loadingMessage}</div>
1103+
</div>
1104+
)}
10561105
<Layer
10571106
onDrop={handleDropEvent}
10581107
onDragOver={preventDefault}
@@ -1063,15 +1112,15 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
10631112
onClick={handleClick}>
10641113
<XircuitsCanvasWidget>
10651114
<CanvasWidget engine={xircuitsApp.getDiagramEngine()} />
1066-
{/**Add Component Panel(ctrl + left-click, dropped link)*/}
1115+
{/* Add Component Panel(ctrl + left-click, dropped link) */}
10671116
{isComponentPanelShown && (
10681117
<div
10691118
onMouseEnter={()=>setDontHidePanel(true)}
10701119
onMouseLeave={()=>setDontHidePanel(false)}
10711120
id='component-panel'
10721121
style={{
10731122
top: componentPanelPosition.y,
1074-
left:componentPanelPosition.x
1123+
left: componentPanelPosition.x
10751124
}}
10761125
className="add-component-panel">
10771126
<ComponentsPanel
@@ -1081,10 +1130,10 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
10811130
linkData={looseLinkData}
10821131
isParameter={isParameterLink}
10831132
key="component-panel"
1084-
></ComponentsPanel>
1133+
/>
10851134
</div>
10861135
)}
1087-
{/**Node Action Panel(left-click)*/}
1136+
{/* Node Action Panel(left-click) */}
10881137
{contextMenuShown && (
10891138
<div
10901139
id='context-menu'
@@ -1097,12 +1146,12 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
10971146
app={app}
10981147
engine={xircuitsApp.getDiagramEngine()}
10991148
nodePosition={nodePosition}
1100-
></CanvasContextMenu>
1149+
/>
11011150
</div>
11021151
)}
11031152
</XircuitsCanvasWidget>
11041153
</Layer>
11051154
</Content>
11061155
</Body>
11071156
);
1108-
}
1157+
}

src/context-menu/CanvasContextMenu.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ export class CanvasContextMenu extends React.Component<CanvasContextMenuProps> {
2222
let models = this.props.engine.getModel().getSelectedEntities();
2323
let visibility = getMenuOptionsVisibility(models);
2424

25+
const handleReloadNode = async () => {
26+
let loadPromise = await this.props.app.commands.execute(commandIDs.reloadNode);
27+
await this.props.app.commands.execute(commandIDs.triggerLoadingAnimation, { loadPromise,
28+
loadingMessage: 'Reloading node...', loadingDisplayDuration: 10000, showLoadingAfter: 10
29+
});
30+
};
31+
2532
return (
2633
<div className="context-menu" onClick={this.hideCanvasContextMenu.bind(this)}>
2734
{visibility.showCutCopyPaste && (
@@ -32,7 +39,7 @@ export class CanvasContextMenu extends React.Component<CanvasContextMenuProps> {
3239
</>
3340
)}
3441
{visibility.showReloadNode && (
35-
<div className="context-menu-option" onClick={() => this.props.app.commands.execute(commandIDs.reloadNode)}>Reload Node</div>
42+
<div className="context-menu-option" onClick={handleReloadNode}>Reload Node</div>
3643
)}
3744
{visibility.showEdit && (
3845
<div className="context-menu-option" onClick={() => this.props.app.commands.execute(commandIDs.editNode)}>Edit</div>

src/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,14 @@ const xircuits: JupyterFrontEndPlugin<void> = {
319319
}
320320
});
321321

322+
// Add command signal to triggerLoadingAnimation
323+
app.commands.addCommand(commandIDs.triggerLoadingAnimation, {
324+
execute: args => {
325+
console.log("loading animation triggered!");
326+
widgetFactory.triggerLoadingAnimationSignal.emit(args);
327+
}
328+
});
329+
322330
// Add command signal to reloadAllNodes
323331
app.commands.addCommand(commandIDs.reloadAllNodes, {
324332
execute: args => {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { CustomNodeModel } from "../components/node/CustomNodeModel";
2+
3+
export function BaseComponentLibrary(nodeName: string): CustomNodeModel{
4+
5+
if (nodeName === 'Start') {
6+
let node = new CustomNodeModel({ name: 'Start', color: 'rgb(255,102,102)', extras: { "type": "Start" } });
7+
node.addOutPortEnhance({label: '▶', name: 'out-0'});
8+
return node;
9+
}
10+
11+
if (nodeName === 'Finish') {
12+
let node = new CustomNodeModel({ name: 'Finish', color: 'rgb(255,102,102)', extras: { "type": "Finish" } });
13+
node.addInPortEnhance({label: '▶', name: 'in-0'});
14+
node.addInPortEnhance({label: 'outputs', name: 'parameter-dynalist-outputs', varName: 'outputs', dataType: 'dynalist'});
15+
return node;
16+
}
17+
}

src/tray_library/Component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ let componentsCache = {
66
data: null
77
};
88

9-
async function fetchComponents() {
9+
export async function fetchComponents() {
1010
console.log("Fetching all components... this might take a while.")
1111
try {
1212
const componentsResponse = await requestAPI<any>('components/');

0 commit comments

Comments
 (0)