Compare commits
No commits in common. "6817ddb491a5f402b76712f358b231bb63d9b06b" and "e0ac2549c6f621b77dbe796d7535bfd66e80ee48" have entirely different histories.
6817ddb491
...
e0ac2549c6
|
|
@ -94,7 +94,8 @@ export default function DeviceDetailOverlay({
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDevicePath = (): string[] => {
|
const getDevicePath = (): string[] => {
|
||||||
return ['iec', 'devices', device.number];
|
const [type, num] = device.id.split('-');
|
||||||
|
return ['iec', 'devices', type, num];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDeviceData = () => {
|
const getDeviceData = () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Printer, HardDrive, Network, Box, ChevronRight, RefreshCw, FolderOpen, Computer, Cable, CassetteTape, Plug } from 'lucide-react';
|
import { Printer, HardDrive, Network, Box, ChevronRight, RefreshCw, FolderOpen } from 'lucide-react';
|
||||||
import DeviceDetailOverlay from './DeviceDetailOverlay';
|
import DeviceDetailOverlay from './DeviceDetailOverlay';
|
||||||
import MediaBrowser from './MediaBrowser';
|
import MediaBrowser from './MediaBrowser';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
@ -45,24 +45,77 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
|
||||||
|
|
||||||
const devices: Device[] = [];
|
const devices: Device[] = [];
|
||||||
|
|
||||||
if (config.iec?.devices) {
|
// Printer devices
|
||||||
Object.entries(config.iec.devices).forEach(([num, device]: [string, any]) => {
|
if (config.iec?.devices?.printer) {
|
||||||
const type = device.type as Device['type'];
|
Object.entries(config.iec.devices.printer).forEach(([num, device]: [string, any]) => {
|
||||||
const name = device.name || (
|
|
||||||
type === 'drive' ? `Drive ${num}` :
|
|
||||||
type === 'network' ? `Network ${num}` :
|
|
||||||
type === 'meatloaf' ? `Meatloaf ${num}` :
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
devices.push({
|
devices.push({
|
||||||
id: `${type}-${num}`,
|
id: `printer-${num}`,
|
||||||
number: num,
|
number: num,
|
||||||
type,
|
type: 'printer',
|
||||||
name,
|
name: device.name,
|
||||||
|
enabled: device.enabled
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drive devices
|
||||||
|
if (config.iec?.devices?.drive) {
|
||||||
|
Object.entries(config.iec.devices.drive).forEach(([key, value]: [string, any]) => {
|
||||||
|
if (key !== 'vdrive' && key !== 'rom') {
|
||||||
|
devices.push({
|
||||||
|
id: `drive-${key}`,
|
||||||
|
number: key,
|
||||||
|
type: 'drive',
|
||||||
|
name: `Drive ${key}`,
|
||||||
|
enabled: value.enabled,
|
||||||
|
base_url: value.base_url,
|
||||||
|
url: value.url,
|
||||||
|
mode: value.mode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network devices
|
||||||
|
if (config.iec?.devices?.network) {
|
||||||
|
Object.entries(config.iec.devices.network).forEach(([num, device]: [string, any]) => {
|
||||||
|
devices.push({
|
||||||
|
id: `network-${num}`,
|
||||||
|
number: num,
|
||||||
|
type: 'network',
|
||||||
|
name: `Network ${num}`,
|
||||||
|
enabled: device.enabled,
|
||||||
|
base_url: device.base_url,
|
||||||
|
url: device.url
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other devices
|
||||||
|
if (config.iec?.devices?.other) {
|
||||||
|
Object.entries(config.iec.devices.other).forEach(([num, device]: [string, any]) => {
|
||||||
|
devices.push({
|
||||||
|
id: `other-${num}`,
|
||||||
|
number: num,
|
||||||
|
type: 'other',
|
||||||
|
name: device.name,
|
||||||
|
enabled: device.enabled
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meatloaf devices
|
||||||
|
if (config.iec?.devices?.meatloaf) {
|
||||||
|
Object.entries(config.iec.devices.meatloaf).forEach(([num, device]: [string, any]) => {
|
||||||
|
devices.push({
|
||||||
|
id: `meatloaf-${num}`,
|
||||||
|
number: num,
|
||||||
|
type: 'meatloaf',
|
||||||
|
name: `Meatloaf ${num}`,
|
||||||
enabled: device.enabled,
|
enabled: device.enabled,
|
||||||
base_url: device.base_url,
|
base_url: device.base_url,
|
||||||
url: device.url,
|
url: device.url,
|
||||||
mode: device.mode,
|
mode: device.mode
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -84,8 +137,6 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
|
||||||
return <HardDrive className="w-5 h-5" />;
|
return <HardDrive className="w-5 h-5" />;
|
||||||
case 'network':
|
case 'network':
|
||||||
return <Network className="w-5 h-5" />;
|
return <Network className="w-5 h-5" />;
|
||||||
case 'meatloaf':
|
|
||||||
return <Computer className="w-5 h-5" />;
|
|
||||||
default:
|
default:
|
||||||
return <Box className="w-5 h-5" />;
|
return <Box className="w-5 h-5" />;
|
||||||
}
|
}
|
||||||
|
|
@ -126,16 +177,27 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
|
||||||
|
|
||||||
const toggleDeviceEnabled = (device: Device, e: React.MouseEvent) => {
|
const toggleDeviceEnabled = (device: Device, e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const [type, num] = device.id.split('-');
|
||||||
|
const path = ['iec', 'devices', type, num, 'enabled'];
|
||||||
|
|
||||||
const newConfig = JSON.parse(JSON.stringify(config));
|
const newConfig = JSON.parse(JSON.stringify(config));
|
||||||
newConfig.iec.devices[device.number].enabled = device.enabled ? 0 : 1;
|
let current = newConfig;
|
||||||
|
|
||||||
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
|
current = current[path[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
current[path[path.length - 1]] = device.enabled ? 0 : 1;
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
|
|
||||||
toast.success(`Device #${device.number} ${device.enabled ? 'disabled' : 'enabled'}`);
|
toast.success(`Device #${device.number} ${device.enabled ? 'disabled' : 'enabled'}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
{/* Host Settings section moved from GeneralPage */}
|
{/* Host Settings section moved from GeneralPage */}
|
||||||
<h2 className="text-sm text-neutral-500 mb-2 flex items-center gap-2"><Computer className="w-4 h-4" /> Host Settings</h2>
|
<h2 className="text-sm text-neutral-500 mb-2">Host Settings</h2>
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200 mb-6">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200 mb-6">
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<label className="text-sm text-neutral-500 block mb-2">Model</label>
|
<label className="text-sm text-neutral-500 block mb-2">Model</label>
|
||||||
|
|
@ -195,7 +257,7 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h2 className="text-sm text-neutral-500 flex items-center gap-2"><Cable className="w-4 h-4" /> IEC Devices</h2>
|
<h2 className="text-sm text-neutral-500">IEC Devices</h2>
|
||||||
<button
|
<button
|
||||||
onClick={rescanBus}
|
onClick={rescanBus}
|
||||||
disabled={isScanning}
|
disabled={isScanning}
|
||||||
|
|
@ -267,7 +329,7 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
|
||||||
|
|
||||||
|
|
||||||
{/* ── Cassette ── */}
|
{/* ── Cassette ── */}
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><CassetteTape className="w-4 h-4" /> Cassette</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">Cassette</h2>
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
<div className="p-4 flex items-center justify-between">
|
<div className="p-4 flex items-center justify-between">
|
||||||
<label className="text-sm text-neutral-500">Enabled</label>
|
<label className="text-sm text-neutral-500">Enabled</label>
|
||||||
|
|
@ -288,33 +350,12 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
|
||||||
</div> */}
|
</div> */}
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<label className="text-sm text-neutral-500 block mb-2">URL</label>
|
<label className="text-sm text-neutral-500 block mb-2">URL</label>
|
||||||
<div className="flex gap-2">
|
<input type="text" value={cassette.url || ''} onChange={(e) => updateSetting(['cassette', 'url'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={cassette.url || ''}
|
|
||||||
onChange={(e) => updateSetting(['cassette', 'url'], e.target.value)}
|
|
||||||
className="flex-1 px-3 py-2 border border-neutral-300 rounded-lg"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowCassetteUrlBrowser(true)}
|
|
||||||
className="px-3 py-2 border border-neutral-300 rounded-lg bg-neutral-50 hover:bg-neutral-100"
|
|
||||||
>
|
|
||||||
<FolderOpen className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showCassetteUrlBrowser && (
|
|
||||||
<MediaBrowser
|
|
||||||
currentPath={cassette.url || '/'}
|
|
||||||
onSelect={(p) => updateSetting(['cassette', 'url'], p)}
|
|
||||||
onClose={() => setShowCassetteUrlBrowser(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ── Hardware ── */}
|
{/* ── Hardware ── */}
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Plug className="w-4 h-4" /> User Port</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">User Port</h2>
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
<div className="p-4 flex items-center justify-between">
|
<div className="p-4 flex items-center justify-between">
|
||||||
<label className="text-sm text-neutral-500">User Port Enabled</label>
|
<label className="text-sm text-neutral-500">User Port Enabled</label>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Settings } from 'lucide-react';
|
|
||||||
import { getFileContents } from '../webdav';
|
import { getFileContents } from '../webdav';
|
||||||
|
|
||||||
interface GeneralPageProps {
|
interface GeneralPageProps {
|
||||||
|
|
@ -42,20 +41,9 @@ export default function GeneralPage({ config, setConfig }: GeneralPageProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 space-y-4">
|
<div className="p-4 space-y-4">
|
||||||
<h2 className="text-sm text-neutral-500 flex items-center gap-2"><Settings className="w-4 h-4" /> General Settings</h2>
|
<h2 className="text-sm text-neutral-500">General Settings</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
|
|
||||||
<div className="p-4">
|
|
||||||
<label className="text-sm text-neutral-500 block mb-2">Device Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={general.devicename || ''}
|
|
||||||
onChange={(e) => updateSetting(['general', 'devicename'], e.target.value)}
|
|
||||||
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<label className="text-sm text-neutral-500 block mb-2">Appearance</label>
|
<label className="text-sm text-neutral-500 block mb-2">Appearance</label>
|
||||||
<select
|
<select
|
||||||
|
|
@ -120,6 +108,16 @@ export default function GeneralPage({ config, setConfig }: GeneralPageProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4">
|
||||||
|
<label className="text-sm text-neutral-500 block mb-2">Device Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={general.devicename || ''}
|
||||||
|
onChange={(e) => updateSetting(['general', 'devicename'], e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="p-4 flex items-center justify-between">
|
<div className="p-4 flex items-center justify-between">
|
||||||
<label className="text-sm text-neutral-500">Rotation Sounds</label>
|
<label className="text-sm text-neutral-500">Rotation Sounds</label>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Cable, Code2, Cpu, FolderOpen, Link, List, Zap } from 'lucide-react';
|
import { FolderOpen } from 'lucide-react';
|
||||||
import MediaBrowser from './MediaBrowser';
|
import MediaBrowser from './MediaBrowser';
|
||||||
|
|
||||||
interface IECPageProps {
|
interface IECPageProps {
|
||||||
|
|
@ -26,7 +26,7 @@ export default function IECPage({ config, setConfig }: IECPageProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 space-y-4">
|
<div className="p-4 space-y-4">
|
||||||
<h2 className="text-sm text-neutral-500 flex items-center gap-2"><Cable className="w-4 h-4" /> IEC Settings</h2>
|
<h2 className="text-sm text-neutral-500">IEC Settings</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
<div className="p-4 flex items-center justify-between">
|
<div className="p-4 flex items-center justify-between">
|
||||||
|
|
@ -108,7 +108,7 @@ export default function IECPage({ config, setConfig }: IECPageProps) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Cpu className="w-4 h-4" /> Drive ROMs</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">Drive ROM</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
<div className="p-4 flex items-center justify-between">
|
<div className="p-4 flex items-center justify-between">
|
||||||
|
|
@ -169,7 +169,7 @@ export default function IECPage({ config, setConfig }: IECPageProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><List className="w-4 h-4" /> Directory Settings</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">Directory Settings</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
{Object.entries(iec.directory || {}).map(([key, value]) => (
|
{Object.entries(iec.directory || {}).map(([key, value]) => (
|
||||||
|
|
@ -202,7 +202,7 @@ export default function IECPage({ config, setConfig }: IECPageProps) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Zap className="w-4 h-4" /> Hardware Fastloaders</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">Hardware Fastloaders</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
<div className="p-3 bg-neutral-50">
|
<div className="p-3 bg-neutral-50">
|
||||||
|
|
@ -252,7 +252,7 @@ export default function IECPage({ config, setConfig }: IECPageProps) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Code2 className="w-4 h-4" /> Software Fastloaders</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">Software Fastloaders</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
{Object.entries(iec.fastloaders?.software || {}).map(([key, value]) => (
|
{Object.entries(iec.fastloaders?.software || {}).map(([key, value]) => (
|
||||||
|
|
@ -276,7 +276,7 @@ export default function IECPage({ config, setConfig }: IECPageProps) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Link className="w-4 h-4" /> Chainloaders</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">Chainloaders</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
{Object.entries(iec.chainloaders || {}).map(([key, value]) => (
|
{Object.entries(iec.chainloaders || {}).map(([key, value]) => (
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export const IMAGE_EXTS = new Set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp',
|
||||||
export const AUDIO_EXTS = new Set(['sid', 'psid', 'rsid', 'mus', 'vgm']);
|
export const AUDIO_EXTS = new Set(['sid', 'psid', 'rsid', 'mus', 'vgm']);
|
||||||
export const ROM_EXTS = new Set(['bin', 'rom', 'crt']);
|
export const ROM_EXTS = new Set(['bin', 'rom', 'crt']);
|
||||||
export const TAPE_EXTS = new Set(['tap', 'htap', 't64', 'tcrt']);
|
export const TAPE_EXTS = new Set(['tap', 'htap', 't64', 'tcrt']);
|
||||||
export const DISK_EXTS = new Set(['c64', 'd41', 'd64', 'd67', 'd71', 'd80', 'd81', 'd82', 'd88', 'f64', 'g41', 'g64', 'g71', 'g81', 'm2i', 'nbz', 'nib', 'p64', 'p71', 'p81', 'scp', 'x64']);
|
export const DISK_EXTS = new Set(['d41', 'd64', 'd71', 'd80', 'd81', 'd82', 'g64', 'g71', 'g81', 'p64', 'p71', 'p81', 'nib']);
|
||||||
export const DISC_EXTS = new Set(['iso', 'img', 'cue']);
|
export const DISC_EXTS = new Set(['iso', 'img', 'cue']);
|
||||||
export const HD_EXTS = new Set(['d1m', 'd2m', 'd4m', 'd90', 'dhd', 'hdd', 'bbt', 'd8b', 'dfi']);
|
export const HD_EXTS = new Set(['d1m', 'd2m', 'd4m', 'd90', 'dhd', 'hdd', 'bbt', 'd8b', 'dfi']);
|
||||||
export const ARCHIVE_EXTS = new Set(['zip', '7z', 'tar', 'gz', 'bz2', 'xz', 'rar', 'arj', 'lzh', 'ace', 'z', 'lha', 'cab', 'lbr', 'arc', 'ark', 'lnx']);
|
export const ARCHIVE_EXTS = new Set(['zip', '7z', 'tar', 'gz', 'bz2', 'xz', 'rar', 'arj', 'lzh', 'ace', 'z', 'lha', 'cab', 'lbr', 'arc', 'ark', 'lnx']);
|
||||||
|
|
|
||||||
|
|
@ -738,8 +738,9 @@ export default function MediaManager({ initialPath = '/', rootPath, title, confi
|
||||||
const newConfig = JSON.parse(JSON.stringify(config));
|
const newConfig = JSON.parse(JSON.stringify(config));
|
||||||
if (!newConfig.iec) newConfig.iec = {};
|
if (!newConfig.iec) newConfig.iec = {};
|
||||||
if (!newConfig.iec.devices) newConfig.iec.devices = {};
|
if (!newConfig.iec.devices) newConfig.iec.devices = {};
|
||||||
if (!newConfig.iec.devices[key]) newConfig.iec.devices[key] = { type: deviceType };
|
if (!newConfig.iec.devices[deviceType]) newConfig.iec.devices[deviceType] = {};
|
||||||
const dev = newConfig.iec.devices[key];
|
if (!newConfig.iec.devices[deviceType][key]) newConfig.iec.devices[deviceType][key] = {};
|
||||||
|
const dev = newConfig.iec.devices[deviceType][key];
|
||||||
|
|
||||||
if (mountEntry.name.toLowerCase().endsWith('.lst')) {
|
if (mountEntry.name.toLowerCase().endsWith('.lst')) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -1304,12 +1305,10 @@ export default function MediaManager({ initialPath = '/', rootPath, title, confi
|
||||||
<DialogDescription className="truncate">{mountEntry?.name}</DialogDescription>
|
<DialogDescription className="truncate">{mountEntry?.name}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{(() => {
|
{(() => {
|
||||||
const allDevices = Object.entries(config?.iec?.devices ?? {});
|
const drives = Object.entries(config?.iec?.devices?.drive ?? {})
|
||||||
const drives = allDevices
|
.filter(([k]) => k !== 'vdrive' && k !== 'rom')
|
||||||
.filter(([, v]: [string, any]) => (v as any)?.type === 'drive')
|
|
||||||
.map(([k, v]: [string, any]) => ({ type: 'drive' as const, key: k, base_url: v?.base_url as string | undefined, url: v?.url as string | undefined, enabled: !!v?.enabled }));
|
.map(([k, v]: [string, any]) => ({ type: 'drive' as const, key: k, base_url: v?.base_url as string | undefined, url: v?.url as string | undefined, enabled: !!v?.enabled }));
|
||||||
const meatloafs = allDevices
|
const meatloafs = Object.entries(config?.iec?.devices?.meatloaf ?? {})
|
||||||
.filter(([, v]: [string, any]) => (v as any)?.type === 'meatloaf')
|
|
||||||
.map(([k, v]: [string, any]) => ({ type: 'meatloaf' as const, key: k, base_url: v?.base_url as string | undefined, url: v?.url as string | undefined, enabled: !!v?.enabled }));
|
.map(([k, v]: [string, any]) => ({ type: 'meatloaf' as const, key: k, base_url: v?.base_url as string | undefined, url: v?.url as string | undefined, enabled: !!v?.enabled }));
|
||||||
const devices = [...drives, ...meatloafs];
|
const devices = [...drives, ...meatloafs];
|
||||||
if (!devices.length)
|
if (!devices.length)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Bluetooth, Globe, Wifi, Trash2, Scan } from 'lucide-react';
|
import { Wifi, Trash2, Scan } from 'lucide-react';
|
||||||
import WiFiScanOverlay from './WiFiScanOverlay';
|
import WiFiScanOverlay from './WiFiScanOverlay';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useWs } from '../ws';
|
import { useWs } from '../ws';
|
||||||
|
|
@ -51,7 +51,7 @@ export default function NetworkPage({ config, setConfig }: NetworkPageProps) {
|
||||||
return (
|
return (
|
||||||
<div className="p-4 space-y-4">
|
<div className="p-4 space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-sm text-neutral-500 flex items-center gap-2"><Wifi className="w-4 h-4" /> Known WiFi Networks</h2>
|
<h2 className="text-sm text-neutral-500">Known WiFi Networks</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => { wsSend('scan'); setShowWiFiScan(true); }}
|
onClick={() => { wsSend('scan'); setShowWiFiScan(true); }}
|
||||||
className="flex items-center gap-1 px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg"
|
className="flex items-center gap-1 px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg"
|
||||||
|
|
@ -110,7 +110,7 @@ export default function NetworkPage({ config, setConfig }: NetworkPageProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Globe className="w-4 h-4" /> Network</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">Network</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
|
|
@ -214,9 +214,18 @@ export default function NetworkPage({ config, setConfig }: NetworkPageProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Bluetooth className="w-4 h-4" /> Bluetooth</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">Bluetooth</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
|
<div className="p-4">
|
||||||
|
<label className="text-sm text-neutral-500 block mb-2">Device Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={bluetooth.devicename || ''}
|
||||||
|
onChange={(e) => updateSetting(['bluetooth', 'devicename'], e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="p-4 flex items-center justify-between">
|
<div className="p-4 flex items-center justify-between">
|
||||||
<label className="text-sm text-neutral-500">Enabled</label>
|
<label className="text-sm text-neutral-500">Enabled</label>
|
||||||
|
|
@ -233,17 +242,6 @@ export default function NetworkPage({ config, setConfig }: NetworkPageProps) {
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4">
|
|
||||||
<label className="text-sm text-neutral-500 block mb-2">Device Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={bluetooth.devicename || ''}
|
|
||||||
onChange={(e) => updateSetting(['bluetooth', 'devicename'], e.target.value)}
|
|
||||||
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<label className="text-sm text-neutral-500 block mb-2">Baud Rate</label>
|
<label className="text-sm text-neutral-500 block mb-2">Baud Rate</label>
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,8 @@ export default function SearchOverlay({ config, setConfig, onClose }: SearchOver
|
||||||
|
|
||||||
const handleMount = (deviceNum: string, result: SearchResult) => {
|
const handleMount = (deviceNum: string, result: SearchResult) => {
|
||||||
const newConfig = JSON.parse(JSON.stringify(config));
|
const newConfig = JSON.parse(JSON.stringify(config));
|
||||||
if (newConfig.iec?.devices?.[deviceNum]) {
|
if (newConfig.iec?.devices?.drive?.[deviceNum]) {
|
||||||
newConfig.iec.devices[deviceNum].url = result.path;
|
newConfig.iec.devices.drive[deviceNum].url = result.path;
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
toast.success(`Mounted ${result.name} on Device #${deviceNum}`);
|
toast.success(`Mounted ${result.name} on Device #${deviceNum}`);
|
||||||
setShowDeviceMenu(null);
|
setShowDeviceMenu(null);
|
||||||
|
|
@ -131,11 +131,10 @@ export default function SearchOverlay({ config, setConfig, onClose }: SearchOver
|
||||||
|
|
||||||
const getAvailableDevices = () => {
|
const getAvailableDevices = () => {
|
||||||
const devices: { number: string; name: string; url?: string }[] = [];
|
const devices: { number: string; name: string; url?: string }[] = [];
|
||||||
if (config.iec?.devices) {
|
if (config.iec?.devices?.drive) {
|
||||||
for (const [num, device] of Object.entries(config.iec.devices)) {
|
for (const [num, device] of Object.entries(config.iec.devices.drive)) {
|
||||||
const d = device as any;
|
if (num !== 'vdrive' && num !== 'rom' && (device as any).enabled) {
|
||||||
if (d.type === 'drive' && d.enabled) {
|
devices.push({ number: num, name: `Drive ${num}`, url: (device as any).url });
|
||||||
devices.push({ number: num, name: `Drive ${num}`, url: d.url });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,10 @@ export default function StatusPage({ config, setConfig }: StatusPageProps) {
|
||||||
const [showDeviceOverlay, setShowDeviceOverlay] = useState(false);
|
const [showDeviceOverlay, setShowDeviceOverlay] = useState(false);
|
||||||
// Find the first enabled device as the active device
|
// Find the first enabled device as the active device
|
||||||
const findActiveDevice = () => {
|
const findActiveDevice = () => {
|
||||||
if (config.iec?.devices) {
|
if (config.iec?.devices?.drive) {
|
||||||
for (const [num, device] of Object.entries(config.iec.devices)) {
|
for (const [num, device] of Object.entries(config.iec.devices.drive)) {
|
||||||
const d = device as any;
|
if (num !== 'vdrive' && num !== 'rom' && (device as any).enabled) {
|
||||||
if (d.type === 'drive' && d.enabled) {
|
return { number: num, ...device as any, type: 'drive' };
|
||||||
return { number: num, ...d };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,8 +51,8 @@ export default function StatusPage({ config, setConfig }: StatusPageProps) {
|
||||||
|
|
||||||
const switchActiveMedia = (file: string) => {
|
const switchActiveMedia = (file: string) => {
|
||||||
const newConfig = JSON.parse(JSON.stringify(config));
|
const newConfig = JSON.parse(JSON.stringify(config));
|
||||||
if (newConfig.iec?.devices?.[activeDevice!.number]) {
|
if (newConfig.iec?.devices?.drive?.[activeDevice!.number]) {
|
||||||
newConfig.iec.devices[activeDevice!.number].url = file;
|
newConfig.iec.devices.drive[activeDevice!.number].url = file;
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -378,13 +377,6 @@ export default function StatusPage({ config, setConfig }: StatusPageProps) {
|
||||||
<h2 className="text-sm text-neutral-500 flex items-center gap-2">
|
<h2 className="text-sm text-neutral-500 flex items-center gap-2">
|
||||||
<Computer className="w-4 h-4" />
|
<Computer className="w-4 h-4" />
|
||||||
System Status
|
System Status
|
||||||
<div className="ml-auto flex items-center gap-3">
|
|
||||||
<div className="text-xs text-neutral-500">Uptime</div>
|
|
||||||
<div className="flex items-center gap-1 text-sm">
|
|
||||||
<Clock className="w-3 h-3" />
|
|
||||||
<span>3h 24m</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg p-4">
|
<div className="bg-white border border-neutral-200 rounded-lg p-4">
|
||||||
|
|
@ -429,6 +421,10 @@ export default function StatusPage({ config, setConfig }: StatusPageProps) {
|
||||||
<Wifi className="w-3 h-3 text-green-600" />
|
<Wifi className="w-3 h-3 text-green-600" />
|
||||||
<span>Connected</span>
|
<span>Connected</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-xs text-neutral-500 mt-1">IP Address</div>
|
||||||
|
<div className="text-sm text-neutral-700">192.168.1.100</div>
|
||||||
|
<div className="text-xs text-neutral-500 mt-1">MAC Address</div>
|
||||||
|
<div className="text-sm text-neutral-700">AA:BB:CC:DD:EE:FF</div>
|
||||||
<div className="text-xs text-neutral-500 mt-1">WebSocket</div>
|
<div className="text-xs text-neutral-500 mt-1">WebSocket</div>
|
||||||
<div className="flex items-center gap-1 text-sm">
|
<div className="flex items-center gap-1 text-sm">
|
||||||
{wsStatus === 'connecting' && <><Loader2 className="w-3 h-3 text-yellow-500 animate-spin" /><span className="text-yellow-600">Connecting</span></>}
|
{wsStatus === 'connecting' && <><Loader2 className="w-3 h-3 text-yellow-500 animate-spin" /><span className="text-yellow-600">Connecting</span></>}
|
||||||
|
|
@ -437,10 +433,11 @@ export default function StatusPage({ config, setConfig }: StatusPageProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-neutral-500 mt-1">IP Address</div>
|
<div className="text-xs text-neutral-500">Uptime</div>
|
||||||
<div className="text-sm text-neutral-700">192.168.1.100</div>
|
<div className="flex items-center gap-1 text-sm">
|
||||||
<div className="text-xs text-neutral-500 mt-1">MAC Address</div>
|
<Clock className="w-3 h-3" />
|
||||||
<div className="text-sm text-neutral-700">AA:BB:CC:DD:EE:FF</div>
|
<span>3h 24m</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2 mt-6">
|
<div className="flex flex-col gap-2 mt-6">
|
||||||
|
|
|
||||||
|
|
@ -269,7 +269,7 @@ export default function ToolsPage({ config }: ToolsPageProps) {
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 flex items-center gap-2"><Wrench className="w-4 h-4" /> System Tools</h2>
|
<h2 className="text-sm text-neutral-500">System Tools</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
<button
|
<button
|
||||||
|
|
@ -312,7 +312,7 @@ export default function ToolsPage({ config }: ToolsPageProps) {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><HardDrive className="w-4 h-4" /> Storage Tools</h2>
|
<h2 className="text-sm text-neutral-500 pt-4">Storage Tools</h2>
|
||||||
|
|
||||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -137,99 +137,93 @@
|
||||||
"bbs": 1
|
"bbs": 1
|
||||||
},
|
},
|
||||||
"devices": {
|
"devices": {
|
||||||
"4": {
|
"printer": {
|
||||||
"enabled": 1,
|
"4": {
|
||||||
"type": "printer",
|
"enabled": 1,
|
||||||
"name": "MPS803"
|
"type": "printer",
|
||||||
|
"name": "MPS803"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"8": {
|
"drive": {
|
||||||
"enabled": 1,
|
"8": {
|
||||||
"type": "drive",
|
"enabled": 1,
|
||||||
"url": "/sd",
|
"url": "/sd",
|
||||||
"mode": 1
|
"mode": 1
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/",
|
||||||
|
"mode": 1
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/",
|
||||||
|
"mode": 1
|
||||||
|
},
|
||||||
|
"11": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/",
|
||||||
|
"mode": 1
|
||||||
|
},
|
||||||
|
"12": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/",
|
||||||
|
"mode": 1
|
||||||
|
},
|
||||||
|
"13": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/",
|
||||||
|
"mode": 1
|
||||||
|
},
|
||||||
|
"14": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/",
|
||||||
|
"mode": 1
|
||||||
|
},
|
||||||
|
"15": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/",
|
||||||
|
"mode": 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"9": {
|
"network": {
|
||||||
"enabled": 1,
|
"16": {
|
||||||
"type": "drive",
|
"enabled": 1,
|
||||||
"url": "/",
|
"url": "/sd"
|
||||||
"mode": 1
|
},
|
||||||
|
"17": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/"
|
||||||
|
},
|
||||||
|
"18": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/"
|
||||||
|
},
|
||||||
|
"19": {
|
||||||
|
"enabled": 1,
|
||||||
|
"url": "/"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"10": {
|
"other": {
|
||||||
"enabled": 1,
|
"20": {
|
||||||
"type": "drive",
|
"enabled": 1,
|
||||||
"url": "/",
|
"name": "CP/m"
|
||||||
"mode": 1
|
},
|
||||||
|
"21": {
|
||||||
|
"enabled": 1,
|
||||||
|
"name": "S.A.M"
|
||||||
|
},
|
||||||
|
"29": {
|
||||||
|
"enabled": 1,
|
||||||
|
"name": "Clock"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"11": {
|
"meatloaf": {
|
||||||
"enabled": 1,
|
"30": {
|
||||||
"type": "drive",
|
"enabled": 1,
|
||||||
"url": "/",
|
"url": "/",
|
||||||
"mode": 1
|
"mode": 1
|
||||||
},
|
}
|
||||||
"12": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "drive",
|
|
||||||
"url": "/",
|
|
||||||
"mode": 1
|
|
||||||
},
|
|
||||||
"13": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "drive",
|
|
||||||
"url": "/",
|
|
||||||
"mode": 1
|
|
||||||
},
|
|
||||||
"14": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "drive",
|
|
||||||
"url": "/",
|
|
||||||
"mode": 1
|
|
||||||
},
|
|
||||||
"15": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "drive",
|
|
||||||
"url": "/",
|
|
||||||
"mode": 1
|
|
||||||
},
|
|
||||||
"16": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "network",
|
|
||||||
"url": "/sd"
|
|
||||||
},
|
|
||||||
"17": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "network",
|
|
||||||
"url": "/"
|
|
||||||
},
|
|
||||||
"18": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "network",
|
|
||||||
"url": "/"
|
|
||||||
},
|
|
||||||
"19": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "network",
|
|
||||||
"url": "/"
|
|
||||||
},
|
|
||||||
"20": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "other",
|
|
||||||
"name": "CP/m"
|
|
||||||
},
|
|
||||||
"21": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "other",
|
|
||||||
"name": "S.A.M"
|
|
||||||
},
|
|
||||||
"29": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "other",
|
|
||||||
"name": "Clock"
|
|
||||||
},
|
|
||||||
"30": {
|
|
||||||
"enabled": 1,
|
|
||||||
"type": "meatloaf",
|
|
||||||
"url": "/",
|
|
||||||
"mode": 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user