fix: display friendly legacy printer model names in stream overlay (#3134)

This commit is contained in:
Adam Spice
2026-09-29 16:26:40 +02:00
committed by GitHub
parent 8ea50583f8
commit 2b6dbe5ae5
5 changed files with 61 additions and 63 deletions
@@ -150,6 +150,17 @@ describe('StreamOverlayPage', () => {
if (query === '?show=model') expect(screen.queryByText(/X1 Carbon/)).not.toBeInTheDocument();
});
it.each([
['BL-P001', 'X1C'],
['O1D', 'H2D'],
['H2D', 'H2D'],
['Future printer', 'Future printer'],
])('displays the saved model %s as %s', async (model, displayName) => {
server.use(http.get('/api/v1/printers/:id', () => HttpResponse.json({ ...mockPrinter, model })));
renderOverlayPage(1, '?show=model');
expect(await screen.findByText(displayName)).toBeInTheDocument();
});
it.each([null, ''])('omits a missing model without adding a separator (%s)', async (model) => {
server.use(http.get('/api/v1/printers/:id', () => HttpResponse.json({ ...mockPrinter, model })));
renderOverlayPage(1, '?show=printer,model');
@@ -157,7 +168,7 @@ describe('StreamOverlayPage', () => {
expect(screen.queryByText(/·/)).not.toBeInTheDocument();
});
it.each([['H2D', 'Workshop · H2D'], [null, 'Workshop'], ['', 'Workshop']] as const)('reads the model (%s) from the OBS token feed without requesting printer details', async (model, identity) => {
it.each([['H2D', 'H2D'], ['BL-P001', 'X1C'], ['O1D', 'H2D'], [null, ''], ['', '']] as const)('reads the model %s from the OBS token feed without requesting printer details', async (model, displayName) => {
let printerHit = false;
server.use(
http.get('/api/v1/printers/:id/overlay-status', () => HttpResponse.json({
@@ -170,7 +181,7 @@ describe('StreamOverlayPage', () => {
}),
);
renderOverlayPage(1, '?token=obs-tok&show=printer,model');
expect(await screen.findByText(identity)).toBeInTheDocument();
expect(await screen.findByText(displayName ? `Workshop · ${displayName}` : 'Workshop')).toBeInTheDocument();
expect(printerHit).toBe(false);
if (!model) expect(screen.queryByText(/·/)).not.toBeInTheDocument();
});
+1 -45
View File
@@ -1,6 +1,7 @@
import { useState, useEffect, useLayoutEffect, useMemo, useRef, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { compareFwVersions } from '../utils/firmwareVersion';
import { mapModelCode } from '../utils/printerModel';
import { formatPrintName } from '../utils/printName';
import { isBedSlinger } from '../utils/bedSlinger';
import { computePopoverPosition, type PopoverPosition } from '../utils/popoverPosition';
@@ -1747,51 +1748,6 @@ const MODELS_WITH_EXHAUST_LABEL: ReadonlySet<string> = new Set([
'X2D',
]);
// Map SSDP model codes to display names
function mapModelCode(ssdpModel: string | null): string {
if (!ssdpModel) return '';
const modelMap: Record<string, string> = {
// H2 Series
'O1D': 'H2D',
'O1E': 'H2D Pro',
'O2D': 'H2D Pro',
'O1C': 'H2C',
'O1C2': 'H2C',
'O1S': 'H2S',
// X1 Series
'BL-P001': 'X1C',
'BL-P002': 'X1',
'BL-P003': 'X1E',
// X2 Series
'N6': 'X2D',
// A2 Series
'N9': 'A2L',
// P Series
'C11': 'P1S',
'C12': 'P1P',
'C13': 'P2S',
// A1 Series
'N2S': 'A1',
'N1': 'A1 Mini',
// Direct matches
'X1C': 'X1C',
'X1': 'X1',
'X1E': 'X1E',
'X2D': 'X2D',
'P1S': 'P1S',
'P1P': 'P1P',
'P2S': 'P2S',
'A1': 'A1',
'A1 Mini': 'A1 Mini',
'A2L': 'A2L',
'H2D': 'H2D',
'H2D Pro': 'H2D Pro',
'H2C': 'H2C',
'H2S': 'H2S',
};
return modelMap[ssdpModel] || ssdpModel;
}
// ─── AMS Name Hover Card ──────────────────────────────────────────────────────
// Wraps the AMS label (e.g. "AMS-A") and shows a popup with:
// • User-defined friendly name (editable, protected by printers:update)
+2 -1
View File
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import { Layers, Clock, Timer, Printer, Flame, Square, Box } from 'lucide-react';
import { api, ApiError, withStreamToken } from '../api/client';
import { formatDuration, formatETA, type TimeFormat } from '../utils/date';
import { mapModelCode } from '../utils/printerModel';
type TFunction = (key: string, options?: Record<string, unknown>) => string;
@@ -217,7 +218,7 @@ export function StreamOverlayPage() {
);
const printerIdentity = [
config.showPrinter ? printer?.name : null,
config.showModel ? printer?.model : null,
config.showModel ? mapModelCode(printer?.model ?? null) : null,
].filter(Boolean).join(' · ');
const status = kiosk ? overlay : statusData;
const timeFormat: TimeFormat = (kiosk ? overlay?.time_format : settings?.time_format) || 'system';
@@ -8,6 +8,7 @@ import { api } from '../../api/client';
import type { PrinterStatus, AMSTray, SpoolAssignment } from '../../api/client';
import { getGlobalTrayId, getFillBarColor, getSpoolmanFillLevel, getFallbackSpoolTag, formatSlotLabel, isBambuLabSpool, resolveSlotNozzleDiameter } from '../../utils/amsHelpers';
import { getSwatchStyle, resolveSpoolColorName } from '../../utils/colors';
import { mapModelCode } from '../../utils/printerModel';
import { spoolSwatchStyle } from '../../components/spoolbuddy/spoolPaint';
/**
@@ -39,21 +40,6 @@ function getAmsName(amsId: number): string {
return `AMS ${amsId}`;
}
function mapModelCode(ssdpModel: string | null): string {
if (!ssdpModel) return '';
const modelMap: Record<string, string> = {
'O1D': 'H2D', 'O1E': 'H2D Pro', 'O2D': 'H2D Pro', 'O1C': 'H2C', 'O1C2': 'H2C', 'O1S': 'H2S',
'BL-P001': 'X1C', 'BL-P002': 'X1', 'BL-P003': 'X1E',
'N6': 'X2D',
'N9': 'A2L',
'C11': 'P1S', 'C12': 'P1P', 'C13': 'P2S',
'N2S': 'A1', 'N1': 'A1 Mini',
'X1C': 'X1C', 'X1': 'X1', 'X1E': 'X1E', 'X2D': 'X2D', 'P1S': 'P1S', 'P1P': 'P1P', 'P2S': 'P2S',
'A1': 'A1', 'A1 Mini': 'A1 Mini', 'A2L': 'A2L', 'H2D': 'H2D', 'H2D Pro': 'H2D Pro', 'H2C': 'H2C', 'H2S': 'H2S',
};
return modelMap[ssdpModel] || ssdpModel;
}
function isTrayEmpty(tray: AMSTray): boolean {
return !tray.tray_type || tray.tray_type === '';
}
+44
View File
@@ -0,0 +1,44 @@
// Map SSDP model codes to display names
export function mapModelCode(ssdpModel: string | null): string {
if (!ssdpModel) return '';
const modelMap: Record<string, string> = {
// H2 Series
'O1D': 'H2D',
'O1E': 'H2D Pro',
'O2D': 'H2D Pro',
'O1C': 'H2C',
'O1C2': 'H2C',
'O1S': 'H2S',
// X1 Series
'BL-P001': 'X1C',
'BL-P002': 'X1',
'BL-P003': 'X1E',
// X2 Series
'N6': 'X2D',
// A2 Series
'N9': 'A2L',
// P Series
'C11': 'P1S',
'C12': 'P1P',
'C13': 'P2S',
// A1 Series
'N2S': 'A1',
'N1': 'A1 Mini',
// Direct matches
'X1C': 'X1C',
'X1': 'X1',
'X1E': 'X1E',
'X2D': 'X2D',
'P1S': 'P1S',
'P1P': 'P1P',
'P2S': 'P2S',
'A1': 'A1',
'A1 Mini': 'A1 Mini',
'A2L': 'A2L',
'H2D': 'H2D',
'H2D Pro': 'H2D Pro',
'H2C': 'H2C',
'H2S': 'H2S',
};
return modelMap[ssdpModel] || ssdpModel;
}