Skip to content

Commit 84de76f

Browse files
committed
release: Merge branch 'develop' into rc/v11.2.0
2 parents 63158b6 + 9a7de53 commit 84de76f

28 files changed

+3278
-1053
lines changed

.github/workflows/appengine_deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
path: _deploy/
4343

4444
- name: Deploy to App Engine
45-
uses: google-github-actions/deploy-appengine@v2.1.3
45+
uses: google-github-actions/deploy-appengine@v2.1.4
4646
# For parameters see:
4747
# https://github.com/google-github-actions/deploy-appengine#inputs
4848
with:

blocks/lists.ts

Lines changed: 61 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,24 @@ const LISTS_GETINDEX = {
412412
this.appendDummyInput()
413413
.appendField(modeMenu, 'MODE')
414414
.appendField('', 'SPACE');
415+
const menu = fieldRegistry.fromJson({
416+
type: 'field_dropdown',
417+
options: this.WHERE_OPTIONS,
418+
}) as FieldDropdown;
419+
menu.setValidator(
420+
/** @param value The input value. */
421+
function (this: FieldDropdown, value: string) {
422+
const oldValue: string | null = this.getValue();
423+
const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END';
424+
const newAt = value === 'FROM_START' || value === 'FROM_END';
425+
if (newAt !== oldAt) {
426+
const block = this.getSourceBlock() as GetIndexBlock;
427+
block.updateAt_(newAt);
428+
}
429+
return undefined;
430+
},
431+
);
432+
this.appendDummyInput().appendField(menu, 'WHERE');
415433
this.appendDummyInput('AT');
416434
if (Msg['LISTS_GET_INDEX_TAIL']) {
417435
this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_INDEX_TAIL']);
@@ -577,31 +595,6 @@ const LISTS_GETINDEX = {
577595
} else {
578596
this.appendDummyInput('AT');
579597
}
580-
const menu = fieldRegistry.fromJson({
581-
type: 'field_dropdown',
582-
options: this.WHERE_OPTIONS,
583-
}) as FieldDropdown;
584-
menu.setValidator(
585-
/**
586-
* @param value The input value.
587-
* @returns Null if the field has been replaced; otherwise undefined.
588-
*/
589-
function (this: FieldDropdown, value: string) {
590-
const newAt = value === 'FROM_START' || value === 'FROM_END';
591-
// The 'isAt' variable is available due to this function being a
592-
// closure.
593-
if (newAt !== isAt) {
594-
const block = this.getSourceBlock() as GetIndexBlock;
595-
block.updateAt_(newAt);
596-
// This menu has been destroyed and replaced. Update the
597-
// replacement.
598-
block.setFieldValue(value, 'WHERE');
599-
return null;
600-
}
601-
return undefined;
602-
},
603-
);
604-
this.getInput('AT')!.appendField(menu, 'WHERE');
605598
if (Msg['LISTS_GET_INDEX_TAIL']) {
606599
this.moveInputBefore('TAIL', null);
607600
}
@@ -644,6 +637,24 @@ const LISTS_SETINDEX = {
644637
this.appendDummyInput()
645638
.appendField(operationDropdown, 'MODE')
646639
.appendField('', 'SPACE');
640+
const menu = fieldRegistry.fromJson({
641+
type: 'field_dropdown',
642+
options: this.WHERE_OPTIONS,
643+
}) as FieldDropdown;
644+
menu.setValidator(
645+
/** @param value The input value. */
646+
function (this: FieldDropdown, value: string) {
647+
const oldValue: string | null = this.getValue();
648+
const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END';
649+
const newAt = value === 'FROM_START' || value === 'FROM_END';
650+
if (newAt !== oldAt) {
651+
const block = this.getSourceBlock() as SetIndexBlock;
652+
block.updateAt_(newAt);
653+
}
654+
return undefined;
655+
},
656+
);
657+
this.appendDummyInput().appendField(menu, 'WHERE');
647658
this.appendDummyInput('AT');
648659
this.appendValueInput('TO').appendField(Msg['LISTS_SET_INDEX_INPUT_TO']);
649660
this.setInputsInline(true);
@@ -756,36 +767,10 @@ const LISTS_SETINDEX = {
756767
} else {
757768
this.appendDummyInput('AT');
758769
}
759-
const menu = fieldRegistry.fromJson({
760-
type: 'field_dropdown',
761-
options: this.WHERE_OPTIONS,
762-
}) as FieldDropdown;
763-
menu.setValidator(
764-
/**
765-
* @param value The input value.
766-
* @returns Null if the field has been replaced; otherwise undefined.
767-
*/
768-
function (this: FieldDropdown, value: string) {
769-
const newAt = value === 'FROM_START' || value === 'FROM_END';
770-
// The 'isAt' variable is available due to this function being a
771-
// closure.
772-
if (newAt !== isAt) {
773-
const block = this.getSourceBlock() as SetIndexBlock;
774-
block.updateAt_(newAt);
775-
// This menu has been destroyed and replaced. Update the
776-
// replacement.
777-
block.setFieldValue(value, 'WHERE');
778-
return null;
779-
}
780-
return undefined;
781-
},
782-
);
783770
this.moveInputBefore('AT', 'TO');
784771
if (this.getInput('ORDINAL')) {
785772
this.moveInputBefore('ORDINAL', 'TO');
786773
}
787-
788-
this.getInput('AT')!.appendField(menu, 'WHERE');
789774
},
790775
};
791776
blocks['lists_setIndex'] = LISTS_SETINDEX;
@@ -818,7 +803,30 @@ const LISTS_GETSUBLIST = {
818803
this.appendValueInput('LIST')
819804
.setCheck('Array')
820805
.appendField(Msg['LISTS_GET_SUBLIST_INPUT_IN_LIST']);
806+
const createMenu = (n: 1 | 2): FieldDropdown => {
807+
const menu = fieldRegistry.fromJson({
808+
type: 'field_dropdown',
809+
options:
810+
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
811+
}) as FieldDropdown;
812+
menu.setValidator(
813+
/** @param value The input value. */
814+
function (this: FieldDropdown, value: string) {
815+
const oldValue: string | null = this.getValue();
816+
const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END';
817+
const newAt = value === 'FROM_START' || value === 'FROM_END';
818+
if (newAt !== oldAt) {
819+
const block = this.getSourceBlock() as GetSublistBlock;
820+
block.updateAt_(n, newAt);
821+
}
822+
return undefined;
823+
},
824+
);
825+
return menu;
826+
};
827+
this.appendDummyInput('WHERE1_INPUT').appendField(createMenu(1), 'WHERE1');
821828
this.appendDummyInput('AT1');
829+
this.appendDummyInput('WHERE2_INPUT').appendField(createMenu(2), 'WHERE2');
822830
this.appendDummyInput('AT2');
823831
if (Msg['LISTS_GET_SUBLIST_TAIL']) {
824832
this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_SUBLIST_TAIL']);
@@ -896,35 +904,10 @@ const LISTS_GETSUBLIST = {
896904
} else {
897905
this.appendDummyInput('AT' + n);
898906
}
899-
const menu = fieldRegistry.fromJson({
900-
type: 'field_dropdown',
901-
options:
902-
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
903-
}) as FieldDropdown;
904-
menu.setValidator(
905-
/**
906-
* @param value The input value.
907-
* @returns Null if the field has been replaced; otherwise undefined.
908-
*/
909-
function (this: FieldDropdown, value: string) {
910-
const newAt = value === 'FROM_START' || value === 'FROM_END';
911-
// The 'isAt' variable is available due to this function being a
912-
// closure.
913-
if (newAt !== isAt) {
914-
const block = this.getSourceBlock() as GetSublistBlock;
915-
block.updateAt_(n, newAt);
916-
// This menu has been destroyed and replaced.
917-
// Update the replacement.
918-
block.setFieldValue(value, 'WHERE' + n);
919-
return null;
920-
}
921-
},
922-
);
923-
this.getInput('AT' + n)!.appendField(menu, 'WHERE' + n);
924907
if (n === 1) {
925-
this.moveInputBefore('AT1', 'AT2');
908+
this.moveInputBefore('AT1', 'WHERE2_INPUT');
926909
if (this.getInput('ORDINAL1')) {
927-
this.moveInputBefore('ORDINAL1', 'AT2');
910+
this.moveInputBefore('ORDINAL1', 'WHERE2_INPUT');
928911
}
929912
}
930913
if (Msg['LISTS_GET_SUBLIST_TAIL']) {

blocks/text.ts

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,30 @@ const GET_SUBSTRING_BLOCK = {
216216
this.appendValueInput('STRING')
217217
.setCheck('String')
218218
.appendField(Msg['TEXT_GET_SUBSTRING_INPUT_IN_TEXT']);
219+
const createMenu = (n: 1 | 2): FieldDropdown => {
220+
const menu = fieldRegistry.fromJson({
221+
type: 'field_dropdown',
222+
options:
223+
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
224+
}) as FieldDropdown;
225+
menu.setValidator(
226+
/** @param value The input value. */
227+
function (this: FieldDropdown, value: any): null | undefined {
228+
const oldValue: string | null = this.getValue();
229+
const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END';
230+
const newAt = value === 'FROM_START' || value === 'FROM_END';
231+
if (newAt !== oldAt) {
232+
const block = this.getSourceBlock() as GetSubstringBlock;
233+
block.updateAt_(n, newAt);
234+
}
235+
return undefined;
236+
},
237+
);
238+
return menu;
239+
};
240+
this.appendDummyInput('WHERE1_INPUT').appendField(createMenu(1), 'WHERE1');
219241
this.appendDummyInput('AT1');
242+
this.appendDummyInput('WHERE2_INPUT').appendField(createMenu(2), 'WHERE2');
220243
this.appendDummyInput('AT2');
221244
if (Msg['TEXT_GET_SUBSTRING_TAIL']) {
222245
this.appendDummyInput('TAIL').appendField(Msg['TEXT_GET_SUBSTRING_TAIL']);
@@ -288,37 +311,10 @@ const GET_SUBSTRING_BLOCK = {
288311
this.removeInput('TAIL', true);
289312
this.appendDummyInput('TAIL').appendField(Msg['TEXT_GET_SUBSTRING_TAIL']);
290313
}
291-
const menu = fieldRegistry.fromJson({
292-
type: 'field_dropdown',
293-
options:
294-
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
295-
}) as FieldDropdown;
296-
menu.setValidator(
297-
/**
298-
* @param value The input value.
299-
* @returns Null if the field has been replaced; otherwise undefined.
300-
*/
301-
function (this: FieldDropdown, value: any): null | undefined {
302-
const newAt = value === 'FROM_START' || value === 'FROM_END';
303-
// The 'isAt' variable is available due to this function being a
304-
// closure.
305-
if (newAt !== isAt) {
306-
const block = this.getSourceBlock() as GetSubstringBlock;
307-
block.updateAt_(n, newAt);
308-
// This menu has been destroyed and replaced.
309-
// Update the replacement.
310-
block.setFieldValue(value, 'WHERE' + n);
311-
return null;
312-
}
313-
return undefined;
314-
},
315-
);
316-
317-
this.getInput('AT' + n)!.appendField(menu, 'WHERE' + n);
318314
if (n === 1) {
319-
this.moveInputBefore('AT1', 'AT2');
315+
this.moveInputBefore('AT1', 'WHERE2_INPUT');
320316
if (this.getInput('ORDINAL1')) {
321-
this.moveInputBefore('ORDINAL1', 'AT2');
317+
this.moveInputBefore('ORDINAL1', 'WHERE2_INPUT');
322318
}
323319
}
324320
},

core/block_svg.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,9 +1472,9 @@ export class BlockSvg
14721472
if (conn.isConnected() && neighbour.isConnected()) continue;
14731473

14741474
if (conn.isSuperior()) {
1475-
neighbour.bumpAwayFrom(conn);
1475+
neighbour.bumpAwayFrom(conn, /* initiatedByThis = */ false);
14761476
} else {
1477-
conn.bumpAwayFrom(neighbour);
1477+
conn.bumpAwayFrom(neighbour, /* initiatedByThis = */ true);
14781478
}
14791479
}
14801480
}

core/bubbles/textinput_bubble.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as touch from '../touch.js';
99
import {browserEvents} from '../utils.js';
1010
import {Coordinate} from '../utils/coordinate.js';
1111
import * as dom from '../utils/dom.js';
12+
import * as drag from '../utils/drag.js';
1213
import {Rect} from '../utils/rect.js';
1314
import {Size} from '../utils/size.js';
1415
import {Svg} from '../utils/svg.js';
@@ -62,6 +63,8 @@ export class TextInputBubble extends Bubble {
6263
20 + Bubble.DOUBLE_BORDER,
6364
);
6465

66+
private editable = true;
67+
6568
/**
6669
* @param workspace The workspace this bubble belongs to.
6770
* @param anchor The anchor location of the thing this bubble is attached to.
@@ -95,6 +98,21 @@ export class TextInputBubble extends Bubble {
9598
this.onTextChange();
9699
}
97100

101+
/** Sets whether or not the text in the bubble is editable. */
102+
setEditable(editable: boolean) {
103+
this.editable = editable;
104+
if (this.editable) {
105+
this.textArea.removeAttribute('readonly');
106+
} else {
107+
this.textArea.setAttribute('readonly', '');
108+
}
109+
}
110+
111+
/** Returns whether or not the text in the bubble is editable. */
112+
isEditable(): boolean {
113+
return this.editable;
114+
}
115+
98116
/** Adds a change listener to be notified when this bubble's text changes. */
99117
addTextChangeListener(listener: () => void) {
100118
this.textChangeListeners.push(listener);
@@ -224,7 +242,8 @@ export class TextInputBubble extends Bubble {
224242
return;
225243
}
226244

227-
this.workspace.startDrag(
245+
drag.start(
246+
this.workspace,
228247
e,
229248
new Coordinate(
230249
this.workspace.RTL ? -this.getSize().width : this.getSize().width,
@@ -264,7 +283,7 @@ export class TextInputBubble extends Bubble {
264283

265284
/** Handles pointer move events on the resize target. */
266285
private onResizePointerMove(e: PointerEvent) {
267-
const delta = this.workspace.moveDrag(e);
286+
const delta = drag.move(this.workspace, e);
268287
this.setSize(
269288
new Size(this.workspace.RTL ? -delta.x : delta.x, delta.y),
270289
false,

core/comments/comment_view.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as layers from '../layers.js';
1111
import * as touch from '../touch.js';
1212
import {Coordinate} from '../utils/coordinate.js';
1313
import * as dom from '../utils/dom.js';
14+
import * as drag from '../utils/drag.js';
1415
import {Size} from '../utils/size.js';
1516
import {Svg} from '../utils/svg.js';
1617
import {WorkspaceSvg} from '../workspace_svg.js';
@@ -524,8 +525,8 @@ export class CommentView implements IRenderedElement {
524525

525526
this.preResizeSize = this.getSize();
526527

527-
// TODO(#7926): Move this into a utils file.
528-
this.workspace.startDrag(
528+
drag.start(
529+
this.workspace,
529530
e,
530531
new Coordinate(
531532
this.workspace.RTL ? -this.getSize().width : this.getSize().width,
@@ -569,8 +570,7 @@ export class CommentView implements IRenderedElement {
569570

570571
/** Resizes the comment in response to a drag on the resize handle. */
571572
private onResizePointerMove(e: PointerEvent) {
572-
// TODO(#7926): Move this into a utils file.
573-
const size = this.workspace.moveDrag(e);
573+
const size = drag.move(this.workspace, e);
574574
this.setSizeWithoutFiringEvents(
575575
new Size(this.workspace.RTL ? -size.x : size.x, size.y),
576576
);
@@ -623,6 +623,7 @@ export class CommentView implements IRenderedElement {
623623
* event on the foldout icon.
624624
*/
625625
private onFoldoutDown(e: PointerEvent) {
626+
touch.clearTouchIdentifier();
626627
this.bringToFront();
627628
if (browserEvents.isRightButton(e)) {
628629
e.stopPropagation();
@@ -738,6 +739,7 @@ export class CommentView implements IRenderedElement {
738739
* delete icon.
739740
*/
740741
private onDeleteDown(e: PointerEvent) {
742+
touch.clearTouchIdentifier();
741743
if (browserEvents.isRightButton(e)) {
742744
e.stopPropagation();
743745
return;

core/connection.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,11 +214,11 @@ export class Connection implements IASTNodeLocationWithBlock {
214214
* Called when an attempted connection fails. NOP by default (i.e. for
215215
* headless workspaces).
216216
*
217-
* @param _otherConnection Connection that this connection failed to connect
218-
* to.
217+
* @param _superiorConnection Connection that this connection failed to connect
218+
* to. The provided connection should be the superior connection.
219219
* @internal
220220
*/
221-
onFailedConnect(_otherConnection: Connection) {}
221+
onFailedConnect(_superiorConnection: Connection) {}
222222
// NOP
223223

224224
/**

0 commit comments

Comments
 (0)