Collector auto-links measuring tools for metrology PCs; settings rail cleanup
Metrology PCs (CMM, Keyence, Genspect, wax-and-trace imaging pc-types) drive an attached measuring instrument. The PC itself stays a shopfloor PC, but the collector now models the instrument: - New METROLOGY_TOOL_MAP (pctypemap.py) maps those pc-types to a MeasuringToolType (CMM, Vision System, Genspect, Form Tracer). - ComputersPlugin._sync_measuringtool_link creates the MeasuringTool asset once and a directional PC->tool "controls" relationship, tagged collector:measuringtool. Idempotent (re-push reuses, no duplicate asset) and self-archiving (a PC re-imaged to a non-metrology type deactivates the link but keeps the asset and any calibration history). Mirrors the printer-link pattern. The MeasuringToolType is created on demand if not seeded. - 4 tests: create+link, idempotent re-push, non-metrology skip, repurpose archives. Non-metrology PCs never warn about a missing controls type. Settings rail cleanup: - Collapsible groups so the 13-group rail fits without scrolling (1511px -> 488px). The group containing the current page expands; the rest collapse. CSS-drawn caret (ASCII source, no Unicode). Empty groups never render, in both the rail and the landing page. - Measuring Tools group placed with the other asset groups (right after Machines) instead of appended last; empty placeholder positions the plugin-contributed cards. - Operating Systems moved from PCs to General Reference: OS is cross-asset (PCs, machines, measuring tools, network devices all run one). Plus docs/proposals/ge-enforce-plugin.md: a planning doc for refactoring GE-Enforce/DSC into a shopdb plugin (manifest as shopdb data, payloads on SMB/HTTP/inline), grounded in the real manifest schema. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,17 +10,27 @@
|
||||
|
||||
<nav class="rail-nav">
|
||||
<template v-for="group in visibleGroups" :key="group.title">
|
||||
<div class="rail-group-heading">{{ group.title }}</div>
|
||||
<router-link
|
||||
v-for="card in group.cards"
|
||||
:key="card.to"
|
||||
:to="card.to"
|
||||
class="rail-link"
|
||||
:class="{ active: isActive(card.to) }"
|
||||
<button
|
||||
type="button"
|
||||
class="rail-group-heading"
|
||||
:class="{ collapsed: !isExpanded(group.title) }"
|
||||
@click="toggleGroup(group.title)"
|
||||
>
|
||||
<span class="rail-icon"><component :is="card.icon" :size="16" /></span>
|
||||
<span class="rail-label">{{ card.title }}</span>
|
||||
</router-link>
|
||||
<span class="rail-chevron" aria-hidden="true"></span>
|
||||
{{ group.title }}
|
||||
</button>
|
||||
<template v-if="isExpanded(group.title)">
|
||||
<router-link
|
||||
v-for="card in group.cards"
|
||||
:key="card.to"
|
||||
:to="card.to"
|
||||
class="rail-link"
|
||||
:class="{ active: isActive(card.to) }"
|
||||
>
|
||||
<span class="rail-icon"><component :is="card.icon" :size="16" /></span>
|
||||
<span class="rail-label">{{ card.title }}</span>
|
||||
</router-link>
|
||||
</template>
|
||||
</template>
|
||||
<p v-if="!visibleGroups.length" class="rail-empty">No matching settings</p>
|
||||
</nav>
|
||||
@@ -48,7 +58,9 @@ const search = ref('')
|
||||
// Filter the rail by title/description; drop groups that end up empty.
|
||||
const visibleGroups = computed(() => {
|
||||
const term = search.value.trim().toLowerCase()
|
||||
if (!term) return groups.value
|
||||
// Drop empty groups (e.g. the Measuring Tools placeholder when its plugin is
|
||||
// disabled) so a bare heading never renders.
|
||||
if (!term) return groups.value.filter(g => g.cards.length)
|
||||
return groups.value
|
||||
.map(g => ({
|
||||
title: g.title,
|
||||
@@ -65,6 +77,25 @@ function isActive(to) {
|
||||
const base = to.split('?')[0]
|
||||
return route.path === base
|
||||
}
|
||||
|
||||
// Collapsible groups so the 13-section rail fits without scrolling: only the
|
||||
// group containing the current page is open; the user can toggle others.
|
||||
// Searching forces every matching group open.
|
||||
const groupOfActive = computed(() => {
|
||||
for (const g of groups.value) {
|
||||
if (g.cards.some(c => isActive(c.to))) return g.title
|
||||
}
|
||||
return groups.value[0]?.title
|
||||
})
|
||||
const manualToggles = ref({})
|
||||
function toggleGroup(title) {
|
||||
manualToggles.value = { ...manualToggles.value, [title]: !isExpanded(title) }
|
||||
}
|
||||
function isExpanded(title) {
|
||||
if (search.value.trim()) return true
|
||||
if (title in manualToggles.value) return manualToggles.value[title]
|
||||
return title === groupOfActive.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -82,11 +113,11 @@ function isActive(to) {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.rail-title {
|
||||
margin: 0 0 0.75rem 0;
|
||||
font-size: 1.5rem;
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.rail-search {
|
||||
margin-bottom: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.search-input {
|
||||
width: 100%;
|
||||
@@ -99,26 +130,52 @@ function isActive(to) {
|
||||
}
|
||||
|
||||
.rail-group-heading {
|
||||
margin: 1rem 0 0.3rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
width: 100%;
|
||||
margin: 0.35rem 0 0.15rem 0;
|
||||
padding: 0.2rem 0;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-light);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
.rail-group-heading:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
.rail-group-heading:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
/* CSS-drawn caret (ASCII-only source): a right-pointing triangle that
|
||||
rotates down when the group is expanded. */
|
||||
.rail-chevron {
|
||||
width: 0;
|
||||
height: 0;
|
||||
flex: 0 0 auto;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 5px solid currentColor;
|
||||
transition: transform 0.12s ease;
|
||||
}
|
||||
.rail-group-heading:not(.collapsed) .rail-chevron {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.rail-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.4rem 0.55rem;
|
||||
padding: 0.25rem 0.55rem;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
font-size: 0.88rem;
|
||||
font-size: 0.86rem;
|
||||
border-left: 2px solid transparent;
|
||||
}
|
||||
.rail-link:hover {
|
||||
|
||||
Reference in New Issue
Block a user