Files
MineGeo/components/PanoramaPreview.tsx

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>
);
}