Skip to content

Commit 2cfc3dd

Browse files
committed
refactor: Allow for curNode being null.
1 parent a8027df commit 2cfc3dd

File tree

8 files changed

+83
-57
lines changed

8 files changed

+83
-57
lines changed

src/actions/action_menu.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export class ActionMenu {
111111
const cursor = workspace.getCursor();
112112
if (!cursor) throw new Error('workspace has no cursor');
113113
const node = cursor.getCurNode();
114+
if (!node) throw new Error('No node is currently selected');
114115
const nodeType = node.getType();
115116
switch (nodeType) {
116117
case ASTNode.types.BLOCK:
@@ -154,16 +155,16 @@ export class ActionMenu {
154155
connection,
155156
} as unknown as ContextMenuRegistry.Scope;
156157
for (const option of possibleOptions) {
157-
const precondition = option.preconditionFn(scope);
158+
const precondition = option.preconditionFn?.(scope);
158159
if (precondition === 'hidden') continue;
159160
const displayText =
160-
typeof option.displayText === 'function'
161+
(typeof option.displayText === 'function'
161162
? option.displayText(scope)
162-
: option.displayText;
163+
: option.displayText) ?? '';
163164
menuOptions.push({
164165
text: displayText,
165166
enabled: precondition === 'enabled',
166-
callback: option.callback,
167+
callback: option.callback!,
167168
scope,
168169
weight: option.weight,
169170
});

src/actions/arrow_navigation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class ArrowNavigation {
4040
return false;
4141
}
4242
const curNode = cursor.getCurNode();
43-
if (curNode.getType() === ASTNode.types.FIELD) {
43+
if (curNode?.getType() === ASTNode.types.FIELD) {
4444
return (curNode.getLocation() as Field).onShortcut(shortcut);
4545
}
4646
return false;

src/actions/clipboard.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ export class Clipboard {
169169
private cutCallback(workspace: WorkspaceSvg) {
170170
const cursor = workspace.getCursor();
171171
if (!cursor) throw new TypeError('no cursor');
172-
const sourceBlock = cursor.getCurNode().getSourceBlock() as BlockSvg | null;
172+
const sourceBlock = cursor
173+
.getCurNode()
174+
?.getSourceBlock() as BlockSvg | null;
173175
if (!sourceBlock) throw new TypeError('no source block');
174176
this.copyData = sourceBlock.toCopyData();
175177
this.copyWorkspace = sourceBlock.workspace;
@@ -274,7 +276,9 @@ export class Clipboard {
274276
const sourceBlock = activeWorkspace
275277
?.getCursor()
276278
?.getCurNode()
277-
.getSourceBlock() as BlockSvg;
279+
?.getSourceBlock() as BlockSvg;
280+
if (!sourceBlock) return false;
281+
278282
workspace.hideChaff();
279283
this.copyData = sourceBlock.toCopyData();
280284
this.copyWorkspace = sourceBlock.workspace;

src/actions/delete.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export class DeleteAction {
150150
private deletePrecondition(workspace: WorkspaceSvg) {
151151
if (!this.canCurrentlyEdit(workspace)) return false;
152152

153-
const sourceBlock = workspace.getCursor()?.getCurNode().getSourceBlock();
153+
const sourceBlock = workspace.getCursor()?.getCurNode()?.getSourceBlock();
154154
return !!sourceBlock?.isDeletable();
155155
}
156156

@@ -169,7 +169,10 @@ export class DeleteAction {
169169
const cursor = workspace.getCursor();
170170
if (!cursor) return false;
171171

172-
const sourceBlock = cursor.getCurNode().getSourceBlock() as BlockSvg;
172+
const sourceBlock = cursor
173+
.getCurNode()
174+
?.getSourceBlock() as BlockSvg | null;
175+
if (!sourceBlock) return false;
173176
// Delete or backspace.
174177
// There is an event if this is triggered from a keyboard shortcut,
175178
// but not if it's triggered from a context menu.

src/actions/enter.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export class EnterAction {
5959
return false;
6060
}
6161
curNode = flyoutCursor.getCurNode();
62-
nodeType = curNode.getType();
62+
nodeType = curNode?.getType();
6363

6464
switch (nodeType) {
6565
case ASTNode.types.STACK:
@@ -88,22 +88,25 @@ export class EnterAction {
8888
const cursor = workspace.getCursor();
8989
if (!cursor) return;
9090
const curNode = cursor.getCurNode();
91-
const nodeType = curNode.getType();
91+
const nodeType = curNode?.getType();
9292
if (nodeType === ASTNode.types.FIELD) {
93-
(curNode.getLocation() as Field).showEditor();
93+
(curNode!.getLocation() as Field).showEditor();
9494
} else if (nodeType === ASTNode.types.BLOCK) {
95-
const block = curNode.getLocation() as Block;
96-
if (!this.tryShowFullBlockFieldEditor(block)) {
95+
const block = curNode?.getLocation() as Block | undefined;
96+
if (block && !this.tryShowFullBlockFieldEditor(block)) {
9797
const metaKey = navigator.platform.startsWith('Mac') ? 'Cmd' : 'Ctrl';
9898
const canMoveInHint = `Press right arrow to move in or ${metaKey} + Enter for more options`;
9999
const genericHint = `Press ${metaKey} + Enter for options`;
100100
const hint =
101-
curNode.in()?.getSourceBlock() === block
101+
curNode?.in()?.getSourceBlock() === block
102102
? canMoveInHint
103103
: genericHint;
104104
alert(hint);
105105
}
106-
} else if (curNode.isConnection() || nodeType === ASTNode.types.WORKSPACE) {
106+
} else if (
107+
curNode?.isConnection() ||
108+
nodeType === ASTNode.types.WORKSPACE
109+
) {
107110
this.navigation.openToolboxOrFlyout(workspace);
108111
} else if (nodeType === ASTNode.types.STACK) {
109112
console.warn('Cannot mark a stack.');
@@ -152,7 +155,8 @@ export class EnterAction {
152155
const button = this.navigation
153156
.getFlyoutCursor(workspace)!
154157
.getCurNode()
155-
.getLocation() as FlyoutButton;
158+
?.getLocation() as FlyoutButton | undefined;
159+
if (!button) return;
156160
const buttonCallback = (workspace as any).flyoutButtonCallbacks.get(
157161
(button as any).callbackKey,
158162
);
@@ -204,8 +208,8 @@ export class EnterAction {
204208
const curBlock = this.navigation
205209
.getFlyoutCursor(workspace)!
206210
.getCurNode()
207-
.getLocation() as BlockSvg;
208-
if (!curBlock.isEnabled()) {
211+
?.getLocation() as BlockSvg | undefined;
212+
if (!curBlock?.isEnabled()) {
209213
console.warn("Can't insert a disabled block.");
210214
return null;
211215
}

src/actions/ws_movement.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class WorkspaceMovement {
5555
callback: (workspace) => this.moveWSCursor(workspace, 0, -1),
5656
keyCodes: [createSerializedKey(KeyCodes.W, [KeyCodes.SHIFT])],
5757
},
58-
58+
5959
/** Move the cursor on the workspace down. */
6060
{
6161
name: Constants.SHORTCUT_NAMES.MOVE_WS_CURSOR_DOWN,
@@ -100,7 +100,7 @@ export class WorkspaceMovement {
100100
const cursor = workspace.getCursor();
101101
if (!cursor) return false;
102102
const curNode = cursor?.getCurNode();
103-
if (curNode.getType() !== ASTNode.types.WORKSPACE) return false;
103+
if (!curNode || curNode.getType() !== ASTNode.types.WORKSPACE) return false;
104104

105105
const wsCoord = curNode.getWsCoordinate();
106106
const newX = xDirection * this.WS_MOVE_DISTANCE + wsCoord.x;

src/line_cursor.ts

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ export class LineCursor extends Marker {
7474
const markerManager = this.workspace.getMarkerManager();
7575
this.oldCursor = markerManager.getCursor();
7676
markerManager.setCursor(this);
77-
if (this.oldCursor) this.setCurNode(this.oldCursor.getCurNode());
77+
const oldCursorNode = this.oldCursor?.getCurNode();
78+
if (oldCursorNode) this.setCurNode(oldCursorNode);
7879
this.workspace.addChangeListener(this.selectListener);
7980
this.installed = true;
8081
}
@@ -402,7 +403,7 @@ export class LineCursor extends Marker {
402403
preDelete(deletedBlock: Blockly.Block) {
403404
const curNode = this.getCurNode();
404405

405-
const nodes: Blockly.ASTNode[] = [curNode];
406+
const nodes: Blockly.ASTNode[] = curNode ? [curNode] : [];
406407
// The connection to which the deleted block is attached.
407408
const parentConnection =
408409
deletedBlock.previousConnection?.targetConnection ??
@@ -425,7 +426,7 @@ export class LineCursor extends Marker {
425426
}
426427
// A location on the workspace beneath the deleted block.
427428
// Move to the workspace.
428-
const curBlock = curNode.getSourceBlock();
429+
const curBlock = curNode?.getSourceBlock();
429430
if (curBlock) {
430431
const workspaceNode = Blockly.ASTNode.createWorkspaceNode(
431432
this.workspace,
@@ -475,7 +476,7 @@ export class LineCursor extends Marker {
475476
*
476477
* @returns The current field, connection, or block the cursor is on.
477478
*/
478-
override getCurNode(): ASTNode {
479+
override getCurNode(): ASTNode | null {
479480
const curNode = super.getCurNode();
480481
const selected = Blockly.common.getSelected();
481482
if (selected?.workspace !== this.workspace) return curNode;
@@ -494,6 +495,40 @@ export class LineCursor extends Marker {
494495
return newNode;
495496
}
496497

498+
/**
499+
* Sets the object in charge of drawing the marker.
500+
*
501+
* We want to customize drawing, so rather than directly setting the given
502+
* object, we instead set a wrapper proxy object that passes through all
503+
* method calls and property accesses except for draw(), which it delegates
504+
* to the drawMarker() method in this class.
505+
*
506+
* @param drawer The object ~in charge of drawing the marker.
507+
*/
508+
override setDrawer(drawer: Blockly.blockRendering.MarkerSvg) {
509+
const altDraw = function (
510+
this: LineCursor,
511+
oldNode: ASTNode | null,
512+
curNode: ASTNode | null,
513+
) {
514+
// Pass the unproxied, raw drawer object so that drawMarker can call its
515+
// `draw()` method without triggering infinite recursion.
516+
this.drawMarker(oldNode, curNode, drawer);
517+
}.bind(this);
518+
519+
super.setDrawer(
520+
new Proxy(drawer, {
521+
get(target: typeof drawer, prop: keyof typeof drawer) {
522+
if (prop === 'draw') {
523+
return altDraw;
524+
}
525+
526+
return target[prop];
527+
},
528+
}),
529+
);
530+
}
531+
497532
/**
498533
* Set the location of the cursor and draw it.
499534
*
@@ -503,17 +538,7 @@ export class LineCursor extends Marker {
503538
* @param newNode The new location of the cursor.
504539
*/
505540
override setCurNode(newNode: ASTNode) {
506-
const oldNode = super.getCurNode();
507-
// Kludge: we can't set this.curNode directly, so we have to call
508-
// super.setCurNode(...) to do it for us - but that would call
509-
// this.drawer.draw(...), so prevent that by temporarily setting
510-
// this.drawer to null (which we also can't do directly!)
511-
const drawer = this.getDrawer();
512-
this.setDrawer(null as any); // Cast required since param is not nullable.
513541
super.setCurNode(newNode);
514-
this.setDrawer(drawer);
515-
// Draw this marker the way we want to.
516-
this.drawMarker(oldNode, newNode);
517542
// Try to scroll cursor into view.
518543
if (newNode?.getType() === ASTNode.types.BLOCK) {
519544
const block = newNode.getLocation() as Blockly.BlockSvg;
@@ -540,21 +565,6 @@ export class LineCursor extends Marker {
540565
}
541566
}
542567

543-
/**
544-
* Redraw the current marker.
545-
*
546-
* Overrides normal Marker drawing logic to use this.drawMarker()
547-
* instead of this.drawer.draw() directly.
548-
*
549-
* This hooks the method used by the renderer to draw the marker,
550-
* preventing the marker drawer from showing a marker if we don't
551-
* want it to.
552-
*/
553-
override draw() {
554-
const curNode = super.getCurNode();
555-
this.drawMarker(curNode, curNode);
556-
}
557-
558568
/**
559569
* Draw this cursor's marker.
560570
*
@@ -579,7 +589,11 @@ export class LineCursor extends Marker {
579589
* @param oldNode The previous node.
580590
* @param curNode The current node.
581591
*/
582-
private drawMarker(oldNode: ASTNode, curNode: ASTNode) {
592+
private drawMarker(
593+
oldNode: ASTNode | null,
594+
curNode: ASTNode | null,
595+
realDrawer: Blockly.blockRendering.MarkerSvg,
596+
) {
583597
// If old node was a block, unselect it or remove fake selection.
584598
if (oldNode?.getType() === ASTNode.types.BLOCK) {
585599
const block = oldNode.getLocation() as Blockly.BlockSvg;
@@ -592,13 +606,13 @@ export class LineCursor extends Marker {
592606

593607
// If curNode node is not block, just use the drawer.
594608
if (curNode?.getType() !== ASTNode.types.BLOCK) {
595-
this.getDrawer()?.draw(oldNode, curNode);
609+
realDrawer.draw(oldNode, curNode);
596610
return;
597611
}
598612

599613
// curNode is a block. Hide any visible marker SVG and instead
600614
// select the block or make it look selected.
601-
super.hide(); // Calls this.drawer?.hide().
615+
realDrawer.hide();
602616
const block = curNode.getLocation() as Blockly.BlockSvg;
603617
if (!block.isShadow()) {
604618
Blockly.common.setSelected(block);
@@ -608,7 +622,7 @@ export class LineCursor extends Marker {
608622

609623
// Call MarkerSvg.prototype.fireMarkerEvent like
610624
// MarkerSvg.prototype.draw would (even though it's private).
611-
(this.getDrawer() as any)?.fireMarkerEvent?.(oldNode, curNode);
625+
(realDrawer as any)?.fireMarkerEvent?.(oldNode, curNode);
612626
}
613627

614628
/**

src/navigation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,14 +362,14 @@ export class Navigation {
362362
if (
363363
!cursor ||
364364
!cursor.getCurNode() ||
365-
!cursor.getCurNode().getSourceBlock()
365+
!cursor.getCurNode()?.getSourceBlock()
366366
) {
367367
return;
368368
}
369369

370370
const curNode = cursor.getCurNode();
371-
const sourceBlock = curNode.getSourceBlock()!;
372-
if (sourceBlock.id === deletedBlockId || ids.includes(sourceBlock.id)) {
371+
const sourceBlock = curNode?.getSourceBlock()!;
372+
if (sourceBlock?.id === deletedBlockId || ids.includes(sourceBlock?.id)) {
373373
cursor.setCurNode(
374374
Blockly.ASTNode.createWorkspaceNode(
375375
workspace,

0 commit comments

Comments
 (0)