add fog URL param

This commit is contained in:
Brian Beck 2026-02-13 21:12:12 -08:00
parent d7ef09c576
commit c380893040
27 changed files with 100 additions and 43 deletions

View file

@ -2,6 +2,7 @@ import { RefObject, useCallback, useRef, useState } from "react";
import { FaMapPin } from "react-icons/fa";
import { FaClipboardCheck } from "react-icons/fa6";
import { Camera, Quaternion, Vector3 } from "three";
import { useSettings } from "./SettingsProvider";
function encodeViewHash({
position,
@ -18,9 +19,14 @@ function encodeViewHash({
export function CopyCoordinatesButton({
cameraRef,
missionName,
missionType,
}: {
cameraRef: RefObject<Camera | null>;
missionName: string;
missionType: string;
}) {
const { fogEnabled } = useSettings();
const [showCopied, setShowCopied] = useState(false);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@ -29,8 +35,10 @@ export function CopyCoordinatesButton({
const camera = cameraRef.current;
if (!camera) return;
const hash = encodeViewHash(camera);
// Update the URL hash
const fullPath = `${window.location.pathname}${window.location.search}${hash}`;
const params = new URLSearchParams();
params.set("mission", `${missionName}~${missionType}`);
params.set("fog", fogEnabled.toString());
const fullPath = `${window.location.pathname}?${params}${hash}`;
const fullUrl = `${window.location.origin}${fullPath}`;
window.history.replaceState(null, "", fullPath);
try {
@ -42,7 +50,7 @@ export function CopyCoordinatesButton({
} catch (err) {
console.error(err);
}
}, [cameraRef]);
}, [cameraRef, missionName, missionType, fogEnabled]);
return (
<button

View file

@ -73,7 +73,11 @@ export function InspectorControls({
const settingsFields = (
<>
<div className="Controls-group">
<CopyCoordinatesButton cameraRef={cameraRef} />
<CopyCoordinatesButton
cameraRef={cameraRef}
missionName={missionName}
missionType={missionType}
/>
</div>
<div className="Controls-group">
<div className="CheckboxField">

View file

@ -1,6 +1,7 @@
import {
createContext,
ReactNode,
useCallback,
useContext,
useEffect,
useLayoutEffect,
@ -65,7 +66,15 @@ export function useControls() {
return useContext(ControlsContext);
}
export function SettingsProvider({ children }: { children: ReactNode }) {
export function SettingsProvider({
children,
fogEnabledOverride,
onClearFogEnabledOverride,
}: {
children: ReactNode;
fogEnabledOverride?: boolean | null;
onClearFogEnabledOverride: () => void;
}) {
const [fogEnabled, setFogEnabled] = useState(true);
const [highQualityFog, setHighQualityFog] = useState(false);
const [speedMultiplier, setSpeedMultiplier] = useState(1);
@ -75,10 +84,18 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
const [debugMode, setDebugMode] = useState(false);
const [touchMode, setTouchMode] = useState<TouchMode>("moveLookStick");
const setFogEnabledWithoutOverride: StateSetter<boolean> = useCallback(
(value) => {
setFogEnabled(value);
onClearFogEnabledOverride();
},
[onClearFogEnabledOverride],
);
const settingsContext: SettingsContext = useMemo(
() => ({
fogEnabled,
setFogEnabled,
fogEnabled: fogEnabledOverride ?? fogEnabled,
setFogEnabled: setFogEnabledWithoutOverride,
highQualityFog,
setHighQualityFog,
fov,
@ -88,7 +105,15 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
animationEnabled,
setAnimationEnabled,
}),
[fogEnabled, highQualityFog, fov, audioEnabled, animationEnabled],
[
fogEnabled,
fogEnabledOverride,
setFogEnabledWithoutOverride,
highQualityFog,
fov,
audioEnabled,
animationEnabled,
],
);
const debugContext: DebugContext = useMemo(
@ -144,7 +169,7 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
clearTimeout(saveTimerRef.current);
}
// Debounce localStorage writes (wait 300ms after last change)
// Debounce localStorage writes
saveTimerRef.current = setTimeout(() => {
const settingsToSave: PersistedSettings = {
fogEnabled,

View file

@ -1,24 +1,32 @@
import { useEffect, useState } from "react";
import { useCallback, useRef, useSyncExternalStore } from "react";
// Only check pointer: coarse. Adding "hover: none" would be more precise but
// Samsung Android devices incorrectly report hover: hover for touchscreens.
// See: https://www.ctrl.blog/entry/css-media-hover-samsung.html
const query = "(pointer: coarse)";
const getServerSnapshot = () => null;
export function useTouchDevice() {
const [isTouch, setIsTouch] = useState<boolean | null>(null);
const queryRef = useRef<ReturnType<typeof window.matchMedia>>(null);
useEffect(() => {
const subscribe = useCallback((onStoreChange: () => void) => {
const mql = window.matchMedia(query);
setIsTouch(mql.matches);
const handleChange = (e: MediaQueryListEvent) => {
setIsTouch(e.matches);
};
mql.addEventListener("change", handleChange);
mql.addEventListener("change", onStoreChange);
queryRef.current = mql;
return () => {
mql.removeEventListener("change", handleChange);
mql.removeEventListener("change", onStoreChange);
};
}, []);
const getSnapshot = useCallback(() => {
return queryRef.current.matches;
}, []);
const isTouch = useSyncExternalStore<boolean | null>(
subscribe,
getSnapshot,
getServerSnapshot,
);
return isTouch;
}