Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 172 additions & 5 deletions main/blocklyinit.js
Original file line number Diff line number Diff line change
Expand Up @@ -1630,7 +1630,7 @@ export function overrideSearchPlugin(workspace) {
}

const resolvedMessage =
Blockly.utils.replaceMessageReferences(message0);
Blockly.utils.parsing.replaceMessageReferences(message0);
return translate(resolvedMessage);
}

Expand Down Expand Up @@ -1869,6 +1869,120 @@ export function overrideSearchPlugin(workspace) {
return indexedBlocks;
}

function buildCategoryIndex() {
const categories = [];
let idCounter = 0;

function resolveDisplayName(name) {
if (!name) return "";
return (
Blockly.utils.parsing.replaceMessageReferences(
name,
) || name
);
}

function collectCategories(schema, parentPath, parentDisplayName) {
if (
!schema ||
schema.kind?.toLowerCase() !== "category"
)
return;
const rawName = schema.name;
if (!rawName) return;

const displayName = resolveDisplayName(rawName);
const categoryPath = [...parentPath, rawName];
const id = `cat${idCounter++}`;

const searchText = parentDisplayName
? `${displayName} ${parentDisplayName}`
: displayName;

categories.push({
kind: "category",
id,
name: rawName,
displayName,
parentDisplayName,
categoryPath,
icon: schema.icon,
categorystyle: schema.categorystyle,
text: searchText,
});

schema.contents?.forEach((item) => {
if (item.kind?.toLowerCase() === "category") {
collectCategories(
item,
categoryPath,
displayName,
);
}
});
}

workspace.options.languageTree?.contents?.forEach((item) => {
if (item.kind?.toLowerCase() === "category") {
collectCategories(item, [], null);
}
});

return categories;
}

function navigateToCategory(categoryPath) {
const toolbox = workspace.getToolbox?.();
if (!toolbox) return;

let currentItems = toolbox.getToolboxItems?.() || [];
let targetItem = null;

for (let i = 0; i < categoryPath.length; i++) {
const rawName = categoryPath[i];
const found = currentItems.find(
(item) =>
item.getToolboxItemDef?.()?.name ===
rawName ||
item.toolboxItemDef_?.name === rawName,
);

if (!found) return;

if (i < categoryPath.length - 1) {
if (typeof found.setExpanded === "function") {
found.setExpanded(true);
}
currentItems =
found.getChildToolboxItems?.() || [];
} else {
targetItem = found;
}
}

if (!targetItem) return;

if (
typeof targetItem.ensurePointerFocusedSelection_ ===
"function"
) {
targetItem.ensurePointerFocusedSelection_();
} else {
toolbox.setSelectedItem?.(targetItem);
if (typeof targetItem.setSelected === "function") {
targetItem.setSelected(true);
}
if (typeof targetItem.setExpanded === "function") {
targetItem.setExpanded(true);
}
const flyout = toolbox.getFlyout?.();
if (flyout) {
const contents = targetItem.getContents?.();
if (contents) flyout.show?.(contents);
}
}
}

const searchToolboxItem = workspace
.getToolbox()
?.getToolboxItems?.()
Expand Down Expand Up @@ -2012,7 +2126,14 @@ export function overrideSearchPlugin(workspace) {
return false;
});

this.showMatchingBlocks(matches);
if (!Array.isArray(workspace.flockCategoryIndex)) {
workspace.flockCategoryIndex = buildCategoryIndex();
}
const categoryMatches = workspace.flockCategoryIndex.filter(
(cat) => cat.text.toLowerCase().includes(query),
);

this.showMatchingBlocks(matches, categoryMatches);
};

function createXmlFromJson(
Expand Down Expand Up @@ -2093,7 +2214,10 @@ export function overrideSearchPlugin(workspace) {
return blockXml;
}

SearchCategory.prototype.showMatchingBlocks = function (matches) {
SearchCategory.prototype.showMatchingBlocks = function (
matches,
categoryMatches = [],
) {
if (!isSearchCategorySelected(this)) {
return;
}
Expand All @@ -2106,10 +2230,53 @@ export function overrideSearchPlugin(workspace) {
flyout.hide();
flyout.show([]);

const xmlList = matches.map((match) =>
const xmlItems = [];

if (categoryMatches.length > 0) {
const label = document.createElement("label");
label.setAttribute("text", "Categories");
label.setAttribute(
"web-class",
"flockSearchCategoryHeader",
);
xmlItems.push(label);

categoryMatches.forEach((cat) => {
const key = `flockCatNav_${cat.id}`;
this.workspace_.registerButtonCallback(
key,
() => {
navigateToCategory(
cat.categoryPath,
);
},
);
const btn = document.createElement("button");
const btnText = cat.parentDisplayName
? `${cat.parentDisplayName} \u203a ${cat.displayName}`
: cat.displayName;
btn.setAttribute("text", btnText);
btn.setAttribute("callbackKey", key);
btn.setAttribute(
"web-class",
`flockSearchCategoryButton${cat.categorystyle ? ` flock-cat-${cat.categorystyle}` : ""}`,
);
xmlItems.push(btn);
});

if (matches.length > 0) {
const sep = document.createElement("sep");
sep.setAttribute("gap", "24");
xmlItems.push(sep);
}
}

const blockXmlList = matches.map((match) =>
createXmlFromJson(match.full),
);
flyout.show(xmlList);
xmlItems.push(...blockXmlList);

flyout.show(xmlItems);
};

const toolboxDef = workspace.options.languageTree;
Expand Down
57 changes: 57 additions & 0 deletions style/blockly.css
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,63 @@ textarea.blocklyCommentText.blocklyTextarea.blocklyText {
box-sizing: border-box !important;
}

/* ── Toolbox search: category results ─────────────────────────────────── */

/* "Categories" header label */
.blocklyFlyoutLabelText.flockSearchCategoryHeader {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
fill: #888;
}

/* Category button background */
.blocklyFlyoutButton.flockSearchCategoryButton rect.blocklyFlyoutButtonBackground {
rx: 6;
ry: 6;
fill: #5b67a5;
opacity: 0.92;
}
.blocklyFlyoutButton.flockSearchCategoryButton rect.blocklyFlyoutButtonShadow {
rx: 6;
ry: 6;
fill: rgba(0, 0, 0, 0.15);
}
.blocklyFlyoutButton.flockSearchCategoryButton .blocklyFlyoutButtonLabel {
fill: #fff;
font-weight: 600;
font-size: 13px;
}
.blocklyFlyoutButton.flockSearchCategoryButton:hover rect.blocklyFlyoutButtonBackground {
opacity: 1;
fill: #4a57a0;
}

/* Per-categorystyle colours for the button background */
.blocklyFlyoutButton.flock-cat-scene_category rect.blocklyFlyoutButtonBackground { fill: #5c8a40; }
.blocklyFlyoutButton.flock-cat-events_category rect.blocklyFlyoutButtonBackground { fill: #a04040; }
.blocklyFlyoutButton.flock-cat-transform_category rect.blocklyFlyoutButtonBackground { fill: #8a7e40; }
.blocklyFlyoutButton.flock-cat-animate_category rect.blocklyFlyoutButtonBackground { fill: #7a7040; }
.blocklyFlyoutButton.flock-cat-materials_category rect.blocklyFlyoutButtonBackground { fill: #6b3b8a; }
.blocklyFlyoutButton.flock-cat-sound_category rect.blocklyFlyoutButtonBackground { fill: #a06040; }
.blocklyFlyoutButton.flock-cat-sensing_category rect.blocklyFlyoutButtonBackground { fill: #3b7a8a; }
.blocklyFlyoutButton.flock-cat-control_category rect.blocklyFlyoutButtonBackground { fill: #508a50; }
.blocklyFlyoutButton.flock-cat-logic_category rect.blocklyFlyoutButtonBackground { fill: #4a5ab0; }
.blocklyFlyoutButton.flock-cat-variables_category rect.blocklyFlyoutButtonBackground { fill: #963065; }
.blocklyFlyoutButton.flock-cat-text_category rect.blocklyFlyoutButtonBackground { fill: #5545be; }
.blocklyFlyoutButton.flock-cat-math_category rect.blocklyFlyoutButtonBackground { fill: #3a4ab0; }
.blocklyFlyoutButton.flock-cat-procedures_category rect.blocklyFlyoutButtonBackground { fill: #7a3a8a; }
.blocklyFlyoutButton.flock-cat-snippets_category rect.blocklyFlyoutButtonBackground { fill: #387091; }

/* Dark theme adjustments */
[data-theme="dark"] .blocklyFlyoutLabelText.flockSearchCategoryHeader {
fill: #aaa;
}
[data-theme="dark"] .blocklyFlyoutButton.flockSearchCategoryButton rect.blocklyFlyoutButtonBackground {
opacity: 0.85;
}