Skip to content

Commit 837aafe

Browse files
committed
docs(manufacturers): clarify PR 857 logo workflow comments
1 parent 0c52653 commit 837aafe

11 files changed

Lines changed: 35 additions & 10 deletions

File tree

client/src/components/otherModels.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IFilament } from "../pages/filaments/model";
55
import { IVendor } from "../pages/vendors/model";
66
import { getAPIURL, getBasePath } from "../utils/url";
77

8+
// Build shared table-filter options here so spool/filament selectors reuse the same labels and tooltip metadata.
89
export function useSpoolmanFilamentFilter(enabled: boolean = false) {
910
return useQuery<IFilament[], unknown, ColumnFilterItem[]>({
1011
enabled: enabled,
@@ -17,13 +18,10 @@ export function useSpoolmanFilamentFilter(enabled: boolean = false) {
1718
return response.json();
1819
},
1920
select: (data) => {
20-
// Concatenate vendor name and filament name
2121
const names = data
22-
// Remove empty names
2322
.filter((filament) => {
2423
return filament.name !== null && filament.name !== undefined && filament.name !== "";
2524
})
26-
// Transform to ColumnFilterItem
2725
.map((filament) => {
2826
let name = "";
2927
if (filament.vendor?.name) {
@@ -73,12 +71,11 @@ export function useSpoolmanFilamentFilter(enabled: boolean = false) {
7371
</Tooltip>
7472
),
7573
value: filament.id,
74+
// Preserve a plain string sort key so the dropdown stays alphabetized after wrapping with JSX.
7675
sortId: name,
7776
};
7877
})
79-
// Remove duplicates
8078
.filter((item, index, self) => self.findIndex((t) => t.value === item.value) === index)
81-
// Sort by name
8279
.sort((a, b) => a.sortId.localeCompare(b.sortId));
8380
return names;
8481
},
@@ -228,6 +225,7 @@ interface VendorLogoManifest {
228225
print_files?: string[];
229226
}
230227

228+
// Missing bundled/runtime logo packs should degrade to empty suggestions instead of breaking vendor forms.
231229
export function useVendorLogoManifest(enabled: boolean = true) {
232230
return useQuery<VendorLogoManifest>({
233231
enabled,

client/src/components/vendorLogo.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface VendorLogoProps {
1010
fallbackStyle?: CSSProperties;
1111
}
1212

13+
// Walk the vendor's candidate logo URLs in order, then optionally fall back to plain vendor text.
1314
export function VendorLogo({
1415
vendor,
1516
usePrintLogo = false,
@@ -20,6 +21,7 @@ export function VendorLogo({
2021
const candidates = useMemo(() => getVendorLogoCandidates(vendor, usePrintLogo), [vendor, usePrintLogo]);
2122
const [currentCandidateIndex, setCurrentCandidateIndex] = useState(0);
2223
useEffect(() => {
24+
// Reset to the highest-priority candidate whenever the vendor or logo mode changes.
2325
setCurrentCandidateIndex(0);
2426
}, [vendor, usePrintLogo]);
2527

client/src/pages/printing/exportDialog.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface ExportDialogProps {
2020
zipFileTypeName: string;
2121
}
2222

23+
// Reuse the print-preview DOM for export so PNG and AML output always match the configured label layout.
2324
const ExportDialog = ({
2425
items,
2526
printSettings,
@@ -52,6 +53,7 @@ const ExportDialog = ({
5253
const itemHeight = Math.max(paperHeight - margin.top - margin.bottom, 0);
5354

5455
const contentRef = useRef<HTMLDivElement>(null);
56+
// AML consumers care about physical size, so exported PNGs need explicit pHYs DPI metadata.
5557
const pngSignature = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
5658

5759
const readUint32BE = (bytes: Uint8Array, offset: number) => {
@@ -204,6 +206,7 @@ const ExportDialog = ({
204206
.replace(/\.+$/g, "");
205207
};
206208

209+
// Export mode intentionally renders one label per preview page so filenames map 1:1 to saved files.
207210
const pageBlocks: ReactElement[][] = [];
208211
for (const item of items) {
209212
pageBlocks.push([item]);
@@ -406,6 +409,7 @@ const ExportDialog = ({
406409
};
407410
};
408411

412+
// Repeated copies share the same DOM shape, but export should still emit only one file per unique label design.
409413
const getUniqueExportItems = () => {
410414
const hasPrinted: Element[] = [];
411415
const itemsToPrint = getPrintItems();
@@ -414,7 +418,6 @@ const ExportDialog = ({
414418
let idx = 1;
415419

416420
for (const item of itemsToPrint) {
417-
// Prevent printing copies
418421
let isDuplicate = false;
419422
for (let i = 0; i < hasPrinted.length; i += 1) {
420423
if (item.isEqualNode(hasPrinted[i])) {

client/src/pages/printing/qrCodeExportDialog.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ interface QRCodeExportDialogProps {
5757
zipFileTypeName: string;
5858
}
5959

60+
// Layer QR/logo/title export controls onto the shared export dialog used by spool and filament label flows.
6061
const QRCodeExportDialog = ({
6162
items,
6263
printSettings,
@@ -111,10 +112,12 @@ const QRCodeExportDialog = ({
111112
const bottomMargin = printSettings.printSettings?.margin?.bottom ?? 0;
112113
const containerPaddingMm = 1.2; // .print-qrcode-item vertical padding
113114
const minMainHeightMm = showQRCodeMode === "no" ? 0 : 8;
115+
// Keep enough room for the QR/info area even when title and logo settings request a tall header.
114116
const availableContentHeightMm = Math.max(0, paperHeight - topMargin - bottomMargin - containerPaddingMm);
115117
const maxHeaderHeightMm = Math.max(0, availableContentHeightMm - minMainHeightMm);
116118
const preview =
117119
previewValues ?? ({ default: `WEB+SPOOLMAN:S-{id}`, url: `${baseUrlRoot}/spool/show/{id}` } as const);
120+
// Show the actual fitted title size range from the preview, not just the configured maximum.
118121
const appliedTitleSizeDisplay = useMemo(() => {
119122
const values = Object.values(titleEffectiveTextSizesByItem).filter((value) => Number.isFinite(value));
120123
if (values.length === 0) {
@@ -128,6 +131,7 @@ const QRCodeExportDialog = ({
128131
return `${maxSize.toFixed(1)} mm`;
129132
}, [titleEffectiveTextSizesByItem, titleMaxTextSize]);
130133

134+
// Each label can shrink its title differently, so keep measurements per preview item for the settings summary.
131135
const elements = items.map((item, idx) => {
132136
const hasHeader = (showManufacturerLogo && !!item.vendor) || (showTitle && !!item.title);
133137
return (

client/src/pages/printing/qrCodePrintingDialog.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ interface QRCodePrintingDialogProps {
5555
previewValues?: { default: string; url: string };
5656
}
5757

58+
// Layer QR/logo/title print controls onto the shared sheet-printing dialog used by spool and filament label flows.
5859
const QRCodePrintingDialog = ({
5960
items,
6061
printSettings,
@@ -107,10 +108,12 @@ const QRCodePrintingDialog = ({
107108
const bottomMargin = printSettings.printSettings?.margin?.bottom ?? 0;
108109
const containerPaddingMm = 1.2; // .print-qrcode-item vertical padding
109110
const minMainHeightMm = showQRCodeMode === "no" ? 0 : 8;
111+
// Keep enough room for the QR/info area even when title and logo settings request a tall header.
110112
const availableContentHeightMm = Math.max(0, paperHeight - topMargin - bottomMargin - containerPaddingMm);
111113
const maxHeaderHeightMm = Math.max(0, availableContentHeightMm - minMainHeightMm);
112114
const preview =
113115
previewValues ?? ({ default: `WEB+SPOOLMAN:S-{id}`, url: `${baseUrlRoot}/spool/show/{id}` } as const);
116+
// Show the actual fitted title size range from the preview, not just the configured maximum.
114117
const appliedTitleSizeDisplay = useMemo(() => {
115118
const values = Object.values(titleEffectiveTextSizesByItem).filter((value) => Number.isFinite(value));
116119
if (values.length === 0) {
@@ -124,6 +127,7 @@ const QRCodePrintingDialog = ({
124127
return `${maxSize.toFixed(1)} mm`;
125128
}, [titleEffectiveTextSizesByItem, titleMaxTextSize]);
126129

130+
// Each label can shrink its title differently, so keep measurements per preview item for the settings summary.
127131
const elements = items.map((item, idx) => {
128132
const hasHeader = (showManufacturerLogo && !!item.vendor) || (showTitle && !!item.title);
129133
return (

client/src/pages/printing/spoolSelectModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface ISpoolCollapsed extends ISpool {
2222
"filament.material"?: string;
2323
}
2424

25+
// Flatten filament details so shared table helpers can sort and filter them like top-level spool fields.
2526
function collapseSpool(element: ISpool): ISpoolCollapsed {
2627
let filament_name: string;
2728
if (element.filament.vendor && "name" in element.filament.vendor) {
@@ -37,6 +38,7 @@ function collapseSpool(element: ISpool): ISpoolCollapsed {
3738
};
3839
}
3940

41+
// Keep spool picking self-contained: the modal owns selection state and only returns ids once the user confirms.
4042
const SpoolSelectModal = ({ description, initialSelectedIds, onExport, onPrint }: Props) => {
4143
const [selectedItems, setSelectedItems] = useState<number[]>(initialSelectedIds ?? []);
4244
const [showArchived, setShowArchived] = useState(false);
@@ -85,7 +87,7 @@ const SpoolSelectModal = ({ description, initialSelectedIds, onExport, onPrint }
8587
[tableProps.dataSource],
8688
);
8789

88-
// Function to add/remove all filtered items from selected items
90+
// Bulk toggles apply to the rows currently loaded in the modal so the action matches the visible table.
8991
const selectUnselectFiltered = (select: boolean) => {
9092
setSelectedItems((prevSelected) => {
9193
const nextSelected = new Set(prevSelected);
@@ -100,14 +102,12 @@ const SpoolSelectModal = ({ description, initialSelectedIds, onExport, onPrint }
100102
});
101103
};
102104

103-
// Handler for selecting/unselecting individual items
104105
const handleSelectItem = (item: number) => {
105106
setSelectedItems((prevSelected) =>
106107
prevSelected.includes(item) ? prevSelected.filter((selected) => selected !== item) : [...prevSelected, item],
107108
);
108109
};
109110

110-
// State for the select/unselect all checkbox
111111
const isAllFilteredSelected = dataSource.every((spool) => selectedItems.includes(spool.id));
112112
const isSomeButNotAllFilteredSelected =
113113
dataSource.some((spool) => selectedItems.includes(spool.id)) && !isAllFilteredSelected;
@@ -189,7 +189,7 @@ const SpoolSelectModal = ({ description, initialSelectedIds, onExport, onPrint }
189189
onChange={(e) => {
190190
setShowArchived(e.target.checked);
191191
if (!e.target.checked) {
192-
// Remove archived spools from selected items
192+
// Hidden archived rows should not linger in the selection once the toggle is turned back off.
193193
setSelectedItems((prevSelected) =>
194194
prevSelected.filter(
195195
(selected) => dataSource.find((spool) => spool.id === selected)?.archived !== true,

client/src/pages/vendors/edit.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export const VendorEdit = () => {
102102
const printLogoUrlValue = typeof watchedExtra?.print_logo_url === "string" ? watchedExtra.print_logo_url.trim() : "";
103103
const hasCustomWebLogo = logoUrlValue !== "";
104104
const hasCustomPrintLogo = printLogoUrlValue !== "";
105+
// Build a synthetic vendor record from unsaved form state so the shared preview component works before save.
105106
const logoPreviewVendor: IVendor = {
106107
id: 0,
107108
registered: "",
@@ -165,6 +166,7 @@ export const VendorEdit = () => {
165166
formProps.form?.setFieldValue(["extra", field], "");
166167
};
167168

169+
// Conversion only materializes a runtime print logo when the user already picked a web logo and no print override.
168170
const convertWebLogoToPrint = async () => {
169171
if (!canConvertPrintLogo) {
170172
if (printLogoUrlValue) {

client/src/utils/vendorLogo.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface LogoMatchScore {
1111
score: number;
1212
}
1313

14+
// Vendor logo extras may be stored as JSON strings or legacy plain text; accept either form for previews.
1415
export function parseExtraString(value: string | undefined): string | undefined {
1516
if (!value) {
1617
return undefined;
@@ -62,6 +63,7 @@ function slugFromManifestPath(path: string, type: "web" | "print"): string {
6263
return base;
6364
}
6465

66+
// Rank manifest entries conservatively so auto-matching prefers vendor-specific files over generic logo names.
6567
function scoreLogoPath(name: string, path: string, type: "web" | "print"): number {
6668
const targetSlug = slugifyVendorName(name);
6769
if (!targetSlug) {
@@ -110,6 +112,7 @@ function rankPaths(name: string, paths: string[], type: "web" | "print"): LogoMa
110112
.sort((a, b) => b.score - a.score);
111113
}
112114

115+
// Only strong matches should auto-populate preview/logo suggestions; weaker matches stay manual choices.
113116
function findBestPath(name: string, paths: string[], type: "web" | "print"): string | undefined {
114117
const ranked = rankPaths(name, paths, type);
115118
if (ranked.length === 0) {
@@ -139,6 +142,7 @@ export function suggestVendorLogoPaths(name: string, manifest: VendorLogoManifes
139142
return { webPath, printPath };
140143
}
141144

145+
// Try explicit overrides first, then slug-based runtime paths so local logo packs still resolve without saved URLs.
142146
export function getVendorLogoCandidates(vendor: IVendor | undefined, usePrintLogo: boolean): string[] {
143147
if (!vendor) {
144148
return [];

spoolman/api/v1/vendor.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
tags=["vendor"],
2323
)
2424

25+
# Logo fields are stored in vendor.extra, but the logo workflow owns their schema instead of generic extra-field config.
2526
RESERVED_VENDOR_EXTRA_KEYS = {"logo_url", "print_logo_url"}
2627

2728
# ruff: noqa: D103
@@ -197,6 +198,7 @@ async def notify_any(
197198
)
198199
async def convert_web_logo_to_print(body: VendorLogoConvertRequest) -> VendorLogoConvertResult | JSONResponse:
199200
try:
201+
# Pillow conversion can touch disk and parse image bytes, so run it off the event loop.
200202
print_logo_url = await asyncio.to_thread(convert_web_logo_to_print_logo, body.logo_url, body.vendor_name)
201203
except (ValueError, RuntimeError, OSError) as exc:
202204
return JSONResponse(status_code=400, content=Message(message=str(exc)).model_dump())
@@ -258,6 +260,7 @@ async def create( # noqa: ANN201
258260
):
259261
if body.extra:
260262
all_fields = await get_extra_fields(db, EntityType.vendor)
263+
# Saved logo paths live alongside extras but bypass generic field validation because they are app-managed.
261264
extra_to_validate = {k: v for k, v in body.extra.items() if k not in RESERVED_VENDOR_EXTRA_KEYS}
262265
try:
263266
validate_extra_field_dict(all_fields, extra_to_validate)
@@ -299,6 +302,7 @@ async def update( # noqa: ANN201
299302

300303
if body.extra:
301304
all_fields = await get_extra_fields(db, EntityType.vendor)
305+
# Saved logo paths live alongside extras but bypass generic field validation because they are app-managed.
302306
extra_to_validate = {k: v for k, v in body.extra.items() if k not in RESERVED_VENDOR_EXTRA_KEYS}
303307
try:
304308
validate_extra_field_dict(all_fields, extra_to_validate)

spoolman/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def get_configjs() -> Response:
103103
@app.get(env.get_base_path() + "/vendor-logos/{asset_path:path}", include_in_schema=False)
104104
def get_vendor_logo_asset(asset_path: str) -> FileResponse:
105105
"""Serve vendor logo assets from runtime data directory first, then bundled assets."""
106+
# Runtime-generated print logos should override bundled defaults without changing client URLs.
106107
resolved = resolve_vendor_logo_asset(asset_path)
107108
if resolved is None:
108109
raise HTTPException(status_code=404, detail="Logo asset not found.")

0 commit comments

Comments
 (0)