123 lines
3.1 KiB
TypeScript
123 lines
3.1 KiB
TypeScript
"use client";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { Panorama } from "./Panorama";
|
|
import { api } from "./StartGame";
|
|
|
|
export function PanoramaPreview({
|
|
endpoint,
|
|
initialId,
|
|
onClose,
|
|
}: {
|
|
endpoint: string;
|
|
initialId?: string;
|
|
onClose: () => void;
|
|
}) {
|
|
const dialog = useRef<HTMLDialogElement>(null);
|
|
const [locations, setLocations] = useState<string[]>([]),
|
|
[selected, setSelected] = useState(initialId ?? ""),
|
|
[urls, setUrls] = useState<string[] | null>(null),
|
|
[ready, setReady] = useState(false),
|
|
[error, setError] = useState("");
|
|
useEffect(() => {
|
|
dialog.current?.showModal();
|
|
let disposed = false;
|
|
api<{ locations: string[] }>(endpoint)
|
|
.then((d) => {
|
|
if (disposed) return;
|
|
setLocations(d.locations);
|
|
setSelected((s) =>
|
|
d.locations.includes(s) ? s : (d.locations[0] ?? ""),
|
|
);
|
|
})
|
|
.catch((e) => {
|
|
if (!disposed) setError(e.message);
|
|
});
|
|
return () => {
|
|
disposed = true;
|
|
};
|
|
}, [endpoint]);
|
|
useEffect(() => {
|
|
if (!selected) return;
|
|
let disposed = false;
|
|
setUrls(null);
|
|
setReady(false);
|
|
setError("");
|
|
api<{ faces: string[] }>(`${endpoint}/${selected}`)
|
|
.then((d) => {
|
|
if (!disposed) setUrls(d.faces);
|
|
})
|
|
.catch((e) => {
|
|
if (!disposed) setError(e.message);
|
|
});
|
|
return () => {
|
|
disposed = true;
|
|
};
|
|
}, [endpoint, selected]);
|
|
const index = locations.indexOf(selected);
|
|
return (
|
|
<dialog
|
|
ref={dialog}
|
|
className="panorama-preview"
|
|
aria-label="Inspect panorama"
|
|
onCancel={onClose}
|
|
>
|
|
<div className="preview-toolbar">
|
|
<label>
|
|
Location{" "}
|
|
<select
|
|
aria-label="Preview location"
|
|
value={selected}
|
|
onChange={(e) => setSelected(e.target.value)}
|
|
>
|
|
{locations.map((p) => (
|
|
<option key={p}>{p}</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<button
|
|
className="button"
|
|
disabled={index <= 0}
|
|
onClick={() => setSelected(locations[index - 1])}
|
|
>
|
|
Previous
|
|
</button>
|
|
<button
|
|
className="button"
|
|
disabled={index < 0 || index >= locations.length - 1}
|
|
onClick={() => setSelected(locations[index + 1])}
|
|
>
|
|
Next
|
|
</button>
|
|
<button className="button" onClick={onClose}>
|
|
Close preview
|
|
</button>
|
|
</div>
|
|
<div className="preview-viewport">
|
|
{urls && (
|
|
<Panorama
|
|
urls={urls}
|
|
zoom
|
|
onReady={() => setReady(true)}
|
|
onError={setError}
|
|
/>
|
|
)}
|
|
{error ? (
|
|
<p role="alert" className="preview-status">
|
|
{error}
|
|
</p>
|
|
) : (
|
|
!ready && (
|
|
<p role="status" className="preview-status">
|
|
Loading panorama…
|
|
</p>
|
|
)
|
|
)}
|
|
</div>
|
|
<p className="muted">
|
|
Drag to look around · Scroll to zoom · Check for blocked views and
|
|
missing terrain.
|
|
</p>
|
|
</dialog>
|
|
);
|
|
}
|