fusion_plating: icon picker grid + auto-icon from name (v19.0.2.0.3)
Replaced text input with a clickable 24-icon grid picker for the side panel. Icons are curated for plating (flask, blast, mask, rinse, bake, plate, inspect, etc.). When adding a new step, the icon is automatically guessed from the name via keyword matching (e.g. "Masking" → paint-brush, "Oven baking" → fire, "Acid Dip" → flask). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,63 @@ const NODE_TYPE_OPTIONS = [
|
|||||||
{ value: "step", label: "Step" },
|
{ value: "step", label: "Step" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// ---- Icon picker options (curated for plating / manufacturing) -----------
|
||||||
|
const ICON_OPTIONS = [
|
||||||
|
{ value: "fa-flask", label: "Flask / Chemistry" },
|
||||||
|
{ value: "fa-industry", label: "Industry / Line" },
|
||||||
|
{ value: "fa-sitemap", label: "Sitemap / Process" },
|
||||||
|
{ value: "fa-wrench", label: "Wrench / Operation" },
|
||||||
|
{ value: "fa-cog", label: "Gear / General" },
|
||||||
|
{ value: "fa-cogs", label: "Gears / System" },
|
||||||
|
{ value: "fa-paint-brush", label: "Paint / Masking" },
|
||||||
|
{ value: "fa-eraser", label: "Eraser / De-Masking" },
|
||||||
|
{ value: "fa-th", label: "Grid / Racking" },
|
||||||
|
{ value: "fa-fire", label: "Fire / Bake" },
|
||||||
|
{ value: "fa-bolt", label: "Bolt / Electric" },
|
||||||
|
{ value: "fa-diamond", label: "Diamond / Plating" },
|
||||||
|
{ value: "fa-tint", label: "Tint / Rinse" },
|
||||||
|
{ value: "fa-shower", label: "Shower / Clean" },
|
||||||
|
{ value: "fa-bullseye", label: "Target / Blast" },
|
||||||
|
{ value: "fa-search", label: "Search / Inspect" },
|
||||||
|
{ value: "fa-check-circle", label: "Check / Approve" },
|
||||||
|
{ value: "fa-clock-o", label: "Clock / Wait" },
|
||||||
|
{ value: "fa-sun-o", label: "Sun / Dry" },
|
||||||
|
{ value: "fa-thermometer-half", label: "Temp / Heat" },
|
||||||
|
{ value: "fa-eye", label: "Eye / Visual" },
|
||||||
|
{ value: "fa-hand-paper-o", label: "Hand / Manual" },
|
||||||
|
{ value: "fa-cube", label: "Cube / Part" },
|
||||||
|
{ value: "fa-shield", label: "Shield / Protect" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ---- Auto-icon: guess the best icon from the node name ------------------
|
||||||
|
const ICON_KEYWORDS = [
|
||||||
|
{ pattern: /mask/i, icon: "fa-paint-brush" },
|
||||||
|
{ pattern: /de-?mask|unmask/i, icon: "fa-eraser" },
|
||||||
|
{ pattern: /rack/i, icon: "fa-th" },
|
||||||
|
{ pattern: /de-?rack|unrack/i, icon: "fa-th" },
|
||||||
|
{ pattern: /blast/i, icon: "fa-bullseye" },
|
||||||
|
{ pattern: /bake|oven/i, icon: "fa-fire" },
|
||||||
|
{ pattern: /clean|soak|wash/i, icon: "fa-shower" },
|
||||||
|
{ pattern: /rinse/i, icon: "fa-tint" },
|
||||||
|
{ pattern: /dry/i, icon: "fa-sun-o" },
|
||||||
|
{ pattern: /nickel|plate|plat/i, icon: "fa-diamond" },
|
||||||
|
{ pattern: /strike|electro/i, icon: "fa-bolt" },
|
||||||
|
{ pattern: /acid|dip|etch/i, icon: "fa-flask" },
|
||||||
|
{ pattern: /inspect|check|test/i, icon: "fa-search" },
|
||||||
|
{ pattern: /ready|wait|queue/i, icon: "fa-clock-o" },
|
||||||
|
{ pattern: /line|process/i, icon: "fa-industry" },
|
||||||
|
{ pattern: /heat|temp/i, icon: "fa-thermometer-half" },
|
||||||
|
{ pattern: /porosity/i, icon: "fa-tint" },
|
||||||
|
];
|
||||||
|
|
||||||
|
function guessIcon(name) {
|
||||||
|
if (!name) return "fa-cog";
|
||||||
|
for (const rule of ICON_KEYWORDS) {
|
||||||
|
if (rule.pattern.test(name)) return rule.icon;
|
||||||
|
}
|
||||||
|
return "fa-cog";
|
||||||
|
}
|
||||||
|
|
||||||
export class RecipeTreeEditor extends Component {
|
export class RecipeTreeEditor extends Component {
|
||||||
static template = "fusion_plating.RecipeTreeEditor";
|
static template = "fusion_plating.RecipeTreeEditor";
|
||||||
static props = ["*"];
|
static props = ["*"];
|
||||||
@@ -205,6 +262,7 @@ export class RecipeTreeEditor extends Component {
|
|||||||
parent_id: this.state.addingTo,
|
parent_id: this.state.addingTo,
|
||||||
name: name,
|
name: name,
|
||||||
node_type: this.state.newNodeType,
|
node_type: this.state.newNodeType,
|
||||||
|
vals: { icon: guessIcon(name) },
|
||||||
});
|
});
|
||||||
if (result && result.ok) {
|
if (result && result.ok) {
|
||||||
this.notification.add(`Added "${name}"`, { type: "success" });
|
this.notification.add(`Added "${name}"`, { type: "success" });
|
||||||
@@ -391,6 +449,10 @@ export class RecipeTreeEditor extends Component {
|
|||||||
return NODE_TYPE_OPTIONS;
|
return NODE_TYPE_OPTIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getIconOptions() {
|
||||||
|
return ICON_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
formatDuration(minutes) {
|
formatDuration(minutes) {
|
||||||
if (!minutes) return "";
|
if (!minutes) return "";
|
||||||
if (minutes < 60) return `${Math.round(minutes)}m`;
|
if (minutes < 60) return `${Math.round(minutes)}m`;
|
||||||
|
|||||||
@@ -372,6 +372,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Icon picker ------------------------------------------------------------
|
||||||
|
|
||||||
|
.o_fp_recipe_icon_picker {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_fp_recipe_icon_btn {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.12s, background-color 0.12s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--o-action, var(--bs-primary));
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--o-action, var(--bs-primary));
|
||||||
|
border-color: var(--o-action, var(--bs-primary));
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Responsive -------------------------------------------------------------
|
// ---- Responsive -------------------------------------------------------------
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
@@ -100,14 +100,15 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-bold">Icon (FA class)</label>
|
<label class="form-label fw-bold">Icon</label>
|
||||||
<div class="input-group">
|
<div class="o_fp_recipe_icon_picker">
|
||||||
<span class="input-group-text">
|
<t t-foreach="getIconOptions()" t-as="ic" t-key="ic.value">
|
||||||
<i t-att-class="'fa ' + (state.selectedNode.icon || 'fa-cog')"/>
|
<button t-att-class="'o_fp_recipe_icon_btn' + (state.selectedNode.icon === ic.value ? ' active' : '')"
|
||||||
</span>
|
t-on-click.stop="() => { state.selectedNode.icon = ic.value; }"
|
||||||
<input type="text" class="form-control"
|
t-att-title="ic.label">
|
||||||
t-att-value="state.selectedNode.icon"
|
<i t-att-class="'fa ' + ic.value"/>
|
||||||
t-on-change="(ev) => { state.selectedNode.icon = ev.target.value; }"/>
|
</button>
|
||||||
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user