Skip to content

Commit bf7bfc9

Browse files
feat: Toolbox-Search add support for preset blocks (#2594)
* feat: Toolbox-Search add support for preset blocks * Run formater
1 parent d27842e commit bf7bfc9

File tree

3 files changed

+94
-38
lines changed

3 files changed

+94
-38
lines changed

plugins/toolbox-search/src/block_searcher.ts

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import * as Blockly from 'blockly/core';
1010
* A class that provides methods for indexing and searching blocks.
1111
*/
1212
export class BlockSearcher {
13-
private blockCreationWorkspace = new Blockly.Workspace();
14-
private trigramsToBlocks = new Map<string, Set<string>>();
13+
private trigramsToBlocks = new Map<
14+
string,
15+
Set<Blockly.utils.toolbox.BlockInfo>
16+
>();
1517

1618
/**
1719
* Populates the cached map of trigrams to the blocks they correspond to.
@@ -21,17 +23,19 @@ export class BlockSearcher {
2123
* indexes their types and human-readable text, and cleans up after
2224
* itself.
2325
*
24-
* @param blockTypes A list of block types to index.
26+
* @param blockInfos A list of blocks to index.
2527
*/
26-
indexBlocks(blockTypes: string[]) {
28+
indexBlocks(blockInfos: Blockly.utils.toolbox.BlockInfo[]) {
2729
const blockCreationWorkspace = new Blockly.Workspace();
28-
blockTypes.forEach((blockType) => {
29-
const block = blockCreationWorkspace.newBlock(blockType);
30-
this.indexBlockText(blockType.replaceAll('_', ' '), blockType);
30+
blockInfos.forEach((blockInfo) => {
31+
const type = blockInfo.type;
32+
if (!type || type === '') return;
33+
const block = blockCreationWorkspace.newBlock(type);
34+
this.indexBlockText(type.replaceAll('_', ' '), blockInfo);
3135
block.inputList.forEach((input) => {
3236
input.fieldRow.forEach((field) => {
33-
this.indexDropdownOption(field, blockType);
34-
this.indexBlockText(field.getText(), blockType);
37+
this.indexDropdownOption(field, blockInfo);
38+
this.indexBlockText(field.getText(), blockInfo);
3539
});
3640
});
3741
});
@@ -41,15 +45,18 @@ export class BlockSearcher {
4145
* Check if the field is a dropdown, and index every text in the option
4246
*
4347
* @param field We need to check the type of field
44-
* @param blockType The block type to associate the trigrams with.
48+
* @param block The block to associate the trigrams with.
4549
*/
46-
private indexDropdownOption(field: Blockly.Field, blockType: string) {
50+
private indexDropdownOption(
51+
field: Blockly.Field,
52+
block: Blockly.utils.toolbox.BlockInfo,
53+
) {
4754
if (field instanceof Blockly.FieldDropdown) {
4855
field.getOptions(true).forEach((option) => {
4956
if (typeof option[0] === 'string') {
50-
this.indexBlockText(option[0], blockType);
57+
this.indexBlockText(option[0], block);
5158
} else if ('alt' in option[0]) {
52-
this.indexBlockText(option[0].alt, blockType);
59+
this.indexBlockText(option[0].alt, block);
5360
}
5461
});
5562
}
@@ -59,13 +66,16 @@ export class BlockSearcher {
5966
* Filters the available blocks based on the current query string.
6067
*
6168
* @param query The text to use to match blocks against.
62-
* @returns A list of block types matching the query.
69+
* @returns A list of blocks matching the query.
6370
*/
64-
blockTypesMatching(query: string): string[] {
71+
blockTypesMatching(query: string): Blockly.utils.toolbox.BlockInfo[] {
6572
return [
6673
...this.generateTrigrams(query)
6774
.map((trigram) => {
68-
return this.trigramsToBlocks.get(trigram) ?? new Set<string>();
75+
return (
76+
this.trigramsToBlocks.get(trigram) ??
77+
new Set<Blockly.utils.toolbox.BlockInfo>()
78+
);
6979
})
7080
.reduce((matches, current) => {
7181
return this.getIntersection(matches, current);
@@ -76,15 +86,17 @@ export class BlockSearcher {
7686

7787
/**
7888
* Generates trigrams for the given text and associates them with the given
79-
* block type.
89+
* block.
8090
*
8191
* @param text The text to generate trigrams of.
82-
* @param blockType The block type to associate the trigrams with.
92+
* @param block The block to associate the trigrams with.
8393
*/
84-
private indexBlockText(text: string, blockType: string) {
94+
private indexBlockText(text: string, block: Blockly.utils.toolbox.BlockInfo) {
8595
this.generateTrigrams(text).forEach((trigram) => {
86-
const blockSet = this.trigramsToBlocks.get(trigram) ?? new Set<string>();
87-
blockSet.add(blockType);
96+
const blockSet =
97+
this.trigramsToBlocks.get(trigram) ??
98+
new Set<Blockly.utils.toolbox.BlockInfo>();
99+
blockSet.add(block);
88100
this.trigramsToBlocks.set(trigram, blockSet);
89101
});
90102
}
@@ -115,7 +127,10 @@ export class BlockSearcher {
115127
* @param b The second set.
116128
* @returns The intersection of the two sets.
117129
*/
118-
private getIntersection(a: Set<string>, b: Set<string>): Set<string> {
130+
private getIntersection(
131+
a: Set<Blockly.utils.toolbox.BlockInfo>,
132+
b: Set<Blockly.utils.toolbox.BlockInfo>,
133+
): Set<Blockly.utils.toolbox.BlockInfo> {
119134
return new Set([...a].filter((value) => b.has(value)));
120135
}
121136
}

plugins/toolbox-search/src/toolbox_search.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,15 @@ export class ToolboxSearchCategory extends Blockly.ToolboxCategory {
113113
*/
114114
private getAvailableBlocks(
115115
schema: Blockly.utils.toolbox.ToolboxItemInfo,
116-
allBlocks: Set<string>,
116+
allBlocks: Set<Blockly.utils.toolbox.BlockInfo>,
117117
) {
118118
if ('contents' in schema) {
119119
schema.contents.forEach((contents) => {
120120
this.getAvailableBlocks(contents, allBlocks);
121121
});
122122
} else if (schema.kind.toLowerCase() === 'block') {
123123
if ('type' in schema && schema.type) {
124-
allBlocks.add(schema.type);
124+
allBlocks.add(schema);
125125
}
126126
}
127127
}
@@ -130,7 +130,7 @@ export class ToolboxSearchCategory extends Blockly.ToolboxCategory {
130130
* Builds the BlockSearcher index based on the available blocks.
131131
*/
132132
private initBlockSearcher() {
133-
const availableBlocks = new Set<string>();
133+
const availableBlocks = new Set<Blockly.utils.toolbox.BlockInfo>();
134134
this.workspace_.options.languageTree?.contents?.forEach((item) =>
135135
this.getAvailableBlocks(item, availableBlocks),
136136
);
@@ -173,12 +173,7 @@ export class ToolboxSearchCategory extends Blockly.ToolboxCategory {
173173
const query = this.searchField?.value || '';
174174

175175
this.flyoutItems_ = query
176-
? this.blockSearcher.blockTypesMatching(query).map((blockType) => {
177-
return {
178-
kind: 'block',
179-
type: blockType,
180-
};
181-
})
176+
? this.blockSearcher.blockTypesMatching(query)
182177
: [];
183178

184179
if (!this.flyoutItems_.length) {

plugins/toolbox-search/test/tests.mocha.js

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,80 @@ suite('Toolbox search', () => {
1717
suite('BlockSearcher', () => {
1818
test('indexes the default value of dropdown fields', () => {
1919
const searcher = new BlockSearcher();
20+
const blocks = [
21+
{
22+
kind: 'block',
23+
type: 'lists_sort',
24+
},
25+
{
26+
kind: 'block',
27+
type: 'lists_split',
28+
},
29+
];
2030
// Text on these:
2131
// lists_sort: sort <numeric> <ascending>
2232
// lists_split: make <list from text> with delimiter ,
23-
searcher.indexBlocks(['lists_sort', 'lists_split']);
33+
searcher.indexBlocks(blocks);
2434

2535
const numericMatches = searcher.blockTypesMatching('numeric');
26-
assert.sameMembers(['lists_sort'], numericMatches);
36+
assert.sameMembers(numericMatches, [blocks[0]]);
2737

2838
const listFromTextMatches = searcher.blockTypesMatching('list from text');
29-
assert.sameMembers(['lists_split'], listFromTextMatches);
39+
assert.sameMembers(listFromTextMatches, [blocks[1]]);
3040
});
3141

3242
test('is not case-sensitive', () => {
3343
const searcher = new BlockSearcher();
34-
searcher.indexBlocks(['lists_create_with']);
44+
const listCreateWithBlock = {
45+
kind: 'block',
46+
type: 'lists_create_with',
47+
};
48+
searcher.indexBlocks([listCreateWithBlock]);
3549

3650
const lowercaseMatches = searcher.blockTypesMatching('create list');
37-
assert.sameMembers(['lists_create_with'], lowercaseMatches);
51+
assert.sameMembers(lowercaseMatches, [listCreateWithBlock]);
3852

3953
const uppercaseMatches = searcher.blockTypesMatching('CREATE LIST');
40-
assert.sameMembers(['lists_create_with'], uppercaseMatches);
54+
assert.sameMembers(uppercaseMatches, [listCreateWithBlock]);
4155

4256
const ransomNoteMatches = searcher.blockTypesMatching('cReATe LiST');
43-
assert.sameMembers(['lists_create_with'], ransomNoteMatches);
57+
assert.sameMembers(ransomNoteMatches, [listCreateWithBlock]);
4458
});
4559

4660
test('returns an empty list when no matches are found', () => {
4761
const searcher = new BlockSearcher();
4862
assert.isEmpty(searcher.blockTypesMatching('abc123'));
4963
});
64+
65+
test('returns preset blocks', () => {
66+
const searcher = new BlockSearcher();
67+
const blocks = [
68+
{
69+
kind: 'block',
70+
type: 'text_replace',
71+
inputs: {
72+
FROM: {
73+
shadow: {
74+
type: 'text',
75+
},
76+
},
77+
TO: {
78+
shadow: {
79+
type: 'text',
80+
},
81+
},
82+
TEXT: {
83+
shadow: {
84+
type: 'text',
85+
},
86+
},
87+
},
88+
},
89+
];
90+
91+
searcher.indexBlocks(blocks);
92+
93+
const matches = searcher.blockTypesMatching('replace');
94+
assert.sameMembers(matches, [blocks[0]]);
95+
});
5096
});

0 commit comments

Comments
 (0)