Make entrance a draggable/resizable square with selection and size controls

This commit is contained in:
2026-10-17 20:02:57 -04:00
parent ced33b672f
commit 752a87cc0f
5 changed files with 238 additions and 132 deletions
+126 -76
View File
@@ -1,4 +1,4 @@
import type { ConditionKind, Entrance, FurnitureConfig, FurniturePreset, LayoutState, RoomConfig } from '../types';
import type { ConditionKind, Entrance, LayoutState, RoomConfig } from '../types';
interface ControlPanelProps {
state: LayoutState;
@@ -23,61 +23,7 @@ export function ControlPanel({
optimizing,
timeLeft,
}: ControlPanelProps) {
const addPreset = (p: FurniturePreset) => {
const id = 'f_' + Date.now();
const newItem: FurnitureConfig = {
id,
name: p.name,
shape: { ...p.shape, x: 50, y: 50 },
conditions: p.conditions.map((c) => ({ ...c })),
color: p.color,
lockedForOptimization: false,
placed: true,
};
onStateChange({ ...state, furniture: [...state.furniture, newItem] });
onSelect(id);
};
const addFurniture = () => {
const id = 'f_' + Date.now();
const newItem: FurnitureConfig = {
id,
name: 'Table ' + state.furniture.length,
shape: { type: 'rectangle', x: 50, y: 50, width: 60, height: 40 },
conditions: [
{ kind: 'walk_to_and_sit', weight: 1 },
{ kind: 'accessible', weight: 1 },
],
color: '#a1887f',
lockedForOptimization: false,
placed: true,
};
onStateChange({ ...state, furniture: [...state.furniture, newItem] });
onSelect(id);
};
const tablePreset: FurniturePreset = {
id: 'table',
name: 'Table',
shape: { type: 'rectangle', width: 80, height: 60 },
conditions: [
{ kind: 'walk_to_and_sit', weight: 1 },
{ kind: 'accessible', weight: 1 },
],
color: '#a1887f',
};
const chairPreset: FurniturePreset = {
id: 'chair',
name: 'Chair',
shape: { type: 'rectangle', width: 40, height: 40 },
conditions: [
{ kind: 'walk_to_and_sit', weight: 1 },
{ kind: 'near_object', weight: 1, params: {} },
{ kind: 'accessible', weight: 1 },
],
color: '#d7ccc5',
};
const setRoom = (room: Partial<RoomConfig>) => {
onStateChange({ ...state, room: { ...state.room, ...room } });
};
@@ -88,7 +34,7 @@ export function ControlPanel({
};
const placeEntrance = () => {
onStateChange({ ...state, entrance: { x: 20, y: 20, width: 8, height: 8 } });
onStateChange({ ...state, entrance: { x: 20, y: 20, width: 20, height: 20 } });
};
const toggleLock = () => {
@@ -128,6 +74,16 @@ export function ControlPanel({
onStateChange({ ...state, furniture: updated });
};
const updateSelectedSize = (width: number, height: number) => {
if (!selectedId) return;
const updated = state.furniture.map((f) =>
f.id === selectedId
? { ...f, shape: { ...f.shape, width: Math.max(10, width), height: Math.max(10, height) } }
: f,
);
onStateChange({ ...state, furniture: updated });
};
const selected = state.furniture.find((f) => f.id === selectedId);
return (
@@ -162,29 +118,10 @@ export function ControlPanel({
style={{ width: 80, marginLeft: 4 }}
/>
</label>
<div style={{ marginTop: 8 }}>
<label>
Object Count:
<input
type="number"
min={0}
value={state.objectCount ?? state.furniture.length}
onChange={(e) =>
onStateChange({ ...state, objectCount: Math.max(0, Number(e.target.value)) })
}
style={{ width: 80, marginLeft: 4 }}
/>
</label>
</div>
<div style={{ marginTop: 8 }}>
<button onClick={addFurniture}>Add Furniture</button>
<button onClick={placeEntrance} style={{ marginLeft: 8 }}>
Place Entrance
</button> <button onClick={() => addPreset(tablePreset)} style={{ marginLeft: 8 }}>
Add Table
</button>
<button onClick={() => addPreset(chairPreset)} style={{ marginLeft: 8 }}>
Add Chair
</button>
</div>
{state.entrance && (
@@ -209,8 +146,53 @@ export function ControlPanel({
</label>
</div>
)}
<h3 style={{ marginTop: 12 }}>Premade Objects</h3>
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span>Tables:</span>
<input
type="number"
min={0}
value={state.objectAmounts?.table ?? 0}
onChange={(e) =>
onStateChange({
...state,
objectAmounts: { ...(state.objectAmounts || {}), table: Math.max(0, Number(e.target.value)) },
})
}
style={{ width: 60 }}
/>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 }}>
<span>Chairs:</span>
<input
type="number"
min={0}
value={state.objectAmounts?.chair ?? 0}
onChange={(e) =>
onStateChange({
...state,
objectAmounts: { ...(state.objectAmounts || {}), chair: Math.max(0, Number(e.target.value)) },
})
}
style={{ width: 60 }}
/>
</div>
</div>
<h3 style={{ marginTop: 12 }}>Objects</h3>
<div>
{state.entrance && (
<div
onClick={() => onSelect('__entrance__')}
style={{
padding: 4,
background: selectedId === '__entrance__' ? '#eee' : 'transparent',
cursor: 'pointer',
}}
>
Entrance
</div>
)}
{state.furniture.map((f) => (
<div
key={f.id}
@@ -225,6 +207,51 @@ export function ControlPanel({
</div>
))}
</div>
{selectedId === '__entrance__' && state.entrance && (
<div style={{ marginTop: 8 }}>
<div>Entrance Settings</div>
<label>
X:
<input
type="number"
value={state.entrance.x}
onChange={(e) => setEntrance({ x: Number(e.target.value) })}
style={{ width: 60, marginLeft: 4 }}
/>
</label>
<label style={{ marginLeft: 8 }}>
Y:
<input
type="number"
value={state.entrance.y}
onChange={(e) => setEntrance({ y: Number(e.target.value) })}
style={{ width: 60, marginLeft: 4 }}
/>
</label>
<div style={{ marginTop: 4 }}>
<label>
W:
<input
type="number"
min={10}
value={state.entrance.width ?? 20}
onChange={(e) => setEntrance({ width: Math.max(10, Number(e.target.value)) })}
style={{ width: 60, marginLeft: 4 }}
/>
</label>
<label style={{ marginLeft: 8 }}>
H:
<input
type="number"
min={10}
value={state.entrance.height ?? 20}
onChange={(e) => setEntrance({ height: Math.max(10, Number(e.target.value)) })}
style={{ width: 60, marginLeft: 4 }}
/>
</label>
</div>
</div>
)}
{selected && (
<div style={{ marginTop: 8 }}>
<div>
@@ -243,6 +270,29 @@ export function ControlPanel({
<div style={{ marginTop: 4 }}>
<button onClick={toggleLock}>{selected.lockedForOptimization ? 'Unlock' : 'Lock'} for opt</button>
</div>
<div style={{ marginTop: 8 }}>
<div>Size:</div>
<label>
W:
<input
type="number"
min={10}
value={selected.shape.width}
onChange={(e) => updateSelectedSize(Number(e.target.value), selected.shape.height)}
style={{ width: 60, marginLeft: 4 }}
/>
</label>
<label style={{ marginLeft: 8 }}>
H:
<input
type="number"
min={10}
value={selected.shape.height}
onChange={(e) => updateSelectedSize(selected.shape.width, Number(e.target.value))}
style={{ width: 60, marginLeft: 4 }}
/>
</label>
</div>
<div style={{ marginTop: 8 }}>
<div>Conditions:</div>
{(['walk_to_and_sit', 'around_open_area', 'near_entrance', 'near_object', 'accessible', 'not_blocking_path', 'open_circulation'] as ConditionKind[]).map(