mirror of
https://github.com/OFFIS-ESC/constellation-analyzer
synced 2026-01-26 23:43:40 +00:00
fix import for ConstellationState
This commit is contained in:
parent
00c7adc41d
commit
23c65ffbb1
4 changed files with 111 additions and 76 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
import { useState, useEffect, KeyboardEvent } from 'react';
|
import { useState, useEffect, KeyboardEvent } from "react";
|
||||||
import SaveIcon from '@mui/icons-material/Save';
|
import SaveIcon from "@mui/icons-material/Save";
|
||||||
import TangibleForm from './TangibleForm';
|
import TangibleForm from "./TangibleForm";
|
||||||
import type { TangibleConfig, TangibleMode, LabelConfig, ConstellationState } from '../../types';
|
import type { TangibleConfig, TangibleMode, LabelConfig } from "../../types";
|
||||||
|
import type { ConstellationState } from "../../types/timeline";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tangible: TangibleConfig;
|
tangible: TangibleConfig;
|
||||||
|
|
@ -16,28 +17,34 @@ interface Props {
|
||||||
hardwareId?: string;
|
hardwareId?: string;
|
||||||
filterLabels?: string[];
|
filterLabels?: string[];
|
||||||
stateId?: string;
|
stateId?: string;
|
||||||
}
|
},
|
||||||
) => void;
|
) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditTangibleInline = ({ tangible, labels, states, onSave, onCancel }: Props) => {
|
const EditTangibleInline = ({
|
||||||
const [name, setName] = useState('');
|
tangible,
|
||||||
const [mode, setMode] = useState<TangibleMode>('filter');
|
labels,
|
||||||
const [description, setDescription] = useState('');
|
states,
|
||||||
const [hardwareId, setHardwareId] = useState('');
|
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 [filterLabels, setFilterLabels] = useState<string[]>([]);
|
||||||
const [stateId, setStateId] = useState('');
|
const [stateId, setStateId] = useState("");
|
||||||
|
|
||||||
// Sync state with tangible prop
|
// Sync state with tangible prop
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tangible) {
|
if (tangible) {
|
||||||
setName(tangible.name);
|
setName(tangible.name);
|
||||||
setMode(tangible.mode);
|
setMode(tangible.mode);
|
||||||
setDescription(tangible.description || '');
|
setDescription(tangible.description || "");
|
||||||
setHardwareId(tangible.hardwareId || '');
|
setHardwareId(tangible.hardwareId || "");
|
||||||
setFilterLabels(tangible.filterLabels || []);
|
setFilterLabels(tangible.filterLabels || []);
|
||||||
setStateId(tangible.stateId || '');
|
setStateId(tangible.stateId || "");
|
||||||
}
|
}
|
||||||
}, [tangible]);
|
}, [tangible]);
|
||||||
|
|
||||||
|
|
@ -45,12 +52,12 @@ const EditTangibleInline = ({ tangible, labels, states, onSave, onCancel }: Prop
|
||||||
if (!name.trim()) return;
|
if (!name.trim()) return;
|
||||||
|
|
||||||
// Validate mode-specific fields
|
// Validate mode-specific fields
|
||||||
if (mode === 'filter' && filterLabels.length === 0) {
|
if (mode === "filter" && filterLabels.length === 0) {
|
||||||
alert('Filter mode requires at least one label');
|
alert("Filter mode requires at least one label");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((mode === 'state' || mode === 'stateDial') && !stateId) {
|
if ((mode === "state" || mode === "stateDial") && !stateId) {
|
||||||
alert('State mode requires a state selection');
|
alert("State mode requires a state selection");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,16 +66,16 @@ const EditTangibleInline = ({ tangible, labels, states, onSave, onCancel }: Prop
|
||||||
mode,
|
mode,
|
||||||
description: description.trim() || undefined,
|
description: description.trim() || undefined,
|
||||||
hardwareId: hardwareId.trim() || undefined,
|
hardwareId: hardwareId.trim() || undefined,
|
||||||
filterLabels: mode === 'filter' ? filterLabels : undefined,
|
filterLabels: mode === "filter" ? filterLabels : undefined,
|
||||||
stateId: (mode === 'state' || mode === 'stateDial') ? stateId : undefined,
|
stateId: mode === "state" || mode === "stateDial" ? stateId : undefined,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSave();
|
handleSave();
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === "Escape") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onCancel();
|
onCancel();
|
||||||
}
|
}
|
||||||
|
|
@ -117,9 +124,13 @@ const EditTangibleInline = ({ tangible, labels, states, onSave, onCancel }: Prop
|
||||||
{/* Keyboard Shortcut Hint */}
|
{/* Keyboard Shortcut Hint */}
|
||||||
<div className="text-xs text-gray-500 text-center">
|
<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">
|
<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
|
{navigator.platform.includes("Mac") ? "Cmd" : "Ctrl"}+Enter
|
||||||
</kbd>{' '}
|
</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
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState, useRef, KeyboardEvent } from 'react';
|
import { useState, useRef, KeyboardEvent } from "react";
|
||||||
import TangibleForm from './TangibleForm';
|
import TangibleForm from "./TangibleForm";
|
||||||
import type { TangibleMode, LabelConfig, ConstellationState } from '../../types';
|
import type { TangibleMode, LabelConfig } from "../../types";
|
||||||
|
import type { ConstellationState } from "../../types/timeline";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
labels: LabelConfig[];
|
labels: LabelConfig[];
|
||||||
|
|
@ -16,12 +17,12 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const QuickAddTangibleForm = ({ labels, states, onAdd }: Props) => {
|
const QuickAddTangibleForm = ({ labels, states, onAdd }: Props) => {
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState("");
|
||||||
const [hardwareId, setHardwareId] = useState('');
|
const [hardwareId, setHardwareId] = useState("");
|
||||||
const [mode, setMode] = useState<TangibleMode>('filter');
|
const [mode, setMode] = useState<TangibleMode>("filter");
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState("");
|
||||||
const [filterLabels, setFilterLabels] = useState<string[]>([]);
|
const [filterLabels, setFilterLabels] = useState<string[]>([]);
|
||||||
const [stateId, setStateId] = useState('');
|
const [stateId, setStateId] = useState("");
|
||||||
|
|
||||||
const nameInputRef = useRef<HTMLInputElement>(null);
|
const nameInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
|
@ -32,12 +33,12 @@ const QuickAddTangibleForm = ({ labels, states, onAdd }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate mode-specific fields
|
// Validate mode-specific fields
|
||||||
if (mode === 'filter' && filterLabels.length === 0) {
|
if (mode === "filter" && filterLabels.length === 0) {
|
||||||
alert('Filter mode requires at least one label');
|
alert("Filter mode requires at least one label");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((mode === 'state' || mode === 'stateDial') && !stateId) {
|
if ((mode === "state" || mode === "stateDial") && !stateId) {
|
||||||
alert('State mode requires a state selection');
|
alert("State mode requires a state selection");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,33 +47,33 @@ const QuickAddTangibleForm = ({ labels, states, onAdd }: Props) => {
|
||||||
mode,
|
mode,
|
||||||
description,
|
description,
|
||||||
hardwareId: hardwareId.trim() || undefined,
|
hardwareId: hardwareId.trim() || undefined,
|
||||||
filterLabels: mode === 'filter' ? filterLabels : undefined,
|
filterLabels: mode === "filter" ? filterLabels : undefined,
|
||||||
stateId: (mode === 'state' || mode === 'stateDial') ? stateId : undefined,
|
stateId: mode === "state" || mode === "stateDial" ? stateId : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset form
|
// Reset form
|
||||||
setName('');
|
setName("");
|
||||||
setHardwareId('');
|
setHardwareId("");
|
||||||
setMode('filter');
|
setMode("filter");
|
||||||
setDescription('');
|
setDescription("");
|
||||||
setFilterLabels([]);
|
setFilterLabels([]);
|
||||||
setStateId('');
|
setStateId("");
|
||||||
|
|
||||||
nameInputRef.current?.focus();
|
nameInputRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === "Escape") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setName('');
|
setName("");
|
||||||
setHardwareId('');
|
setHardwareId("");
|
||||||
setMode('filter');
|
setMode("filter");
|
||||||
setDescription('');
|
setDescription("");
|
||||||
setFilterLabels([]);
|
setFilterLabels([]);
|
||||||
setStateId('');
|
setStateId("");
|
||||||
nameInputRef.current?.blur();
|
nameInputRef.current?.blur();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
interface Props {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -74,49 +75,60 @@ const TangibleForm = ({
|
||||||
type="radio"
|
type="radio"
|
||||||
name="mode"
|
name="mode"
|
||||||
value="filter"
|
value="filter"
|
||||||
checked={mode === 'filter'}
|
checked={mode === "filter"}
|
||||||
onChange={(e) => onModeChange(e.target.value as TangibleMode)}
|
onChange={(e) => onModeChange(e.target.value as TangibleMode)}
|
||||||
className="mr-2"
|
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>
|
||||||
<label className="flex items-center cursor-pointer">
|
<label className="flex items-center cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="mode"
|
name="mode"
|
||||||
value="state"
|
value="state"
|
||||||
checked={mode === 'state'}
|
checked={mode === "state"}
|
||||||
onChange={(e) => onModeChange(e.target.value as TangibleMode)}
|
onChange={(e) => onModeChange(e.target.value as TangibleMode)}
|
||||||
className="mr-2"
|
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>
|
||||||
<label className="flex items-center cursor-pointer">
|
<label className="flex items-center cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="mode"
|
name="mode"
|
||||||
value="stateDial"
|
value="stateDial"
|
||||||
checked={mode === 'stateDial'}
|
checked={mode === "stateDial"}
|
||||||
onChange={(e) => onModeChange(e.target.value as TangibleMode)}
|
onChange={(e) => onModeChange(e.target.value as TangibleMode)}
|
||||||
className="mr-2"
|
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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mode-specific fields */}
|
{/* Mode-specific fields */}
|
||||||
{mode === 'filter' && (
|
{mode === "filter" && (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||||
Filter Labels * (select one or more)
|
Filter Labels * (select one or more)
|
||||||
</label>
|
</label>
|
||||||
<div className="border border-gray-300 rounded-md p-2 max-h-40 overflow-y-auto">
|
<div className="border border-gray-300 rounded-md p-2 max-h-40 overflow-y-auto">
|
||||||
{labels.length === 0 ? (
|
{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) => (
|
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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={filterLabels.includes(label.id)}
|
checked={filterLabels.includes(label.id)}
|
||||||
|
|
@ -124,7 +136,9 @@ const TangibleForm = ({
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
onFilterLabelsChange([...filterLabels, label.id]);
|
onFilterLabelsChange([...filterLabels, label.id]);
|
||||||
} else {
|
} else {
|
||||||
onFilterLabelsChange(filterLabels.filter((id) => id !== label.id));
|
onFilterLabelsChange(
|
||||||
|
filterLabels.filter((id) => id !== label.id),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
|
|
@ -141,7 +155,7 @@ const TangibleForm = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(mode === 'state' || mode === 'stateDial') && (
|
{(mode === "state" || mode === "stateDial") && (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||||
Timeline State *
|
Timeline State *
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import type { TangibleConfig, LabelConfig, ConstellationState } from '../../types';
|
import type { TangibleConfig, LabelConfig } from "../../types";
|
||||||
|
import type { ConstellationState } from "../../types/timeline";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tangibles: TangibleConfig[];
|
tangibles: TangibleConfig[];
|
||||||
|
|
@ -9,7 +10,12 @@ interface Props {
|
||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TangibleManagementList = ({ tangibles, states, onEdit, onDelete }: Props) => {
|
const TangibleManagementList = ({
|
||||||
|
tangibles,
|
||||||
|
states,
|
||||||
|
onEdit,
|
||||||
|
onDelete,
|
||||||
|
}: Props) => {
|
||||||
if (tangibles.length === 0) {
|
if (tangibles.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12 text-gray-500">
|
<div className="text-center py-12 text-gray-500">
|
||||||
|
|
@ -21,17 +27,17 @@ const TangibleManagementList = ({ tangibles, states, onEdit, onDelete }: Props)
|
||||||
|
|
||||||
const getModeDisplay = (tangible: TangibleConfig) => {
|
const getModeDisplay = (tangible: TangibleConfig) => {
|
||||||
switch (tangible.mode) {
|
switch (tangible.mode) {
|
||||||
case 'filter': {
|
case "filter": {
|
||||||
const labelCount = tangible.filterLabels?.length || 0;
|
const labelCount = tangible.filterLabels?.length || 0;
|
||||||
return `Filter (${labelCount} label${labelCount !== 1 ? 's' : ''})`;
|
return `Filter (${labelCount} label${labelCount !== 1 ? "s" : ""})`;
|
||||||
}
|
}
|
||||||
case 'state': {
|
case "state": {
|
||||||
const state = states.find(s => s.id === tangible.stateId);
|
const state = states.find((s) => s.id === tangible.stateId);
|
||||||
return `State: ${state?.label || 'Unknown'}`;
|
return `State: ${state?.label || "Unknown"}`;
|
||||||
}
|
}
|
||||||
case 'stateDial': {
|
case "stateDial": {
|
||||||
const dialState = states.find(s => s.id === tangible.stateId);
|
const dialState = states.find((s) => s.id === tangible.stateId);
|
||||||
return `State Dial: ${dialState?.label || 'Unknown'}`;
|
return `State Dial: ${dialState?.label || "Unknown"}`;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return tangible.mode;
|
return tangible.mode;
|
||||||
|
|
@ -48,7 +54,7 @@ const TangibleManagementList = ({ tangibles, states, onEdit, onDelete }: Props)
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onEdit(tangible);
|
onEdit(tangible);
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +68,10 @@ const TangibleManagementList = ({ tangibles, states, onEdit, onDelete }: Props)
|
||||||
</h4>
|
</h4>
|
||||||
{tangible.hardwareId && (
|
{tangible.hardwareId && (
|
||||||
<p className="text-xs text-gray-600 mt-1">
|
<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>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue