auto-git:
[change] src/app/App.tsx
This commit is contained in:
407
src/app/App.tsx
407
src/app/App.tsx
@@ -22563,6 +22563,413 @@ export function App({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-section">
|
||||
<div className="label">Spline Repeaters</div>
|
||||
<div className="material-summary">
|
||||
Place bundled corridor GLBs along this spline. Repeaters
|
||||
work even when the road corridor is disabled.
|
||||
</div>
|
||||
<div className="inline-actions">
|
||||
<button
|
||||
className="toolbar__button"
|
||||
data-testid="add-path-repeater"
|
||||
type="button"
|
||||
onClick={handleAddPathRepeater}
|
||||
>
|
||||
Add Repeater
|
||||
</button>
|
||||
</div>
|
||||
{selectedPath.repeaters.length === 0 ? (
|
||||
<div className="material-summary">
|
||||
No repeaters on this path yet.
|
||||
</div>
|
||||
) : null}
|
||||
{selectedPath.repeaters.map((repeater, repeaterIndex) => {
|
||||
const selectedAsset =
|
||||
BUNDLED_SPLINE_CORRIDOR_ASSETS.find(
|
||||
(asset) => asset.id === repeater.assetId
|
||||
) ?? BUNDLED_SPLINE_CORRIDOR_ASSETS[0];
|
||||
const repeaterLabel =
|
||||
repeater.name ??
|
||||
selectedAsset?.label ??
|
||||
`Repeater ${repeaterIndex + 1}`;
|
||||
|
||||
return (
|
||||
<div className="form-section" key={repeater.id}>
|
||||
<div className="label">{repeaterLabel}</div>
|
||||
<label className="form-field form-field--toggle">
|
||||
<span className="label">Enabled</span>
|
||||
<input
|
||||
data-testid={`path-repeater-${repeaterIndex}-enabled`}
|
||||
type="checkbox"
|
||||
checked={repeater.enabled}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
enabled: event.currentTarget.checked
|
||||
},
|
||||
event.currentTarget.checked
|
||||
? "Enabled Path repeater."
|
||||
: "Disabled Path repeater."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Asset</span>
|
||||
<select
|
||||
className="select-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-asset`}
|
||||
value={repeater.assetId}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
assetId: normalizeScenePathRepeaterAssetId(
|
||||
event.currentTarget.value
|
||||
)
|
||||
},
|
||||
"Updated Path repeater asset."
|
||||
)
|
||||
}
|
||||
>
|
||||
{BUNDLED_SPLINE_CORRIDOR_ASSETS.map((asset) => (
|
||||
<option key={asset.id} value={asset.id}>
|
||||
{getSplineCorridorAssetCategoryLabel(
|
||||
asset.category
|
||||
)}
|
||||
{" - "}
|
||||
{asset.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Placement</span>
|
||||
<select
|
||||
className="select-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-placement`}
|
||||
value={repeater.placement}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
placement:
|
||||
normalizeScenePathRepeaterPlacement(
|
||||
event.currentTarget.value
|
||||
)
|
||||
},
|
||||
"Updated Path repeater placement."
|
||||
)
|
||||
}
|
||||
>
|
||||
{(["center", "left", "right"] as const).map(
|
||||
(placement) => (
|
||||
<option key={placement} value={placement}>
|
||||
{getPathRepeaterPlacementLabel(placement)}
|
||||
</option>
|
||||
)
|
||||
)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Offset</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-offset`}
|
||||
type="number"
|
||||
min={0}
|
||||
step={0.1}
|
||||
value={repeater.offset}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
offset: normalizeScenePathRepeaterOffset(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater offset."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Spacing</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-spacing`}
|
||||
type="number"
|
||||
min={0.05}
|
||||
step={0.1}
|
||||
value={repeater.spacing}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
spacing: normalizeScenePathRepeaterSpacing(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater spacing."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Start Inset</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-start-inset`}
|
||||
type="number"
|
||||
min={0}
|
||||
step={0.1}
|
||||
value={repeater.startInset}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
startInset: normalizeScenePathRepeaterInset(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater start inset."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">End Inset</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-end-inset`}
|
||||
type="number"
|
||||
min={0}
|
||||
step={0.1}
|
||||
value={repeater.endInset}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
endInset: normalizeScenePathRepeaterInset(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater end inset."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Scale</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-scale`}
|
||||
type="number"
|
||||
min={0.01}
|
||||
step={0.05}
|
||||
value={repeater.scale}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
scale: normalizeScenePathRepeaterScale(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater scale."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Random Scale</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-random-scale`}
|
||||
type="number"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
value={repeater.randomScale}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
randomScale:
|
||||
normalizeScenePathRepeaterRandomScale(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater random scale."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Random Yaw</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-random-yaw`}
|
||||
type="number"
|
||||
min={0}
|
||||
max={180}
|
||||
step={1}
|
||||
value={repeater.randomYawDegrees}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
randomYawDegrees:
|
||||
normalizeScenePathRepeaterRandomYawDegrees(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater random yaw."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Yaw Offset</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-yaw-offset`}
|
||||
type="number"
|
||||
step={1}
|
||||
value={repeater.yawOffsetDegrees}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
yawOffsetDegrees:
|
||||
normalizeScenePathRepeaterYawOffsetDegrees(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater yaw offset."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field form-field--toggle">
|
||||
<span className="label">Align to spline</span>
|
||||
<input
|
||||
data-testid={`path-repeater-${repeaterIndex}-align`}
|
||||
type="checkbox"
|
||||
checked={repeater.alignToSpline}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
alignToSpline: event.currentTarget.checked
|
||||
},
|
||||
"Updated Path repeater alignment."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field form-field--toggle">
|
||||
<span className="label">Conform to terrain</span>
|
||||
<input
|
||||
data-testid={`path-repeater-${repeaterIndex}-terrain-conform`}
|
||||
type="checkbox"
|
||||
checked={repeater.terrainConform}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
terrainConform: event.currentTarget.checked
|
||||
},
|
||||
"Updated Path repeater terrain conform."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Height Offset</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-height-offset`}
|
||||
type="number"
|
||||
step={0.05}
|
||||
value={repeater.heightOffset}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
heightOffset:
|
||||
normalizeScenePathRepeaterHeightOffset(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater height offset."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span className="label">Seed</span>
|
||||
<input
|
||||
className="text-input"
|
||||
data-testid={`path-repeater-${repeaterIndex}-seed`}
|
||||
type="number"
|
||||
step={1}
|
||||
value={repeater.seed}
|
||||
onChange={(event) =>
|
||||
handlePathRepeaterChange(
|
||||
repeaterIndex,
|
||||
{
|
||||
...repeater,
|
||||
seed: Math.trunc(
|
||||
Number(event.currentTarget.value)
|
||||
)
|
||||
},
|
||||
"Updated Path repeater seed."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<div className="inline-actions">
|
||||
<button
|
||||
className="toolbar__button toolbar__button--danger"
|
||||
data-testid={`delete-path-repeater-${repeaterIndex}`}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDeletePathRepeater(repeaterIndex)
|
||||
}
|
||||
>
|
||||
Remove Repeater
|
||||
</button>
|
||||
</div>
|
||||
{selectedAsset === undefined ? null : (
|
||||
<div className="material-summary">
|
||||
{selectedAsset.intendedUse} Dimensions:{" "}
|
||||
{selectedAsset.dimensions.widthX}m x{" "}
|
||||
{selectedAsset.dimensions.heightY}m x{" "}
|
||||
{selectedAsset.dimensions.lengthZ}m.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="form-section">
|
||||
<div className="label">Points</div>
|
||||
<div className="material-summary">
|
||||
|
||||
Reference in New Issue
Block a user