Skip to content
Merged
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
1 change: 1 addition & 0 deletions multiace/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"ws_error": "WS-Fehler"
},
"tabs": {
"print_hub": "Print Hub",
"dashboard": "Übersicht",
"dry": "Trocknen",
"config": "Konfiguration",
Expand Down
1 change: 1 addition & 0 deletions multiace/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"ws_error": "WS error"
},
"tabs": {
"print_hub": "Print Hub",
"dashboard": "Dashboard",
"dry": "Dry",
"config": "Config",
Expand Down
1 change: 1 addition & 0 deletions multiace/i18n/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"ws_error": "WebSocket 错误"
},
"tabs": {
"print_hub": "打印中心",
"dashboard": "控制台",
"dry": "烘干",
"config": "配置",
Expand Down
51 changes: 50 additions & 1 deletion multiace/web/frontend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,55 @@ createApp({
}
return items;
});
// Groups of tabs. An entry with `group: "<key>"` is a child of that group:
// the top bar shows ONE entry for the group (placed before the entry named
// in `before`) and a second row lists the children while one of them is
// open. A group with no children is not shown, so adding a feature's tab
// is the only thing that makes its group appear.
const NAV_GROUPS = [
{key: "printhub", labelKey: "ui.tabs.print_hub", before: "spools"},
];
const groupLast = reactive({});
for (const g of NAV_GROUPS) {
try { groupLast[g.key] = localStorage.getItem("multiace.tabGroup." + g.key) || ""; }
catch (e) { groupLast[g.key] = ""; }
}
const groupOf = (key) => {
const it = navItems.value.find(i => i.key === key);
return it && it.group ? it.group : null;
};
const activeGroup = computed(() => groupOf(tab.value));
// Remember the last child opened in each group, per browser.
watch(tab, (v) => {
const g = groupOf(v);
if (!g) return;
groupLast[g] = v;
try { localStorage.setItem("multiace.tabGroup." + g, v); } catch (e) {}
});
const topItems = computed(() => {
const out = [];
const addGroup = (g) => {
const children = navItems.value.filter(i => i.group === g.key);
if (children.length) {
out.push({key: "group:" + g.key, group: g.key, children, label: t(g.labelKey)});
}
};
for (const it of navItems.value) {
if (it.group) continue;
for (const g of NAV_GROUPS) if (g.before === it.key) addGroup(g);
out.push(it);
}
return out;
});
const subItems = computed(() =>
activeGroup.value ? navItems.value.filter(i => i.group === activeGroup.value) : []);
const topActive = (it) => it.group ? activeGroup.value === it.group : tab.value === it.key;
// A group opens the child that was open last, else its first child.
function openNav(it) {
if (!it.group) { tab.value = it.key; return; }
const last = groupLast[it.group];
tab.value = (it.children.find(c => c.key === last) || it.children[0]).key;
}
function pluginIframeSrc(p) {
const u = (p && p.ui_url) || "/";
return `/plugin/${p.name}${u.startsWith("/") ? u : "/" + u}`;
Expand Down Expand Up @@ -7435,7 +7484,7 @@ createApp({
headPlanMapping, multiTopology, slotHead, pinLabel,
updateState, updateCheck, updateApply,
debugState, debugEnable, debugDisable,
navItems,
navItems, topItems, subItems, topActive, openNav,
plugins, refreshPlugins, pluginIframeSrc,
notifications, notifWarnOnly, notifTime, dismissNotification, dismissAllNotifications,
confirmDialog, okConfirm, altConfirm, cancelConfirm, confirmInputError,
Expand Down
11 changes: 9 additions & 2 deletions multiace/web/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ <h1>m<span class="hl">U</span>lt<span class="hl">1</span>ACE
:title="t('ui.common.firmware') + ' ' + printerFw">· {{ printerName }}</small>
</h1>
<nav>
<button v-for="it in navItems" :key="it.key"
:class="{active: tab===it.key}" @click="tab=it.key">{{ it.label }}</button>
<button v-for="it in topItems" :key="it.key"
:class="{active: topActive(it)}" @click="openNav(it)">{{ it.label }}</button>
</nav>
<input ref="uploadInput" type="file" accept=".gcode,.gco,.g"
class="upload-input" @change="onUploadGcode($event.target.files)">
Expand All @@ -333,6 +333,13 @@ <h1>m<span class="hl">U</span>lt<span class="hl">1</span>ACE
</button>
<span class="conn" :class="connClass">{{ connText }}</span>
</header>
<!-- Second row: the children of the open group (see NAV_GROUPS in app.js). -->
<div v-if="subItems.length && !panelMode" class="subnav">
<button v-for="it in subItems" :key="it.key"
:class="{active: tab===it.key}"
:aria-current="tab===it.key ? 'page' : null"
@click="tab=it.key">{{ it.label }}</button>
</div>
<div v-if="rebootNeeded && !panelMode" class="reboot-banner">{{ t('ui.common.please_restart') }}</div>
<div v-if="state.preflight_inbox?.pending && !panelMode" class="inbox-banner">
<span>{{ t('ui.inbox.banner', {name: state.preflight_inbox?.name}) }}</span>
Expand Down
16 changes: 16 additions & 0 deletions multiace/web/frontend/style-new.css
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ html[data-theme="new"] nav button.active {
background: transparent; color: var(--nt-fg-strong); font-weight: 600;
box-shadow: inset 0 -2px 0 var(--accent);
}
/* Second row: the children of the open tab group. Same underline marker as
the top bar, one step smaller. */
html[data-theme="new"] .subnav {
gap: 0; padding: 0 20px; overflow-x: auto;
background: var(--bg); border-bottom: 1px solid var(--border);
}
html[data-theme="new"] .subnav button {
background: transparent; border: 0; border-radius: 0; padding: 10px 16px;
font-size: 13px; font-weight: 500; color: var(--muted); white-space: nowrap;
}
html[data-theme="new"] .subnav button:hover { color: var(--fg); }
html[data-theme="new"] .subnav button.active {
color: var(--nt-fg-strong); font-weight: 600; box-shadow: inset 0 -2px 0 var(--accent);
}
html[data-theme="new"] header .upload-btn {
order: 5; margin-left: 0; border: 0; border-left: 1px solid var(--border); border-radius: 0;
background: var(--accent); color: var(--accent-fg);
Expand Down Expand Up @@ -559,6 +573,8 @@ html[data-theme="new"] .toolhead-card.nt-head .toolhead-actions { grid-area: act
order: 6; flex: 1 1 100%; overflow-x: auto; border-top: 1px solid var(--border);
}
html[data-theme="new"] header nav button { padding: 12px 14px; white-space: nowrap; }
html[data-theme="new"] .subnav { padding: 0 8px; }
html[data-theme="new"] .subnav button { min-height: 44px; padding: 12px 14px; }
html[data-theme="new"] header .conn { margin-left: 0; min-height: 44px; }
html[data-theme="new"] header .upload-btn { min-height: 44px; }
html[data-theme="new"] main { padding: 0 12px 16px; }
Expand Down
16 changes: 16 additions & 0 deletions multiace/web/frontend/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ nav button {
}
nav button:hover { border-color: var(--accent); }
nav button.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
/* Second row: the children of the open tab group. */
.subnav {
display: flex; gap: .3rem; padding: .4rem 1rem; overflow-x: auto;
border-bottom: 1px solid var(--border); background: var(--card);
}
.subnav button {
background: transparent; color: var(--fg);
border: 1px solid var(--border);
padding: .3rem .8rem; cursor: pointer;
border-radius: 4px; font-size: .85rem; white-space: nowrap;
}
.subnav button:hover { border-color: var(--accent); }
.subnav button.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
@media (max-width: 700px) {
.subnav button { min-height: 44px; }
}
.upload-input { display: none; }
.upload-btn {
margin-left: auto;
Expand Down
Loading