fix import for ConstellationState

This commit is contained in:
Jan-Henrik Bruhn 2026-01-16 10:04:40 +01:00
parent 00c7adc41d
commit 23c65ffbb1
4 changed files with 111 additions and 76 deletions

View file

@ -1,7 +1,8 @@
import { useState, useEffect, KeyboardEvent } from 'react';
import SaveIcon from '@mui/icons-material/Save';
import TangibleForm from './TangibleForm';
import type { TangibleConfig, TangibleMode, LabelConfig, ConstellationState } from '../../types';
import { useState, useEffect, KeyboardEvent } from "react";
import SaveIcon from "@mui/icons-material/Save";
import TangibleForm from "./TangibleForm";
import type { TangibleConfig, TangibleMode, LabelConfig } from "../../types";
import type { ConstellationState } from "../../types/timeline";
interface Props {
tangible: TangibleConfig;
@ -16,28 +17,34 @@ interface Props {
hardwareId?: string;
filterLabels?: string[];
stateId?: string;
}
},
) => void;
onCancel: () => void;
}
const EditTangibleInline = ({ tangible, labels, states, onSave, onCancel }: Props) => {
const [name, setName] = useState('');
const [mode, setMode] = useState<TangibleMode>('filter');
const [description, setDescription] = useState('');
const [hardwareId, setHardwareId] = useState('');
const EditTangibleInline = ({
tangible,
labels,
states,
onSave,
onCancel,
}: Props) => {
const [name, setName] = useState("");
const [mode, setMode] = useState<TangibleMode>("filter");
const [description, setDescription] = useState("");
const [hardwareId, setHardwareId] = useState("");
const [filterLabels, setFilterLabels] = useState<string[]>([]);
const [stateId, setStateId] = useState('');
const [stateId, setStateId] = useState("");
// Sync state with tangible prop
useEffect(() => {
if (tangible) {
setName(tangible.name);
setMode(tangible.mode);
setDescription(tangible.description || '');
setHardwareId(tangible.hardwareId || '');
setDescription(tangible.description || "");
setHardwareId(tangible.hardwareId || "");
setFilterLabels(tangible.filterLabels || []);
setStateId(tangible.stateId || '');
setStateId(tangible.stateId || "");
}
}, [tangible]);
@ -45,12 +52,12 @@ const EditTangibleInline = ({ tangible, labels, states, onSave, onCancel }: Prop
if (!name.trim()) return;
// Validate mode-specific fields
if (mode === 'filter' && filterLabels.length === 0) {
alert('Filter mode requires at least one label');
if (mode === "filter" && filterLabels.length === 0) {
alert("Filter mode requires at least one label");
return;
}
if ((mode === 'state' || mode === 'stateDial') && !stateId) {
alert('State mode requires a state selection');
if ((mode === "state" || mode === "stateDial") && !stateId) {
alert("State mode requires a state selection");
return;
}
@ -59,16 +66,16 @@ const EditTangibleInline = ({ tangible, labels, states, onSave, onCancel }: Prop
mode,
description: description.trim() || undefined,
hardwareId: hardwareId.trim() || undefined,
filterLabels: mode === 'filter' ? filterLabels : undefined,
stateId: (mode === 'state' || mode === 'stateDial') ? stateId : undefined,
filterLabels: mode === "filter" ? filterLabels : undefined,
stateId: mode === "state" || mode === "stateDial" ? stateId : undefined,
});
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
handleSave();
} else if (e.key === 'Escape') {
} else if (e.key === "Escape") {
e.preventDefault();
onCancel();
}
@ -117,9 +124,13 @@ const EditTangibleInline = ({ tangible, labels, states, onSave, onCancel }: Prop
{/* Keyboard Shortcut Hint */}
<div className="text-xs text-gray-500 text-center">
<kbd className="px-1.5 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs">
{navigator.platform.includes('Mac') ? 'Cmd' : 'Ctrl'}+Enter
</kbd>{' '}
to save, <kbd className="px-1.5 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs">Esc</kbd> to cancel
{navigator.platform.includes("Mac") ? "Cmd" : "Ctrl"}+Enter
</kbd>{" "}
to save,{" "}
<kbd className="px-1.5 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs">
Esc
</kbd>{" "}
to cancel
</div>
</div>
</div>

View file

@ -1,6 +1,7 @@
import { useState, useRef, KeyboardEvent } from 'react';
import TangibleForm from './TangibleForm';
import type { TangibleMode, LabelConfig, ConstellationState } from '../../types';
import { useState, useRef, KeyboardEvent } from "react";
import TangibleForm from "./TangibleForm";
import type { TangibleMode, LabelConfig } from "../../types";
import type { ConstellationState } from "../../types/timeline";
interface Props {
labels: LabelConfig[];
@ -16,12 +17,12 @@ interface Props {
}
const QuickAddTangibleForm = ({ labels, states, onAdd }: Props) => {
const [name, setName] = useState('');
const [hardwareId, setHardwareId] = useState('');
const [mode, setMode] = useState<TangibleMode>('filter');
const [description, setDescription] = useState('');
const [name, setName] = useState("");
const [hardwareId, setHardwareId] = useState("");
const [mode, setMode] = useState<TangibleMode>("filter");
const [description, setDescription] = useState("");
const [filterLabels, setFilterLabels] = useState<string[]>([]);
const [stateId, setStateId] = useState('');
const [stateId, setStateId] = useState("");
const nameInputRef = useRef<HTMLInputElement>(null);
@ -32,12 +33,12 @@ const QuickAddTangibleForm = ({ labels, states, onAdd }: Props) => {
}
// Validate mode-specific fields
if (mode === 'filter' && filterLabels.length === 0) {
alert('Filter mode requires at least one label');
if (mode === "filter" && filterLabels.length === 0) {
alert("Filter mode requires at least one label");
return;
}
if ((mode === 'state' || mode === 'stateDial') && !stateId) {
alert('State mode requires a state selection');
if ((mode === "state" || mode === "stateDial") && !stateId) {
alert("State mode requires a state selection");
return;
}
@ -46,33 +47,33 @@ const QuickAddTangibleForm = ({ labels, states, onAdd }: Props) => {
mode,
description,
hardwareId: hardwareId.trim() || undefined,
filterLabels: mode === 'filter' ? filterLabels : undefined,
stateId: (mode === 'state' || mode === 'stateDial') ? stateId : undefined,
filterLabels: mode === "filter" ? filterLabels : undefined,
stateId: mode === "state" || mode === "stateDial" ? stateId : undefined,
});
// Reset form
setName('');
setHardwareId('');
setMode('filter');
setDescription('');
setName("");
setHardwareId("");
setMode("filter");
setDescription("");
setFilterLabels([]);
setStateId('');
setStateId("");
nameInputRef.current?.focus();
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSubmit();
} else if (e.key === 'Escape') {
} else if (e.key === "Escape") {
e.preventDefault();
setName('');
setHardwareId('');
setMode('filter');
setDescription('');
setName("");
setHardwareId("");
setMode("filter");
setDescription("");
setFilterLabels([]);
setStateId('');
setStateId("");
nameInputRef.current?.blur();
}
};

View file

@ -1,4 +1,5 @@
import type { TangibleMode, LabelConfig, ConstellationState } from '../../types';
import type { TangibleMode, LabelConfig } from "../../types";
import type { ConstellationState } from "../../types/timeline";
interface Props {
name: string;
@ -74,49 +75,60 @@ const TangibleForm = ({
type="radio"
name="mode"
value="filter"
checked={mode === 'filter'}
checked={mode === "filter"}
onChange={(e) => onModeChange(e.target.value as TangibleMode)}
className="mr-2"
/>
<span className="text-sm text-gray-700">Filter mode (activate label filters)</span>
<span className="text-sm text-gray-700">
Filter mode (activate label filters)
</span>
</label>
<label className="flex items-center cursor-pointer">
<input
type="radio"
name="mode"
value="state"
checked={mode === 'state'}
checked={mode === "state"}
onChange={(e) => onModeChange(e.target.value as TangibleMode)}
className="mr-2"
/>
<span className="text-sm text-gray-700">State mode (switch to timeline state)</span>
<span className="text-sm text-gray-700">
State mode (switch to timeline state)
</span>
</label>
<label className="flex items-center cursor-pointer">
<input
type="radio"
name="mode"
value="stateDial"
checked={mode === 'stateDial'}
checked={mode === "stateDial"}
onChange={(e) => onModeChange(e.target.value as TangibleMode)}
className="mr-2"
/>
<span className="text-sm text-gray-700">State dial mode (clock-like, deferred)</span>
<span className="text-sm text-gray-700">
State dial mode (clock-like, deferred)
</span>
</label>
</div>
</div>
{/* Mode-specific fields */}
{mode === 'filter' && (
{mode === "filter" && (
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Filter Labels * (select one or more)
</label>
<div className="border border-gray-300 rounded-md p-2 max-h-40 overflow-y-auto">
{labels.length === 0 ? (
<p className="text-xs text-gray-500 italic">No labels available</p>
<p className="text-xs text-gray-500 italic">
No labels available
</p>
) : (
labels.map((label) => (
<label key={label.id} className="flex items-center py-1 cursor-pointer">
<label
key={label.id}
className="flex items-center py-1 cursor-pointer"
>
<input
type="checkbox"
checked={filterLabels.includes(label.id)}
@ -124,7 +136,9 @@ const TangibleForm = ({
if (e.target.checked) {
onFilterLabelsChange([...filterLabels, label.id]);
} else {
onFilterLabelsChange(filterLabels.filter((id) => id !== label.id));
onFilterLabelsChange(
filterLabels.filter((id) => id !== label.id),
);
}
}}
className="mr-2"
@ -141,7 +155,7 @@ const TangibleForm = ({
</div>
)}
{(mode === 'state' || mode === 'stateDial') && (
{(mode === "state" || mode === "stateDial") && (
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Timeline State *

View file

@ -1,5 +1,6 @@
import DeleteIcon from '@mui/icons-material/Delete';
import type { TangibleConfig, LabelConfig, ConstellationState } from '../../types';
import DeleteIcon from "@mui/icons-material/Delete";
import type { TangibleConfig, LabelConfig } from "../../types";
import type { ConstellationState } from "../../types/timeline";
interface Props {
tangibles: TangibleConfig[];
@ -9,7 +10,12 @@ interface Props {
onDelete: (id: string) => void;
}
const TangibleManagementList = ({ tangibles, states, onEdit, onDelete }: Props) => {
const TangibleManagementList = ({
tangibles,
states,
onEdit,
onDelete,
}: Props) => {
if (tangibles.length === 0) {
return (
<div className="text-center py-12 text-gray-500">
@ -21,17 +27,17 @@ const TangibleManagementList = ({ tangibles, states, onEdit, onDelete }: Props)
const getModeDisplay = (tangible: TangibleConfig) => {
switch (tangible.mode) {
case 'filter': {
case "filter": {
const labelCount = tangible.filterLabels?.length || 0;
return `Filter (${labelCount} label${labelCount !== 1 ? 's' : ''})`;
return `Filter (${labelCount} label${labelCount !== 1 ? "s" : ""})`;
}
case 'state': {
const state = states.find(s => s.id === tangible.stateId);
return `State: ${state?.label || 'Unknown'}`;
case "state": {
const state = states.find((s) => s.id === tangible.stateId);
return `State: ${state?.label || "Unknown"}`;
}
case 'stateDial': {
const dialState = states.find(s => s.id === tangible.stateId);
return `State Dial: ${dialState?.label || 'Unknown'}`;
case "stateDial": {
const dialState = states.find((s) => s.id === tangible.stateId);
return `State Dial: ${dialState?.label || "Unknown"}`;
}
default:
return tangible.mode;
@ -48,7 +54,7 @@ const TangibleManagementList = ({ tangibles, states, onEdit, onDelete }: Props)
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onEdit(tangible);
}
@ -62,7 +68,10 @@ const TangibleManagementList = ({ tangibles, states, onEdit, onDelete }: Props)
</h4>
{tangible.hardwareId && (
<p className="text-xs text-gray-600 mt-1">
Hardware: <code className="bg-gray-100 px-1 rounded">{tangible.hardwareId}</code>
Hardware:{" "}
<code className="bg-gray-100 px-1 rounded">
{tangible.hardwareId}
</code>
</p>
)}
<p className="text-xs text-gray-500 mt-1">