mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-07 06:31:22 +02:00
home_flag bit 18 was incorrectly interpreted as "wired/ethernet connection", causing the ethernet badge to always show — even on printers without an ethernet port (e.g. A1, P1S). This hid the WiFi signal indicator entirely. Removed the wired_network field and ethernet badge UI. The WiFi signal badge now shows correctly whenever the printer reports signal strength.
247 lines
7.2 KiB
TypeScript
247 lines
7.2 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { X, Copy, Check, Signal } from 'lucide-react';
|
|
import { Card, CardContent } from './Card';
|
|
import { formatDateOnly } from '../utils/date';
|
|
import { getPrinterImage, getWifiStrength } from '../utils/printer';
|
|
import type { Printer, PrinterStatus } from '../api/client';
|
|
|
|
interface PrinterInfoModalProps {
|
|
printer: Printer;
|
|
status?: PrinterStatus;
|
|
totalPrintHours?: number;
|
|
onClose: () => void;
|
|
}
|
|
|
|
function CopyButton({ value }: { value: string }) {
|
|
const { t } = useTranslation();
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
const handleCopy = async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(value);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
} catch {
|
|
// Clipboard may not be available in non-secure contexts
|
|
}
|
|
};
|
|
|
|
return (
|
|
<button
|
|
onClick={handleCopy}
|
|
className="ml-2 p-1 rounded hover:bg-bambu-dark-tertiary text-bambu-gray hover:text-white transition-colors"
|
|
title={copied ? t('printers.copied') : t('printers.copyToClipboard')}
|
|
>
|
|
{copied ? <Check className="w-3.5 h-3.5 text-bambu-green" /> : <Copy className="w-3.5 h-3.5" />}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
export function PrinterInfoModal({ printer, status, totalPrintHours, onClose }: PrinterInfoModalProps) {
|
|
const { t } = useTranslation();
|
|
|
|
useEffect(() => {
|
|
const handleKey = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
window.addEventListener('keydown', handleKey);
|
|
return () => window.removeEventListener('keydown', handleKey);
|
|
}, [onClose]);
|
|
|
|
const rows: { label: string; value: React.ReactNode }[] = [];
|
|
|
|
// Model
|
|
rows.push({
|
|
label: t('printers.model'),
|
|
value: printer.model ?? '—',
|
|
});
|
|
|
|
// Connection Status
|
|
rows.push({
|
|
label: t('common.status'),
|
|
value: (
|
|
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium ${
|
|
status?.connected
|
|
? 'bg-bambu-green/20 text-bambu-green'
|
|
: 'bg-red-500/20 text-red-400'
|
|
}`}>
|
|
<span className={`w-1.5 h-1.5 rounded-full ${status?.connected ? 'bg-bambu-green' : 'bg-red-400'}`} />
|
|
{status?.connected ? t('printers.status.available') : t('printers.status.offline')}
|
|
</span>
|
|
),
|
|
});
|
|
|
|
// State
|
|
if (status?.state) {
|
|
const stateMap: Record<string, string> = {
|
|
IDLE: 'printers.status.idle',
|
|
RUNNING: 'printers.status.printing',
|
|
PAUSE: 'printers.status.paused',
|
|
FINISH: 'printers.status.finished',
|
|
FAILED: 'printers.status.error',
|
|
};
|
|
rows.push({
|
|
label: t('printers.state'),
|
|
value: t(stateMap[status.state] ?? 'printers.status.unknown'),
|
|
});
|
|
}
|
|
|
|
// IP Address
|
|
rows.push({
|
|
label: t('printers.ipAddress'),
|
|
value: (
|
|
<span className="flex items-center">
|
|
<span className="font-mono">{printer.ip_address}</span>
|
|
<CopyButton value={printer.ip_address} />
|
|
</span>
|
|
),
|
|
});
|
|
|
|
// Serial Number
|
|
rows.push({
|
|
label: t('printers.serialNumber'),
|
|
value: (
|
|
<span className="flex items-center">
|
|
<span className="font-mono truncate">{printer.serial_number}</span>
|
|
<CopyButton value={printer.serial_number} />
|
|
</span>
|
|
),
|
|
});
|
|
|
|
// WiFi signal
|
|
if (status?.wifi_signal != null) {
|
|
const wifi = getWifiStrength(status.wifi_signal);
|
|
rows.push({
|
|
label: t('printers.wifiSignalLabel'),
|
|
value: (
|
|
<span className="flex items-center gap-2">
|
|
<Signal className={`w-4 h-4 ${wifi.color}`} />
|
|
<span className={wifi.color}>{t(wifi.labelKey)}</span>
|
|
<span className="text-bambu-gray text-xs">({status.wifi_signal} dBm)</span>
|
|
</span>
|
|
),
|
|
});
|
|
}
|
|
|
|
// Firmware
|
|
rows.push({
|
|
label: t('printers.firmware'),
|
|
value: status?.firmware_version ?? '—',
|
|
});
|
|
|
|
// Developer Mode
|
|
if (status?.developer_mode != null) {
|
|
rows.push({
|
|
label: t('printers.developerMode'),
|
|
value: (
|
|
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
|
|
status.developer_mode
|
|
? 'bg-bambu-green/20 text-bambu-green'
|
|
: 'bg-bambu-dark-tertiary text-bambu-gray'
|
|
}`}>
|
|
{status.developer_mode ? t('printers.enabled') : t('printers.disabled')}
|
|
</span>
|
|
),
|
|
});
|
|
}
|
|
|
|
// Nozzle Count
|
|
rows.push({
|
|
label: t('printers.nozzleCount'),
|
|
value: printer.nozzle_count,
|
|
});
|
|
|
|
// SD Card
|
|
if (status?.sdcard != null) {
|
|
rows.push({
|
|
label: t('printers.sdCard'),
|
|
value: (
|
|
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
|
|
status.sdcard
|
|
? 'bg-bambu-green/20 text-bambu-green'
|
|
: 'bg-bambu-dark-tertiary text-bambu-gray'
|
|
}`}>
|
|
{status.sdcard ? t('printers.inserted') : t('printers.notInserted')}
|
|
</span>
|
|
),
|
|
});
|
|
}
|
|
|
|
// Auto-Archive
|
|
rows.push({
|
|
label: t('printers.autoArchive'),
|
|
value: (
|
|
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
|
|
printer.auto_archive
|
|
? 'bg-bambu-green/20 text-bambu-green'
|
|
: 'bg-bambu-dark-tertiary text-bambu-gray'
|
|
}`}>
|
|
{printer.auto_archive ? t('printers.enabled') : t('printers.disabled')}
|
|
</span>
|
|
),
|
|
});
|
|
|
|
// Total Print Hours
|
|
if (totalPrintHours != null && totalPrintHours > 0) {
|
|
rows.push({
|
|
label: t('printers.totalPrintHours'),
|
|
value: `${Math.round(totalPrintHours)}h`,
|
|
});
|
|
}
|
|
|
|
// Location
|
|
if (printer.location) {
|
|
rows.push({
|
|
label: t('printers.sort.location'),
|
|
value: printer.location,
|
|
});
|
|
}
|
|
|
|
// Added date
|
|
rows.push({
|
|
label: t('printers.addedOn'),
|
|
value: formatDateOnly(printer.created_at),
|
|
});
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
onClick={onClose}
|
|
>
|
|
<Card className="w-full max-w-md" onClick={(e: React.MouseEvent) => e.stopPropagation()}>
|
|
<CardContent>
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-semibold text-white">
|
|
{printer.name}
|
|
</h2>
|
|
<button onClick={onClose} className="p-1 hover:bg-bambu-dark rounded flex-shrink-0">
|
|
<X className="w-5 h-5 text-bambu-gray" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Printer Image */}
|
|
<div className="flex justify-center mb-4">
|
|
<img
|
|
src={getPrinterImage(printer.model)}
|
|
alt={printer.model ?? printer.name}
|
|
className="h-24 object-contain"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-0">
|
|
{rows.map((row, i) => (
|
|
<div key={i} className="flex items-center justify-between gap-4 py-2.5 border-b border-bambu-dark-tertiary last:border-0">
|
|
<span className="text-sm text-bambu-gray whitespace-nowrap">{row.label}</span>
|
|
<span className="text-sm text-white text-right">{row.value}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|