Skip to content

Commit 2e75c9a

Browse files
committed
refactor: Allow for curNode being null.
1 parent e600f6c commit 2e75c9a

File tree

8 files changed

+73
-50
lines changed

8 files changed

+73
-50
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:
@@ -195,16 +196,16 @@ export class ActionMenu {
195196
connection,
196197
} as unknown as ContextMenuRegistry.Scope;
197198
for (const option of possibleOptions) {
198-
const precondition = option.preconditionFn(scope);
199+
const precondition = option.preconditionFn?.(scope);
199200
if (precondition === 'hidden') continue;
200201
const displayText =
201-
typeof option.displayText === 'function'
202+
(typeof option.displayText === 'function'
202203
? option.displayText(scope)
203-
: option.displayText;
204+
: option.displayText) ?? '';
204205
menuOptions.push({
205206
text: displayText,
206207
enabled: precondition === 'enabled',
207-
callback: option.callback,
208+
callback: option.callback!,
208209
scope,
209210
weight: option.weight,
210211
});

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
this.copyData = sourceBlock.toCopyData();
279283
this.copyWorkspace = sourceBlock.workspace;
280284
const copied = !!this.copyData;

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: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class EnterAction {
6464
return false;
6565
}
6666
curNode = flyoutCursor.getCurNode();
67-
nodeType = curNode.getType();
67+
nodeType = curNode?.getType();
6868

6969
switch (nodeType) {
7070
case ASTNode.types.STACK:
@@ -157,7 +157,8 @@ export class EnterAction {
157157
const button = this.navigation
158158
.getFlyoutCursor(workspace)!
159159
.getCurNode()
160-
.getLocation() as FlyoutButton;
160+
?.getLocation() as FlyoutButton | undefined;
161+
if (!button) return;
161162
const buttonCallback = (workspace as any).flyoutButtonCallbacks.get(
162163
(button as any).callbackKey,
163164
);
@@ -209,8 +210,8 @@ export class EnterAction {
209210
const curBlock = this.navigation
210211
.getFlyoutCursor(workspace)!
211212
.getCurNode()
212-
.getLocation() as BlockSvg;
213-
if (!curBlock.isEnabled()) {
213+
?.getLocation() as BlockSvg | undefined;
214+
if (!curBlock?.isEnabled()) {
214215
console.warn("Can't insert a disabled block.");
215216
return null;
216217
}

src/actions/ws_movement.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export class WorkspaceMovement {
108108
const cursor = workspace.getCursor();
109109
if (!cursor) return false;
110110
const curNode = cursor?.getCurNode();
111-
if (curNode.getType() !== ASTNode.types.WORKSPACE) return false;
111+
if (!curNode || curNode.getType() !== ASTNode.types.WORKSPACE) return false;
112112

113113
const wsCoord = curNode.getWsCoordinate();
114114
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
@@ -79,7 +79,8 @@ export class LineCursor extends Marker {
7979
const markerManager = this.workspace.getMarkerManager();
8080
this.oldCursor = markerManager.getCursor();
8181
markerManager.setCursor(this);
82-
if (this.oldCursor) this.setCurNode(this.oldCursor.getCurNode());
82+
const oldCursorNode = this.oldCursor?.getCurNode();
83+
if (oldCursorNode) this.setCurNode(oldCursorNode);
8384
this.workspace.addChangeListener(this.selectListener);
8485
this.installed = true;
8586
}
@@ -423,7 +424,7 @@ export class LineCursor extends Marker {
423424
preDelete(deletedBlock: Blockly.Block) {
424425
const curNode = this.getCurNode();
425426

426-
const nodes: Blockly.ASTNode[] = [curNode];
427+
const nodes: Blockly.ASTNode[] = curNode ? [curNode] : [];
427428
// The connection to which the deleted block is attached.
428429
const parentConnection =
429430
deletedBlock.previousConnection?.targetConnection ??
@@ -446,7 +447,7 @@ export class LineCursor extends Marker {
446447
}
447448
// A location on the workspace beneath the deleted block.
448449
// Move to the workspace.
449-
const curBlock = curNode.getSourceBlock();
450+
const curBlock = curNode?.getSourceBlock();
450451
if (curBlock) {
451452
const workspaceNode = Blockly.ASTNode.createWorkspaceNode(
452453
this.workspace,
@@ -474,6 +475,40 @@ export class LineCursor extends Marker {
474475
throw new Error('no valid nodes in this.potentialNodes');
475476
}
476477

478+
/**
479+
* Sets the object in charge of drawing the marker.
480+
*
481+
* We want to customize drawing, so rather than directly setting the given
482+
* object, we instead set a wrapper proxy object that passes through all
483+
* method calls and property accesses except for draw(), which it delegates
484+
* to the drawMarker() method in this class.
485+
*
486+
* @param drawer The object ~in charge of drawing the marker.
487+
*/
488+
override setDrawer(drawer: Blockly.blockRendering.MarkerSvg) {
489+
const altDraw = function (
490+
this: LineCursor,
491+
oldNode: ASTNode | null,
492+
curNode: ASTNode | null,
493+
) {
494+
// Pass the unproxied, raw drawer object so that drawMarker can call its
495+
// `draw()` method without triggering infinite recursion.
496+
this.drawMarker(oldNode, curNode, drawer);
497+
}.bind(this);
498+
499+
super.setDrawer(
500+
new Proxy(drawer, {
501+
get(target: typeof drawer, prop: keyof typeof drawer) {
502+
if (prop === 'draw') {
503+
return altDraw;
504+
}
505+
506+
return target[prop];
507+
},
508+
}),
509+
);
510+
}
511+
477512
/**
478513
* Set the location of the cursor and draw it.
479514
*
@@ -482,7 +517,7 @@ export class LineCursor extends Marker {
482517
*
483518
* @param newNode The new location of the cursor.
484519
*/
485-
override setCurNode(newNode: ASTNode, selectionInSync = false) {
520+
override setCurNode(newNode: ASTNode | null, selectionInSync = false) {
486521
if (newNode?.getLocation() === this.getCurNode()?.getLocation()) {
487522
return;
488523
}
@@ -505,18 +540,8 @@ export class LineCursor extends Marker {
505540
}
506541
}
507542

508-
const oldNode = super.getCurNode();
509-
// Kludge: we can't set this.curNode directly, so we have to call
510-
// super.setCurNode(...) to do it for us - but that would call
511-
// this.drawer.draw(...), so prevent that by temporarily setting
512-
// this.drawer to null (which we also can't do directly!)
513-
const drawer = this.getDrawer();
514-
this.setDrawer(null as any); // Cast required since param is not nullable.
515543
super.setCurNode(newNode);
516-
this.setDrawer(drawer);
517544

518-
// Draw this marker the way we want to.
519-
this.drawMarker(oldNode, newNode);
520545
// Try to scroll cursor into view.
521546
if (newNode?.getType() === ASTNode.types.BLOCK) {
522547
const block = newNode.getLocation() as Blockly.BlockSvg;
@@ -527,21 +552,6 @@ export class LineCursor extends Marker {
527552
}
528553
}
529554

530-
/**
531-
* Redraw the current marker.
532-
*
533-
* Overrides normal Marker drawing logic to use this.drawMarker()
534-
* instead of this.drawer.draw() directly.
535-
*
536-
* This hooks the method used by the renderer to draw the marker,
537-
* preventing the marker drawer from showing a marker if we don't
538-
* want it to.
539-
*/
540-
override draw() {
541-
const curNode = super.getCurNode();
542-
this.drawMarker(curNode, curNode);
543-
}
544-
545555
/**
546556
* Draw this cursor's marker.
547557
*
@@ -566,7 +576,11 @@ export class LineCursor extends Marker {
566576
* @param oldNode The previous node.
567577
* @param curNode The current node.
568578
*/
569-
private drawMarker(oldNode: ASTNode, curNode: ASTNode) {
579+
private drawMarker(
580+
oldNode: ASTNode | null,
581+
curNode: ASTNode | null,
582+
realDrawer: Blockly.blockRendering.MarkerSvg,
583+
) {
570584
// If old node was a block, unselect it or remove fake selection.
571585
if (oldNode?.getType() === ASTNode.types.BLOCK) {
572586
const block = oldNode.getLocation() as Blockly.BlockSvg;
@@ -587,12 +601,12 @@ export class LineCursor extends Marker {
587601

588602
// If drawing can't be handled locally, just use the drawer.
589603
if (curNodeType !== ASTNode.types.BLOCK && !isZelosInputConnection) {
590-
this.getDrawer()?.draw(oldNode, curNode);
604+
realDrawer.draw(oldNode, curNode);
591605
return;
592606
}
593607

594608
// Hide any visible marker SVG and instead do some manual rendering.
595-
super.hide(); // Calls this.drawer?.hide().
609+
realDrawer.hide();
596610

597611
if (isZelosInputConnection) {
598612
this.showAtInput(curNode);
@@ -607,7 +621,7 @@ export class LineCursor extends Marker {
607621

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

613627
/**

src/navigation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,14 +338,14 @@ export class Navigation {
338338
if (
339339
!cursor ||
340340
!cursor.getCurNode() ||
341-
!cursor.getCurNode().getSourceBlock()
341+
!cursor.getCurNode()?.getSourceBlock()
342342
) {
343343
return;
344344
}
345345

346346
const curNode = cursor.getCurNode();
347-
const sourceBlock = curNode.getSourceBlock()!;
348-
if (sourceBlock.id === deletedBlockId || ids.includes(sourceBlock.id)) {
347+
const sourceBlock = curNode?.getSourceBlock()!;
348+
if (sourceBlock?.id === deletedBlockId || ids.includes(sourceBlock?.id)) {
349349
cursor.setCurNode(
350350
Blockly.ASTNode.createWorkspaceNode(
351351
workspace,

0 commit comments

Comments
 (0)