mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-18 00:12:09 +00:00
Harden speed dial helper inputs
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Speed Dial launcher page. Static-safe module: no DOM access at load time so a
|
||||
// node vm can exercise the pure helpers (filterActions / parseId / nextSel).
|
||||
// node vm can exercise the pure helpers (filterActions / actionLabel / nextSel).
|
||||
|
||||
// ---- state (populated by the C++ bridge via window.HandleStudio) ----
|
||||
var ACTIONS = []; // [{id,title,source,shortcut}], already frecency-sorted by C++
|
||||
@@ -36,13 +36,6 @@ function filterActions(actions, query) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// why: ids are "script:<key>::<cap>"; C++ keys off <key> for dont-ask scoping.
|
||||
function parseId(id) {
|
||||
var body = id.slice(id.indexOf(":") + 1); // strip "script:"
|
||||
var sep = body.indexOf("::");
|
||||
return { key: body.slice(0, sep), cap: body.slice(sep + 2) };
|
||||
}
|
||||
|
||||
function visibleFavourites(favourites, actions) {
|
||||
// why: a fav whose id has no live action (plugin unloaded/disabled) renders a dead
|
||||
// monogram tile whose click run()s to a silent no-op; drop it from the quick-bar.
|
||||
@@ -57,6 +50,10 @@ function resultCountText(total, shown, query) {
|
||||
return (query || "").trim() ? "Showing " + shown + " of " + total + " actions" : total + " actions";
|
||||
}
|
||||
|
||||
function shouldRenderActionList(query) {
|
||||
return !!(query || "").trim();
|
||||
}
|
||||
|
||||
// Resolve the selection cursor {zone,i} to the action id it points at: fav zone indexes the
|
||||
// visible favourites, list zone the filtered actions. Pure so runSelected() shares one lookup.
|
||||
function selectedActionId(sel, actions, favIds) {
|
||||
@@ -73,8 +70,8 @@ function prettySource(source) {
|
||||
return String(source || "").toLowerCase().replace(/\b\w/g, function (c) { return c.toUpperCase(); });
|
||||
}
|
||||
|
||||
// Accessible label "Title from Pretty Source", disambiguated with the plugin key when another action
|
||||
// shares the same title+source (case/separator-insensitive) - so two rows never read out identically.
|
||||
// Accessible label "Title from Pretty Source", disambiguated with the opaque action id when another
|
||||
// action shares the same title+source (case/separator-insensitive) - so two rows never read out identically.
|
||||
function actionLabel(action, actions) {
|
||||
var label = action.title + " from " + prettySource(action.source);
|
||||
if (actions && actions.length) {
|
||||
@@ -83,7 +80,7 @@ function actionLabel(action, actions) {
|
||||
return o.id !== action.id && foldLabel(o.title) + "|" + foldLabel(o.source) === mine;
|
||||
});
|
||||
if (clash)
|
||||
label += " (" + parseId(action.id).key + ")";
|
||||
label += " (" + action.id + ")";
|
||||
}
|
||||
return label;
|
||||
}
|
||||
@@ -327,7 +324,7 @@ function renderList() {
|
||||
listEl.innerHTML = "";
|
||||
// why: search-first - the list stays blank until the user types; only a
|
||||
// non-empty query with zero hits earns the "No actions match" message.
|
||||
if (!q) {
|
||||
if (!shouldRenderActionList(query)) {
|
||||
listEl.className = "dial-list empty";
|
||||
if (countEl) countEl.hidden = true;
|
||||
return;
|
||||
|
||||
29
resources/web/dialog/SpeedDial/speeddial.test.js
Normal file
29
resources/web/dialog/SpeedDial/speeddial.test.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// Regression tests for the DOM-free Speed Dial helpers.
|
||||
// Run: node resources/web/dialog/SpeedDial/speeddial.test.js
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const vm = require("vm");
|
||||
|
||||
const ctx = {};
|
||||
ctx.window = ctx;
|
||||
vm.createContext(ctx);
|
||||
vm.runInContext(fs.readFileSync(__dirname + "/../../js/fuzzy-search.js", "utf8"), ctx);
|
||||
vm.runInContext(fs.readFileSync(__dirname + "/speeddial.js", "utf8"), ctx);
|
||||
|
||||
assert.equal(typeof ctx.parseId, "undefined", "opaque action ids must never be parsed");
|
||||
|
||||
const duplicateActions = [
|
||||
{ id: "0123456789abcdef", title: "Repair", source: "Mesh Tools" },
|
||||
{ id: "fedcba9876543210", title: "Repair", source: "Mesh Tools" }
|
||||
];
|
||||
assert.equal(
|
||||
ctx.actionLabel(duplicateActions[0], duplicateActions),
|
||||
"Repair from Mesh Tools (0123456789abcdef)",
|
||||
"duplicate labels should use the opaque id without interpreting its contents"
|
||||
);
|
||||
|
||||
assert.equal(ctx.shouldRenderActionList(""), false, "an empty search keeps the action list hidden");
|
||||
assert.equal(ctx.shouldRenderActionList(" "), false, "whitespace-only search keeps the action list hidden");
|
||||
assert.equal(ctx.shouldRenderActionList("r"), true, "typing starts rendering matching actions");
|
||||
|
||||
console.log("ok");
|
||||
Reference in New Issue
Block a user