fix: file labels in 3d preview

This commit is contained in:
Tautvydas Derzinskas committed 2026-09-24 19:53:22 +02:00
1 parent ef14899fe7
commit 1fa98a830e
6 files changed
+61 -51

No files matched your search

+11 -21
View File
@@ -63,8 +63,8 @@
"importing": "Importing…",
"importFailed": "Import failed",
"uploadFailed": "Failed to upload: {{files}}",
"multiplateImportFailedWithMessage": "Multi-plate import ({{message}})",
"multiplateImportFailed": "Multi-plate import",
"multiplateImportFailedWithMessage": "Multi-file import ({{message}})",
"multiplateImportFailed": "Multi-file import",
"imported": "Imported \"{{name}}\"",
"profileAdded": "Added a print profile to \"{{name}}\"",
"alreadyInLibrary": "\"{{name}}\" is already in your library",
@@ -110,8 +110,8 @@
"prompt": "How should these files be imported?",
"separateTitle": "Import as separate prints",
"separateDesc": "Each file becomes its own print with a single plate.",
"multiplateTitle": "Import as one multi-plate print",
"multiplateDesc": "All files become plates of a single new print, in the order selected.",
"multiplateTitle": "Import as one model with several files",
"multiplateDesc": "All files are added to a single new model, in the order selected.",
"importing": "Importing…",
"close": "Close",
"importError": "Import failed"
@@ -595,26 +595,19 @@
"addPlatesFailed": "Failed to add plate(s).",
"addPlateFailed": "Failed to add plate.",
"removePlateFailed": "Failed to remove plate.",
"multiplateImportFailed": "Multi-plate import",
"multiplateImportFailedWithMessage": "Multi-plate import ({{message}})"
"multiplateImportFailed": "Multi-file import",
"multiplateImportFailedWithMessage": "Multi-file import ({{message}})"
},
"confirm": {
"deleteMultiple": "Delete {{count}} selected items? This cannot be undone.",
"deleteSingle": "Delete \"{{name}}\"? This cannot be undone.",
"removeFile": "Remove {{filename}} from this model?",
"clearPrepared": "Clear the attached prepared print from this model?",
"removePlate": "Remove plate \"{{filename}}\"?"
"clearPrepared": "Clear the attached prepared print from this model?"
},
"card": {
"select": "Select",
"plateCountBadge_one": "{{count}} plate",
"plateCountBadge_few": "{{count}} plates",
"plateCountBadge_other": "{{count}} plates",
"addingPlates": "Adding plate(s)...",
"dropToAddPlate": "Drop to add as plate",
"doubleClickPreview": "Double-click to open large preview",
"downloadPlaceholder": "Download...",
"downloadAllPlatesZip": "Download all plates (zip)",
"downloadFile": "Download file",
"downloadSelected": "Download all selected",
"downloadTag": "Download tag: {{tag}}",
@@ -659,11 +652,6 @@
"minutes": "{{count}}m"
},
"previewModal": {
"plateCount_one": "{{count}} plate",
"plateCount_few": "{{count}} plates",
"plateCount_other": "{{count}} plates",
"removePlateTooltip": "Remove plate",
"addPlateTooltip": "Add plate",
"addingPlate": "Adding...",
"addPlate": "Add plate",
"openPlateInSlicer": "Open plate in {{slicer}}",
@@ -1118,6 +1106,8 @@
"preview3d": "3D Preview",
"description": "Description",
"noDescription": "No description yet.",
"fileLabel": "File {{n}}",
"fileMeta": "File {{n}} · {{type}}",
"plateLabel": "Plate {{n}}",
"internalPlatesDivider": "{{count}} plates in this file",
"internalPlateObjectCount_one": "{{count}} object",
@@ -1158,8 +1148,8 @@
"openInSlicer": "Open in {{slicer}}",
"openInSlicerGeneric": "Open in Slicer",
"downloadPickerTitle": "Download",
"downloadAllZip": "Download all {{count}} plates (.zip)",
"orDownloadOne": "or download one plate",
"downloadAllZip": "Download all {{count}} files (.zip)",
"orDownloadOne": "or download one file",
"tags": "Tags",
"noTags": "No tags yet.",
"previousImage": "Previous image",
+11 -21
View File
@@ -63,8 +63,8 @@
"importing": "Importuojama…",
"importFailed": "Importuoti nepavyko",
"uploadFailed": "Nepavyko įkelti: {{files}}",
"multiplateImportFailedWithMessage": "Kelių plokščių importas ({{message}})",
"multiplateImportFailed": "Kelių plokščių importas",
"multiplateImportFailedWithMessage": "Kelių failų importas ({{message}})",
"multiplateImportFailed": "Kelių failų importas",
"imported": "Importuota „{{name}}“",
"profileAdded": "Pridėtas spausdinimo profilis prie „{{name}}“",
"alreadyInLibrary": "„{{name}}“ jau yra jūsų bibliotekoje",
@@ -110,8 +110,8 @@
"prompt": "Kaip turėtų būti importuojami šie failai?",
"separateTitle": "Importuoti kaip atskirus modelius",
"separateDesc": "Kiekvienas failas taps atskiru modeliu su viena plokšte.",
"multiplateTitle": "Importuoti kaip vieną kelių plokščių modelį",
"multiplateDesc": "Visi failai taps vieno naujo modelio plokštėmis pasirinkta tvarka.",
"multiplateTitle": "Importuoti kaip vieną modelį su keliais failais",
"multiplateDesc": "Visi failai bus pridėti prie vieno naujo modelio pasirinkta tvarka.",
"importing": "Importuojama…",
"close": "Uždaryti",
"importError": "Importuoti nepavyko"
@@ -596,26 +596,19 @@
"addPlatesFailed": "Nepavyko pridėti plokštės (-ių).",
"addPlateFailed": "Nepavyko pridėti plokštės.",
"removePlateFailed": "Nepavyko pašalinti plokštės.",
"multiplateImportFailed": "Kelių plokščių importas",
"multiplateImportFailedWithMessage": "Kelių plokščių importas ({{message}})"
"multiplateImportFailed": "Kelių failų importas",
"multiplateImportFailedWithMessage": "Kelių failų importas ({{message}})"
},
"confirm": {
"deleteMultiple": "Ištrinti {{count}} pasirinktus elementus? Šio veiksmo anuliuoti negalima.",
"deleteSingle": "Ištrinti „{{name}}“? Šio veiksmo anuliuoti negalima.",
"removeFile": "Pašalinti {{filename}} iš šio modelio?",
"clearPrepared": "Išvalyti prie modelio pridėtą paruoštą spaudinį?",
"removePlate": "Pašalinti plokštę „{{filename}}“?"
"clearPrepared": "Išvalyti prie modelio pridėtą paruoštą spaudinį?"
},
"card": {
"select": "Pasirinkti",
"plateCountBadge_one": "{{count}} plokštė",
"plateCountBadge_few": "{{count}} plokštės",
"plateCountBadge_other": "{{count}} plokščių",
"addingPlates": "Pridedamos plokštės...",
"dropToAddPlate": "Vilkite čia, kad pridėtumėte kaip plokštę",
"doubleClickPreview": "Dukart spustelėkite, kad atidarytumėte didelį peržiūros langą",
"downloadPlaceholder": "Atsisiųsti...",
"downloadAllPlatesZip": "Atsisiųsti visas plokštes (zip)",
"downloadFile": "Atsisiųsti failą",
"downloadSelected": "Atsisiųsti visus pasirinktus",
"downloadTag": "Atsisiųsti žymą: {{tag}}",
@@ -660,11 +653,6 @@
"minutes": "{{count}} min."
},
"previewModal": {
"plateCount_one": "{{count}} plokštė",
"plateCount_few": "{{count}} plokštės",
"plateCount_other": "{{count}} plokščių",
"removePlateTooltip": "Pašalinti plokštę",
"addPlateTooltip": "Pridėti plokštę",
"addingPlate": "Pridedama...",
"addPlate": "Pridėti plokštę",
"openPlateInSlicer": "Atidaryti plokštę su {{slicer}}",
@@ -1119,6 +1107,8 @@
"preview3d": "3D peržiūra",
"description": "Aprašymas",
"noDescription": "Aprašymo dar nėra.",
"fileLabel": "Failas {{n}}",
"fileMeta": "Failas {{n}} · {{type}}",
"plateLabel": "Plokštė {{n}}",
"internalPlatesDivider": "{{count}} plokščių šiame faile",
"internalPlateObjectCount_one": "{{count}} objektas",
@@ -1160,8 +1150,8 @@
"openInSlicer": "Atverti su {{slicer}}",
"openInSlicerGeneric": "Atverti pjaustyklėje",
"downloadPickerTitle": "Atsisiųsti",
"downloadAllZip": "Atsisiųsti visas {{count}} plokštes (.zip)",
"orDownloadOne": "arba atsisiųsti vieną plokštę",
"downloadAllZip": "Atsisiųsti visus failus ({{count}}, .zip)",
"orDownloadOne": "arba atsisiųsti vieną failą",
"tags": "Žymos",
"noTags": "Kol kas nėra žymų.",
"previousImage": "Ankstesnis paveikslėlis",
@@ -14,6 +14,7 @@ import CircularProgress from "@mui/material/CircularProgress";
import DownloadIcon from "@mui/icons-material/Download";
import type { Plate } from "../../api/prints";
import PlateThumbnail from "../../components/media/PlateThumbnail";
import { fileRowText } from "./fileRowText";
type Props = {
open: boolean;
@@ -24,7 +25,7 @@ type Props = {
downloadPlate: (plate: Plate) => void;
};
/** The "which plate?" picker shown when a multi-plate model's download is triggered -- see
/** The "which file?" picker shown when a multi-file model's download is triggered -- see
* useDownloadPrint. Shared by ModelActionsMenu's "Download" menu item and the detail page's
* "Download model files" button. */
export default function DownloadPickerDialog({ open, onClose, downloading, sortedPlates, downloadAllZip, downloadPlate }: Props) {
@@ -45,13 +46,19 @@ export default function DownloadPickerDialog({ open, onClose, downloading, sorte
<Divider>{t("models:detail.orDownloadOne")}</Divider>
<List disablePadding>
{sortedPlates.map((plate, idx) => (
<ListItemButton key={plate.id} onClick={() => downloadPlate(plate)} disabled={downloading} sx={{ borderRadius: 1 }}>
<ListItemButton
key={plate.id}
onClick={() => downloadPlate(plate)}
disabled={downloading}
title={plate.filename}
sx={{ borderRadius: 1 }}
>
<ListItemIcon sx={{ minWidth: 60 }}>
<PlateThumbnail plate={plate} size={48} />
</ListItemIcon>
<ListItemText
primary={t("models:detail.plateLabel", { n: idx + 1 })}
secondary={plate.filename}
{...fileRowText(t, plate.filename, idx)}
primaryTypographyProps={{ noWrap: true }}
secondaryTypographyProps={{ noWrap: true }}
/>
</ListItemButton>
@@ -19,6 +19,7 @@ import PlateThumbnail from "../../components/media/PlateThumbnail";
import ModelViewer, { type CameraView, type ModelViewerHandle, type RenderStyle } from "../../components/media/ModelViewer";
import type { PlateSummary } from "../../utils/bambuThreeMf";
import PreviewToolbar, { DEFAULT_PREVIEW_COLOR } from "./PreviewToolbar";
import { fileRowText } from "./fileRowText";
// A neutral, theme-independent canvas -- this is a fixed "product shot" style preview, not part
// of the app's light/dark chrome, so it stays the same regardless of the viewer's theme.
@@ -66,8 +67,8 @@ export default function Model3DPreviewModal({ print, onClose }: Props) {
viewerRef.current?.setCameraView(view);
};
// A lone file that is itself a multi-plate project would otherwise show one "Plate 1" file row
// above its real plates -- redundant, and reads as if it were one of them.
// A lone file that is itself a multi-plate project would otherwise show its one file row above
// its real plates -- redundant, since there's no other file to switch to.
const showFileRows = sortedPlates.length > 1 || internalPlates.length === 0;
const handlePlatesDetected = (plates: PlateSummary[], getThumbnail: (index: number) => Promise<string | null>) => {
@@ -131,19 +132,22 @@ export default function Model3DPreviewModal({ print, onClose }: Props) {
{showFileRows && (
<List disablePadding>
{sortedPlates.map((plate, idx) => (
// Named by file, not "Plate N": each row is a separate file (an STL, or e.g. one
// MakerWorld print profile's 3MF) -- "plate" is kept for the build plates inside a
// 3MF, listed below the divider.
<ListItemButton
key={plate.id}
selected={plate.id === activePlate?.id}
onClick={() => selectPlate(plate.id)}
title={plate.filename}
sx={{ borderRadius: 1, mb: 0.5 }}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<PlateThumbnail plate={plate} />
</ListItemIcon>
<ListItemText
primary={t("models:detail.plateLabel", { n: idx + 1 })}
secondary={plate.filename}
primaryTypographyProps={{ variant: "body2" }}
{...fileRowText(t, plate.filename, idx)}
primaryTypographyProps={{ variant: "body2", noWrap: true }}
secondaryTypographyProps={{ variant: "caption", noWrap: true }}
/>
</ListItemButton>
@@ -0,0 +1,12 @@
import type { TFunction } from "i18next";
import { extOf, stemOf } from "../../utils/fileExtensions";
/** How one of a model's files is labeled in a list (3D preview, download picker): its name, with
* its position and type as the secondary line -- e.g. "Body" / "File 2 · STL". */
export function fileRowText(t: TFunction, filename: string, index: number): { primary: string; secondary: string } {
const type = extOf(filename).toUpperCase();
return {
primary: stemOf(filename),
secondary: type ? t("models:detail.fileMeta", { n: index + 1, type }) : t("models:detail.fileLabel", { n: index + 1 }),
};
}
+7
View File
@@ -3,3 +3,10 @@ export function extOf(name: string): string {
const match = /\.([^.]+)$/.exec(name || "");
return (match?.[1] || "").toLowerCase();
}
/** The filename without its extension, e.g. "Body" for "Body.stl" -- a file's display name where
* the type is shown separately. A dotfile-style name with nothing before the dot is kept whole. */
export function stemOf(name: string): string {
const stem = (name || "").replace(/\.[^.]+$/, "");
return stem || name || "";
}