Merge branch '0.2.1b' into refactor-all-pages

This commit is contained in:
MartinNYHC
2026-02-18 16:54:55 +01:00
committed by GitHub
27 changed files with 4219 additions and 464 deletions
+1 -1
View File
@@ -177,7 +177,7 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr
<p className="text-xs text-bambu-gray">
{spool.color_name || ''}
{spool.label_weight ? ` - ${spool.label_weight}g` : ''}
{spool.weight_used > 0 ? ` (${Math.round(spool.weight_used)}g used)` : ''}
{spool.label_weight ? ` (${Math.max(0, Math.round(spool.label_weight - spool.weight_used))}g ${t('ams.remainingUnit')})` : ''}
</p>
</div>
{selectedSpoolId === spool.id && (
@@ -24,7 +24,7 @@ interface SpoolmanConfig {
interface InventoryConfig {
onAssignSpool?: () => void;
onUnassignSpool?: () => void;
assignedSpool?: { id: number; material: string; brand: string | null; color_name: string | null } | null;
assignedSpool?: { id: number; material: string; brand: string | null; color_name: string | null; remainingWeightGrams?: number | null } | null;
}
interface ConfigureSlotConfig {
@@ -148,6 +148,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
};
const colorHex = data.colorHex ? `#${data.colorHex.replace('#', '')}` : null;
const assignedRemainingWeight = inventory?.assignedSpool?.remainingWeightGrams ?? null;
return (
<div
@@ -238,7 +239,10 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
{t('ams.fill')}
</span>
<span className="text-xs text-white font-semibold flex items-center gap-1">
{data.fillLevel !== null ? `${data.fillLevel}%` : '—'}
<span>{data.fillLevel !== null ? `${data.fillLevel}%` : '—'}</span>
{assignedRemainingWeight !== null && data.fillLevel !== null && (
<span className="text-[9px] text-bambu-gray font-normal">• {assignedRemainingWeight}g</span>
)}
{data.fillSource === 'spoolman' && data.fillLevel !== null && (
<span className="text-[9px] text-bambu-gray font-normal">{t('spoolman.fillSourceLabel')}</span>
)}
+20 -8
View File
@@ -263,17 +263,29 @@ export function ModelViewerModal({ archiveId, libraryFileId, title, fileType, on
const canOpenInSlicer = isLibrary ? (fileType || '').toLowerCase() === '3mf' : true;
const handleOpenInSlicer = () => {
const handleOpenInSlicer = async () => {
if (!canOpenInSlicer) return;
// URL must include .3mf filename for Bambu Studio to recognize the format
const filename = title || 'model';
if (isLibrary) {
const downloadUrl = `${window.location.origin}${api.getLibraryFileDownloadUrl(libraryFileId!)}`;
openInSlicer(downloadUrl, preferredSlicer);
return;
try {
if (isLibrary) {
const { token } = await api.createLibrarySlicerToken(libraryFileId!);
const path = api.getLibrarySlicerDownloadUrl(libraryFileId!, token, filename);
openInSlicer(`${window.location.origin}${path}`, preferredSlicer);
} else {
const { token } = await api.createArchiveSlicerToken(archiveId!);
const path = api.getArchiveSlicerDownloadUrl(archiveId!, token, filename);
openInSlicer(`${window.location.origin}${path}`, preferredSlicer);
}
} catch {
// Fallback to direct URL (works when auth is disabled)
if (isLibrary) {
const downloadUrl = `${window.location.origin}${api.getLibraryFileDownloadUrl(libraryFileId!)}`;
openInSlicer(downloadUrl, preferredSlicer);
} else {
const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archiveId!, filename)}`;
openInSlicer(downloadUrl, preferredSlicer);
}
}
const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archiveId!, filename)}`;
openInSlicer(downloadUrl, preferredSlicer);
};
return (
+7 -1
View File
@@ -320,7 +320,7 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration
if (!validation.isValid) {
setErrors(validation.errors);
// Switch to filament tab if there are errors there
if (validation.errors.slicer_filament || validation.errors.material) {
if (validation.errors.slicer_filament || validation.errors.material || validation.errors.brand || validation.errors.subtype) {
setActiveTab('filament');
}
return;
@@ -438,6 +438,12 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration
{errors.material && (
<p className="mt-1 text-xs text-red-400">{errors.material}</p>
)}
{errors.brand && (
<p className="mt-1 text-xs text-red-400">{errors.brand}</p>
)}
{errors.subtype && (
<p className="mt-1 text-xs text-red-400">{errors.subtype}</p>
)}
</div>
{/* Color Section */}
@@ -161,7 +161,7 @@ export function FilamentSection({
{/* Brand (dropdown with search) */}
<div>
<label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.brand')}</label>
<label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.brand')} *</label>
<div className="relative" ref={brandRef}>
<input
type="text"
@@ -221,7 +221,7 @@ export function FilamentSection({
{/* Variant / Subtype */}
<div>
<label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.subtype')}</label>
<label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.subtype')} *</label>
<div className="relative" ref={subtypeRef}>
<input
type="text"
@@ -117,6 +117,14 @@ export function validateForm(formData: SpoolFormData): ValidationResult {
errors.material = 'Material is required';
}
if (!formData.brand) {
errors.brand = 'Brand is required';
}
if (!formData.subtype) {
errors.subtype = 'Subtype is required';
}
return {
isValid: Object.keys(errors).length === 0,
errors,