- Updated references in AGENTS.md, README.md, and various component files (DevicesPage, GeneralPage, IECPage, MediaManager, SearchOverlay, StatusPage) to reflect the new config structure. - Adjusted settings handling in settings.ts to accommodate the new structure. - Modified initial config in config.json to match the updated schema.
326 lines
14 KiB
TypeScript
326 lines
14 KiB
TypeScript
import { useState } from 'react';
|
|
import { Cable, Code2, Cpu, FolderOpen, Link, List, Zap } from 'lucide-react';
|
|
import MediaBrowser from './MediaBrowser';
|
|
|
|
interface IECPageProps {
|
|
config: any;
|
|
setConfig: (config: any) => void;
|
|
}
|
|
|
|
export default function IECPage({ config, setConfig }: IECPageProps) {
|
|
const [showMediaBrowser, setShowMediaBrowser] = useState(false);
|
|
const [driveRomBrowsingKey, setDriveRomBrowsingKey] = useState<string | null>(null);
|
|
const updateSetting = (path: string[], value: any) => {
|
|
const newConfig = JSON.parse(JSON.stringify(config));
|
|
let current = newConfig;
|
|
|
|
for (let i = 0; i < path.length - 1; i++) {
|
|
current = current[path[i]];
|
|
}
|
|
|
|
current[path[path.length - 1]] = value;
|
|
setConfig(newConfig);
|
|
};
|
|
|
|
const settings = config.settings || {};
|
|
|
|
const boolDirEntries = Object.entries(settings.directory || {}).filter(([, v]) => v === 0 || v === 1);
|
|
const isCompatMode = boolDirEntries.length > 0 && boolDirEntries.every(([, v]) => v === 0);
|
|
|
|
const setAllDirBools = (val: 0 | 1) => {
|
|
const newConfig = JSON.parse(JSON.stringify(config));
|
|
const dir = newConfig.settings?.directory ?? {};
|
|
for (const k of Object.keys(dir)) {
|
|
if (dir[k] === 0 || dir[k] === 1) dir[k] = val;
|
|
}
|
|
setConfig(newConfig);
|
|
};
|
|
|
|
return (
|
|
<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>
|
|
|
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
|
<div className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">IEC Enabled</label>
|
|
<button
|
|
onClick={() => updateSetting(['settings', 'enabled'], settings.enabled ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
settings.enabled ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
settings.enabled ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">VIC-20 Mode</label>
|
|
<button
|
|
onClick={() => updateSetting(['settings', 'vic20_mode'], settings.vic20_mode ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
settings.vic20_mode ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
settings.vic20_mode ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">VDrive Mode</label>
|
|
<button
|
|
onClick={() => updateSetting(['settings', 'vdrive_mode'], settings.vdrive_mode ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
settings.vdrive_mode ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
settings.vdrive_mode ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-4">
|
|
<label className="text-sm text-neutral-500 block mb-2">Autoboot</label>
|
|
<div className="flex gap-2">
|
|
<input
|
|
type="text"
|
|
value={settings.autoboot || ''}
|
|
onChange={(e) => updateSetting(['settings', 'autoboot'], e.target.value)}
|
|
className="flex-1 px-3 py-2 border border-neutral-300 rounded-lg"
|
|
/>
|
|
<button
|
|
onClick={() => setShowMediaBrowser(true)}
|
|
className="px-3 py-2 border border-neutral-300 rounded-lg bg-neutral-50 hover:bg-neutral-100"
|
|
title="Browse files"
|
|
>
|
|
<FolderOpen className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{showMediaBrowser && (
|
|
<MediaBrowser
|
|
currentPath={settings.autoboot || '/'}
|
|
onSelect={(path) => {
|
|
updateSetting(['settings', 'autoboot'], path);
|
|
setShowMediaBrowser(false);
|
|
}}
|
|
onClose={() => setShowMediaBrowser(false)}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Cpu className="w-4 h-4" /> Drive ROMs</h2>
|
|
|
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
|
<div className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">Enabled</label>
|
|
<button
|
|
onClick={() => {
|
|
const newConfig = JSON.parse(JSON.stringify(config));
|
|
const dr = newConfig.settings.drive_roms ?? {};
|
|
const current = dr.enabled ?? dr.auto ?? 0;
|
|
dr.enabled = current ? 0 : 1;
|
|
delete dr.auto;
|
|
newConfig.settings.drive_roms = dr;
|
|
setConfig(newConfig);
|
|
}}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
(settings.drive_roms?.enabled ?? settings.drive_roms?.auto) ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
(settings.drive_roms?.enabled ?? settings.drive_roms?.auto) ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
{(['default', 'd64', 'd71', 'd81'] as const).map((key) => (
|
|
<div key={key} className="p-4">
|
|
<label className="text-sm text-neutral-500 block mb-2">
|
|
{key === 'default' ? 'Default' : key.toUpperCase()}
|
|
</label>
|
|
<div className="flex gap-2">
|
|
<input
|
|
type="text"
|
|
value={settings.drive_roms?.[key] || ''}
|
|
onChange={(e) => updateSetting(['settings', 'drive_roms', key], e.target.value)}
|
|
className="flex-1 px-3 py-2 border border-neutral-300 rounded-lg"
|
|
/>
|
|
<button
|
|
onClick={() => setDriveRomBrowsingKey(key)}
|
|
className="px-3 py-2 border border-neutral-300 rounded-lg bg-neutral-50 hover:bg-neutral-100"
|
|
title="Browse files"
|
|
>
|
|
<FolderOpen className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{driveRomBrowsingKey && (
|
|
<MediaBrowser
|
|
currentPath={settings.drive_roms?.[driveRomBrowsingKey] || '/'}
|
|
onSelect={(path) => {
|
|
updateSetting(['settings', 'drive_roms', driveRomBrowsingKey], path);
|
|
setDriveRomBrowsingKey(null);
|
|
}}
|
|
onClose={() => setDriveRomBrowsingKey(null)}
|
|
/>
|
|
)}
|
|
|
|
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><List className="w-4 h-4" /> Directory Enhancements</h2>
|
|
|
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
|
{/* CBM Directory Compatibility Mode */}
|
|
<div className="p-4 flex items-center justify-between bg-neutral-50">
|
|
<div>
|
|
<div className="text-sm font-medium text-neutral-700">CBM Directory Compatibility</div>
|
|
<div className="text-xs text-neutral-400 mt-0.5">Disables all directory enhancements</div>
|
|
</div>
|
|
<button
|
|
onClick={() => setAllDirBools(isCompatMode ? 1 : 0)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${isCompatMode ? 'bg-blue-600' : 'bg-neutral-300'}`}
|
|
>
|
|
<div className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${isCompatMode ? 'translate-x-6' : 'translate-x-0.5'}`} />
|
|
</button>
|
|
</div>
|
|
|
|
{Object.entries(settings.directory || {}).map(([key, value]) => (
|
|
<div key={key} className={`p-4 flex items-center justify-between transition-opacity ${isCompatMode ? 'opacity-40' : ''}`}>
|
|
<label className="text-sm text-neutral-500">
|
|
{key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
|
</label>
|
|
{value === 0 || value === 1 ? (
|
|
<button
|
|
onClick={() => { if (!isCompatMode) updateSetting(['settings', 'directory', key], value ? 0 : 1); }}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${value ? 'bg-blue-600' : 'bg-neutral-300'} ${isCompatMode ? 'cursor-not-allowed' : ''}`}
|
|
>
|
|
<div className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${value ? 'translate-x-6' : 'translate-x-0.5'}`} />
|
|
</button>
|
|
) : (
|
|
<input
|
|
type="number"
|
|
value={value}
|
|
disabled={isCompatMode}
|
|
onChange={(e) => updateSetting(['settings', 'directory', key], parseInt(e.target.value))}
|
|
className="w-24 px-3 py-1 border border-neutral-300 rounded-lg text-right disabled:cursor-not-allowed"
|
|
/>
|
|
)}
|
|
</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>
|
|
|
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
|
<div className="p-3 bg-neutral-50">
|
|
<h3 className="text-xs text-neutral-600">Serial</h3>
|
|
</div>
|
|
{Object.entries(settings.fastloaders?.hardware?.serial || {}).map(([key, value]) => (
|
|
<div key={key} className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">
|
|
{key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
|
</label>
|
|
<button
|
|
onClick={() => updateSetting(['settings', 'fastloaders', 'hardware', 'serial', key], value ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
value ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
value ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
))}
|
|
|
|
<div className="p-3 bg-neutral-50">
|
|
<h3 className="text-xs text-neutral-600">Parallel</h3>
|
|
</div>
|
|
{Object.entries(settings.fastloaders?.hardware?.parallel || {}).map(([key, value]) => (
|
|
<div key={key} className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">
|
|
{key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
|
</label>
|
|
<button
|
|
onClick={() => updateSetting(['settings', 'fastloaders', 'hardware', 'parallel', key], value ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
value ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
value ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</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>
|
|
|
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
|
{Object.entries(settings.fastloaders?.software || {}).map(([key, value]) => (
|
|
<div key={key} className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">
|
|
{key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
|
</label>
|
|
<button
|
|
onClick={() => updateSetting(['settings', 'fastloaders', 'software', key], value ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
value ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
value ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Link className="w-4 h-4" /> Chainloaders</h2>
|
|
|
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
|
{Object.entries(settings.chainloaders || {}).map(([key, value]) => (
|
|
<div key={key} className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">
|
|
{key.toUpperCase()}
|
|
</label>
|
|
<button
|
|
onClick={() => updateSetting(['settings', 'chainloaders', key], value ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
value ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
value ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|