Fix equipment profiles: allow selecting active setup and renaming default
- All profiles (including the default AT71+ATR2600C) now show Edit and Use buttons - Active profile tracked in localStorage via astronome_active_profile - Default profile is seeded from localStorage on first load so it can be renamed - Delete button only shown when more than one profile exists Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,15 +3,14 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { api } from '../api';
|
||||
import type { HorizonPoint } from '../api/types';
|
||||
|
||||
// Current hardcoded setup constants (from config.rs)
|
||||
const CURRENT_SETUP = {
|
||||
name: 'AT71 + ATR2600C (current)',
|
||||
const DEFAULT_PROFILE: EquipProfile = {
|
||||
id: 'default',
|
||||
name: 'AT71 + ATR2600C',
|
||||
focal_mm: 490,
|
||||
aperture_mm: 71,
|
||||
pixel_um: 3.76,
|
||||
res_x: 6248,
|
||||
res_y: 4176,
|
||||
active: true,
|
||||
};
|
||||
|
||||
interface EquipProfile {
|
||||
@@ -24,9 +23,18 @@ interface EquipProfile {
|
||||
res_y: number;
|
||||
}
|
||||
|
||||
function loadProfiles(): EquipProfile[] {
|
||||
try {
|
||||
const stored = JSON.parse(localStorage.getItem('astronome_equip_profiles') ?? '[]') as EquipProfile[];
|
||||
// Ensure the default profile is always present
|
||||
if (!stored.find(p => p.id === 'default')) {
|
||||
return [DEFAULT_PROFILE, ...stored];
|
||||
}
|
||||
return stored;
|
||||
} catch { return [DEFAULT_PROFILE]; }
|
||||
}
|
||||
|
||||
function calcProfile(p: { focal_mm: number; aperture_mm: number; pixel_um: number; res_x: number; res_y: number }) {
|
||||
const plate_scale = (206.265 * p.pixel_um) / (p.focal_mm * 1000 / 1000);
|
||||
// plate_scale in arcsec/px: (206265 * pixel_size_um / 1000) / focal_mm
|
||||
const ps = (206.265 * p.pixel_um / 1000) / p.focal_mm * 1000;
|
||||
const fov_w_deg = (ps * p.res_x) / 3600;
|
||||
const fov_h_deg = (ps * p.res_y) / 3600;
|
||||
@@ -39,11 +47,10 @@ function calcProfile(p: { focal_mm: number; aperture_mm: number; pixel_um: numbe
|
||||
}
|
||||
|
||||
function EquipmentProfiles() {
|
||||
const [profiles, setProfiles] = useState<EquipProfile[]>(() => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('astronome_equip_profiles') ?? '[]');
|
||||
} catch { return []; }
|
||||
});
|
||||
const [profiles, setProfiles] = useState<EquipProfile[]>(loadProfiles);
|
||||
const [activeId, setActiveId] = useState<string>(() =>
|
||||
localStorage.getItem('astronome_active_profile') ?? 'default'
|
||||
);
|
||||
const [editing, setEditing] = useState<EquipProfile | null>(null);
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [form, setForm] = useState({ name: '', focal_mm: 490, aperture_mm: 71, pixel_um: 3.76, res_x: 6248, res_y: 4176 });
|
||||
@@ -52,6 +59,10 @@ function EquipmentProfiles() {
|
||||
localStorage.setItem('astronome_equip_profiles', JSON.stringify(profiles));
|
||||
}, [profiles]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('astronome_active_profile', activeId);
|
||||
}, [activeId]);
|
||||
|
||||
const saveProfile = () => {
|
||||
if (!form.name.trim()) return;
|
||||
if (editing) {
|
||||
@@ -65,7 +76,9 @@ function EquipmentProfiles() {
|
||||
};
|
||||
|
||||
const deleteProfile = (id: string) => {
|
||||
if (profiles.length <= 1) return;
|
||||
setProfiles(ps => ps.filter(p => p.id !== id));
|
||||
if (activeId === id) setActiveId(profiles.find(p => p.id !== id)?.id ?? 'default');
|
||||
};
|
||||
|
||||
const startEdit = (p: EquipProfile) => {
|
||||
@@ -74,9 +87,6 @@ function EquipmentProfiles() {
|
||||
setAdding(false);
|
||||
};
|
||||
|
||||
const current = calcProfile(CURRENT_SETUP);
|
||||
const allProfiles = [{ ...CURRENT_SETUP, id: '__current__' }, ...profiles];
|
||||
|
||||
const fieldStyle: React.CSSProperties = {
|
||||
background: 'var(--bg-void)', border: '1px solid var(--border)', borderRadius: 3,
|
||||
color: 'var(--text-hi)', fontFamily: 'var(--font-mono)', fontSize: 12,
|
||||
@@ -90,19 +100,19 @@ function EquipmentProfiles() {
|
||||
</h2>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, maxWidth: 640 }}>
|
||||
{allProfiles.map(p => {
|
||||
{profiles.map(p => {
|
||||
const calc = calcProfile(p);
|
||||
const isCurrent = p.id === '__current__';
|
||||
const isActive = p.id === activeId;
|
||||
return (
|
||||
<div key={p.id} style={{
|
||||
background: 'var(--bg-panel)', border: `1px solid ${isCurrent ? 'var(--amber-dim)' : 'var(--border)'}`,
|
||||
background: 'var(--bg-panel)', border: `1px solid ${isActive ? 'var(--amber-dim)' : 'var(--border)'}`,
|
||||
borderRadius: 6, padding: '12px 16px',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 13, color: isCurrent ? 'var(--amber)' : 'var(--text-hi)', fontWeight: 600 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 13, color: isActive ? 'var(--amber)' : 'var(--text-hi)', fontWeight: 600 }}>
|
||||
{p.name}
|
||||
{isCurrent && <span style={{ marginLeft: 8, fontSize: 10, color: 'var(--amber)', background: 'var(--amber-glow)', padding: '1px 6px', borderRadius: 3 }}>ACTIVE</span>}
|
||||
{isActive && <span style={{ marginLeft: 8, fontSize: 10, color: 'var(--amber)', background: 'var(--amber-glow)', padding: '1px 6px', borderRadius: 3 }}>ACTIVE</span>}
|
||||
</div>
|
||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-lo)', marginTop: 4 }}>
|
||||
{p.focal_mm}mm {calc.focal_ratio} · {p.aperture_mm}mm aperture · {p.pixel_um}μm px · {p.res_x}×{p.res_y}
|
||||
@@ -112,16 +122,21 @@ function EquipmentProfiles() {
|
||||
{' · '}FOV: <strong style={{ color: 'var(--teal)' }}>{calc.fov_w}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{!isCurrent && (
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<button onClick={() => startEdit(p as EquipProfile)} style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--blue)', background: 'none', border: '1px solid var(--border)', borderRadius: 3, padding: '3px 8px', cursor: 'pointer' }}>
|
||||
Edit
|
||||
<div style={{ display: 'flex', gap: 6, flexShrink: 0 }}>
|
||||
{!isActive && (
|
||||
<button onClick={() => setActiveId(p.id)} style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--amber)', background: 'var(--amber-glow)', border: '1px solid var(--amber-dim)', borderRadius: 3, padding: '3px 8px', cursor: 'pointer', whiteSpace: 'nowrap' }}>
|
||||
Use
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => startEdit(p)} style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--blue)', background: 'none', border: '1px solid var(--border)', borderRadius: 3, padding: '3px 8px', cursor: 'pointer' }}>
|
||||
Edit
|
||||
</button>
|
||||
{profiles.length > 1 && (
|
||||
<button onClick={() => deleteProfile(p.id)} style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--danger)', background: 'none', border: '1px solid var(--border)', borderRadius: 3, padding: '3px 8px', cursor: 'pointer' }}>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user