mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 11:21:30 +00:00
Back to spacebar for speed dial shortcut. Integrated inline process settings editing. Optimized saerch results
This commit is contained in:
@@ -65,32 +65,33 @@ assert.equal(ctx.filterTabs([{ id: "home", title: "" }], "home").length, 1,
|
||||
assert.equal(ctx.filterTabs([{ id: "prepare", title: " Prepare" }], "prepare").length, 1,
|
||||
"a leading-space tab title still matches a typed query");
|
||||
|
||||
// settingCode: a setting tile uses the leaf option-name initial (label's last " : "-segment),
|
||||
// escalated by category, then a stable ordinal - so settings aren't all a generic "S".
|
||||
const settings = [
|
||||
{ opt_key: "layer_height", label: "Quality : Layer Height", category: "Quality", type: 0 },
|
||||
{ opt_key: "smooth", label: "Quality : Smooth", category: "Quality", type: 0 }
|
||||
// The main phase is ONE pool: commands/plugins/settings are all actions, ranked by relevance
|
||||
// (no group headers, no actions-vs-settings discrimination).
|
||||
const pool = [
|
||||
{ id: "c1", title: "Layer Height", source: "Quality", group: "Quality : Layers", input: "" },
|
||||
{ id: "s1", title: "Go to layer (percent)", source: "OrcaSlicer", group: "Commands", input: "percent" },
|
||||
{ id: "c2", title: "Top Surface Layers", source: "Quality", group: "Quality : Layers", input: "" }
|
||||
];
|
||||
assert.equal(ctx.leafLabel({ label: "Quality : Layer Height", opt_key: "x" }), "Layer Height",
|
||||
"leafLabel takes the last segment of the label");
|
||||
assert.equal(ctx.settingCode(settings[0], settings), "L",
|
||||
"a unique leaf initial yields a single letter");
|
||||
// renderSettingRow iterates settingsResults, so the coded item is always a member of the list.
|
||||
const infillQ = { opt_key: "a", label: "Quality : Infill", category: "Quality", type: 0 };
|
||||
const infillS = { opt_key: "b", label: "Supports : Infill", category: "Supports", type: 0 };
|
||||
const sameLeaf = [infillQ, infillS];
|
||||
assert.equal(ctx.settingCode(infillQ, sameLeaf), "QI",
|
||||
"a colliding leaf initial escalates to category + initial");
|
||||
assert.equal(ctx.settingCode(infillS, sameLeaf), "SI",
|
||||
"a different category disambiguates the same leaf initial");
|
||||
const dupQ = [
|
||||
{ opt_key: "a", label: "Quality : Infill", category: "Quality", type: 0 },
|
||||
{ opt_key: "b", label: "Quality : Infill", category: "Quality", type: 0 }
|
||||
assert.deepEqual(ctx.searchActions(pool, ""), pool, "an empty query returns the pool unchanged");
|
||||
assert.equal(ctx.searchActions(pool, "zzz").length, 0, "a query with no match returns nothing");
|
||||
// "layer" matches multiple; the exact-titled action ranks above the loosely-matching command.
|
||||
assert.equal(ctx.searchActions(pool, "layer")[0].id, "c1",
|
||||
"a title-exact match ranks above a partial match");
|
||||
assert.equal(ctx.searchActions(pool, "layer").length >= 2, true,
|
||||
"both a setting and a command match the same query in the same list");
|
||||
assert.equal(ctx.searchActions(pool, "surface")[0].id, "c2",
|
||||
"a later-but-precise match still ranks by relevance, not by pool type");
|
||||
|
||||
// commandList (the main-phase list) delegates to the ranked search for a typed query and returns
|
||||
// the mixed recents (no discrimination) for an empty query.
|
||||
const mixed = [
|
||||
{ id: "cmd", title: "Slice", source: "OrcaSlicer", group: "Commands", input: "" },
|
||||
{ id: "set", title: "Sparse Infill Density", source: "Quality", group: "Quality", input: "" }
|
||||
];
|
||||
assert.equal(ctx.settingCode(dupQ[0], dupQ), "QI1",
|
||||
"both title and category collide -> stable ordinal by opt_key");
|
||||
assert.equal(ctx.settingCode(dupQ[1], dupQ), "QI2",
|
||||
"the ordinal advances by opt_key order");
|
||||
assert.equal(ctx.commandList(mixed, [], "sli")[0].id, "cmd",
|
||||
"a typed query keeps the relevance-ranked action list (best match first)");
|
||||
assert.deepEqual(ctx.commandList(mixed, mixed.slice(0, 1), "").map(function (a) { return a.id; }), ["cmd"],
|
||||
"an empty query shows the mixed recents list verbatim");
|
||||
|
||||
// selectedActionId: resolves the active list (recents for an empty query, filtered list otherwise).
|
||||
assert.equal(
|
||||
@@ -114,4 +115,117 @@ assert.equal(
|
||||
"favourites stay runnable with an empty query - the fav bar is always visible"
|
||||
);
|
||||
|
||||
// Fav quick-launch slots: digit 1..9 -> index 0..8, digit 0 -> index 9 (the 10th), else -1.
|
||||
assert.equal(ctx.favIndexForDigit("1"), 0, "digit 1 maps to the 1st favourite slot");
|
||||
assert.equal(ctx.favIndexForDigit("9"), 8, "digit 9 maps to the 9th favourite slot");
|
||||
assert.equal(ctx.favIndexForDigit("0"), 9, "digit 0 maps to the 10th (last) favourite slot");
|
||||
assert.equal(ctx.favIndexForDigit("x"), -1, "non-digit keys are not a slot");
|
||||
assert.equal(ctx.favIndexForDigit(""), -1, "an empty key is not a slot");
|
||||
|
||||
// Badge label per 0-based index: 0..8 -> "1".."9", index 9 -> "0", out of range -> null.
|
||||
assert.equal(ctx.favSlotForIndex(0), "1", "index 0 shows badge 1");
|
||||
assert.equal(ctx.favSlotForIndex(8), "9", "index 8 shows badge 9");
|
||||
assert.equal(ctx.favSlotForIndex(9), "0", "index 9 shows badge 0 (the 10th slot)");
|
||||
assert.equal(ctx.favSlotForIndex(10), null, "index 10 is beyond the cap");
|
||||
assert.equal(ctx.favSlotForIndex(-1), null, "negative index is not a slot");
|
||||
assert.equal(ctx.K_FAV_LIMIT, 10, "the slot count matches the quick-launch cap");
|
||||
|
||||
// nextSel: arrow-nav wrapping. Down wraps at the list bottom to the first row; Up wraps at the
|
||||
// list top to the last row ONLY when there's no fav bar above (else it goes to the fav bar).
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 2 }, "ArrowDown", 3, 0), { zone: "list", i: 0 },
|
||||
"ArrowDown at the last row wraps to the first row");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 1 }, "ArrowDown", 3, 0), { zone: "list", i: 2 },
|
||||
"ArrowDown in the middle advances by one");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 0 }, "ArrowUp", 3, 0), { zone: "list", i: 2 },
|
||||
"ArrowUp at the first row with no fav bar wraps to the last row");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 0 }, "ArrowUp", 3, 2), { zone: "fav", i: 0 },
|
||||
"ArrowUp at the first row with a fav bar goes to the fav bar (unchanged)");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 2 }, "ArrowUp", 3, 0), { zone: "list", i: 1 },
|
||||
"ArrowUp in the middle moves up by one");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "fav", i: 1 }, "ArrowDown", 3, 2), { zone: "list", i: 0 },
|
||||
"ArrowDown from the fav bar lands on the first list row");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 0 }, "ArrowDown", 1, 0), { zone: "list", i: 0 },
|
||||
"a single-row list never wraps off the end");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 0 }, "ArrowUp", 1, 0), { zone: "list", i: 0 },
|
||||
"ArrowUp on the only row stays put");
|
||||
|
||||
// Windowed list reveal: how many rows must be materialized to cover `fromIndex` plus `size` more,
|
||||
// clamped to the total. Drives the "render the next window on scroll / arrow-nav" append.
|
||||
assert.equal(ctx.revealTarget(100, 0, 100), 100, "covers the whole list when the window reaches the end");
|
||||
assert.equal(ctx.revealTarget(100, 60, 100), 100, "clamps to the total at the tail");
|
||||
assert.equal(ctx.revealTarget(30, 5, 100), 30, "a short list is fully covered");
|
||||
assert.equal(ctx.revealTarget(100, 5, 50), 55, "reveals exactly fromIndex + size");
|
||||
assert.equal(ctx.revealTarget(100, -5, 50), 50, "negative start is clamped to the first row");
|
||||
assert.equal(ctx.revealTarget(200, 50, 100), 150, "a scroll viewpoint reveals a window past the current rows");
|
||||
assert.equal(ctx.revealTarget(10, 0, 50), 10, "a list shorter than one window stays fully materialized");
|
||||
|
||||
// ---- inline setting editor helpers (settingControlRows / Value / CollectedValue) ----
|
||||
const scalarBoolDesc = {
|
||||
editable: true, control: "toggle", cardinality: "scalar", value: true,
|
||||
min: undefined, max: undefined, is_int: false, unit: ""
|
||||
};
|
||||
const scalarRows = ctx.settingControlRows(scalarBoolDesc);
|
||||
assert.equal(scalarRows.length, 1, "a scalar setting yields exactly one control row");
|
||||
assert.equal(scalarRows[0].kind, "toggle", "the control kind is carried through");
|
||||
assert.equal(scalarRows[0].index, 0, "the single row is indexed 0");
|
||||
assert.deepEqual(ctx.settingControlRows({ editable: false }), [], "a non-editable setting yields no control rows");
|
||||
|
||||
const vectorDesc = {
|
||||
editable: true, control: "number", cardinality: "vector", values: [0.2, 0.4],
|
||||
index_labels: ["1", "2"], min: 0, max: 1, is_int: false, unit: "mm"
|
||||
};
|
||||
const vectorRows = ctx.settingControlRows(vectorDesc);
|
||||
assert.equal(vectorRows.length, 2, "a vector setting yields one row per value");
|
||||
assert.deepEqual(vectorRows.map(function (r) { return r.value; }), [0.2, 0.4], "each row carries its current value");
|
||||
assert.deepEqual(vectorRows.map(function (r) { return r.label; }), ["1", "2"], "vector rows use the per-index labels");
|
||||
assert.equal(vectorRows[0].unit, "mm", "the unit is carried to each row");
|
||||
|
||||
// settingControlValue reads a control element (checked/value) as the shipped value.
|
||||
assert.equal(ctx.settingControlValue({ kind: "toggle" }, { checked: true }), true, "a toggle submits its checked state");
|
||||
assert.equal(ctx.settingControlValue({ kind: "toggle" }, { checked: false }), false, "an off toggle submits false");
|
||||
assert.equal(ctx.settingControlValue({ kind: "number", is_int: false, min: 0, max: 1 }, { value: "0.5" }), 0.5, "a float number parses");
|
||||
assert.equal(ctx.settingControlValue({ kind: "number", is_int: true, min: 0, max: 10 }, { value: "5" }), 5, "an int number parses");
|
||||
assert.equal(ctx.settingControlValue({ kind: "number", is_int: false, min: 0, max: 1 }, { value: "2" }), undefined, "an out-of-range number is rejected");
|
||||
assert.equal(ctx.settingControlValue({ kind: "number", is_int: false, min: 0, max: 1 }, { value: "" }), undefined, "an empty number is rejected");
|
||||
assert.equal(ctx.settingControlValue({ kind: "dropdown" }, { dataset: { value: "3" } }), 3, "an enum dropdown submits its stored int value");
|
||||
assert.equal(ctx.settingControlValue({ kind: "dropdown" }, { dataset: { value: "nope" } }), undefined, "a non-numeric dropdown value is rejected");
|
||||
assert.equal(ctx.settingControlValue({ kind: "dropdown" }, {}), undefined, "a dropdown with no value is rejected");
|
||||
// dropdownLabel maps the current int value to its human label, falling back to the value.
|
||||
const seamOptions = [
|
||||
{ value: 0, key: "nearest", label: "Nearest" },
|
||||
{ value: 1, key: "aligned", label: "Aligned" },
|
||||
{ value: 2, key: "random", label: "Random" }
|
||||
];
|
||||
assert.equal(ctx.dropdownLabel(seamOptions, 1), "Aligned", "the dropdown label for a known value is its label");
|
||||
assert.equal(ctx.dropdownLabel(seamOptions, 9), "9", "an unknown value falls back to the raw value");
|
||||
assert.equal(ctx.dropdownLabel([], 3), "3", "empty options fall back to the raw value");
|
||||
assert.equal(ctx.dropdownLabel(
|
||||
[{ value: 1, key: "aligned", label: "" }], 1), "aligned", "a blank label falls back to the key");
|
||||
// dropdownIcon maps the current int value to its pattern pictogram, empty when there is none.
|
||||
const patternOptions = [
|
||||
{ value: 0, key: "rectilinear", label: "Rectilinear" },
|
||||
{ value: 3, key: "gyroid", label: "Gyroid", icon: "data:image/svg+xml;base64,AAA" },
|
||||
{ value: 5, key: "grid", label: "Grid", icon: "data:image/svg+xml;base64,BBB" }
|
||||
];
|
||||
assert.equal(ctx.dropdownIcon(patternOptions, 3), "data:image/svg+xml;base64,AAA", "a known value returns its icon");
|
||||
assert.equal(ctx.dropdownIcon(patternOptions, 0), "", "a value with no icon returns empty");
|
||||
assert.equal(ctx.dropdownIcon(patternOptions, 9), "", "an unknown value returns empty");
|
||||
assert.equal(ctx.dropdownIcon([], 3), "", "empty options return empty");
|
||||
assert.equal(ctx.settingControlValue({ kind: "text" }, { value: "hello" }), "hello", "text submits as a string");
|
||||
// percent (coFloatOrPercent / coFloatsOrPercents): submits the raw typed string; empty is invalid.
|
||||
assert.equal(ctx.settingControlValue({ kind: "percent" }, { value: "10%" }), "10%", "a percent value submits as its string");
|
||||
assert.equal(ctx.settingControlValue({ kind: "percent" }, { value: "0.5" }), "0.5", "an mm value submits as a plain string");
|
||||
assert.equal(ctx.settingControlValue({ kind: "percent" }, { value: " " }), undefined, "a blank percent value is rejected");
|
||||
assert.equal(ctx.settingControlValue({ kind: "percent" }, {}), undefined, "a percent field with no value is rejected");
|
||||
// A percent scalar-to-scalar payload carries the raw string through unchanged.
|
||||
assert.equal(ctx.settingCollectedValue(
|
||||
{ editable: true, control: "percent", cardinality: "scalar", value: "10%" },
|
||||
[{ kind: "percent", value: "10%" }], [{ value: "10%" }]), "10%", "a percent scalar assembles its string");
|
||||
|
||||
// settingCollectedValue assembles the payload (scalar vs vector) for the set_setting message.
|
||||
assert.equal(ctx.settingCollectedValue({ editable: false }, [], []), undefined, "a non-editable setting yields no payload");
|
||||
assert.equal(ctx.settingCollectedValue(scalarBoolDesc, scalarRows, [{ checked: true }]), true, "a scalar assembles a single value");
|
||||
assert.deepEqual(ctx.settingCollectedValue(vectorDesc, vectorRows, [{ value: "0.3" }, { value: "0.7" }]), [0.3, 0.7], "a vector assembles an array");
|
||||
assert.equal(ctx.settingCollectedValue(vectorDesc, vectorRows, [{ value: "0.3" }, { value: "2" }]), undefined, "an invalid vector element aborts the whole payload");
|
||||
|
||||
console.log("ok");
|
||||
|
||||
Reference in New Issue
Block a user