pstream is dead; long live pstream
taciturnaxolotl.github.io/pstream-ng/
1import { useCallback, useEffect, useMemo } from "react";
2
3import { useQueryParam } from "@/hooks/useQueryParams";
4import { useOverlayStore } from "@/stores/overlay/store";
5
6function splitPath(path: string, prefix?: string): string[] {
7 const parts = [prefix ?? "", ...path.split("/")];
8 return parts.filter((v) => v.length > 0);
9}
10
11function joinPath(path: string[]): string {
12 return `/${path.join("/")}`;
13}
14
15export function useRouterAnchorUpdate(id: string) {
16 const [route] = useQueryParam("r");
17 const setAnchorPoint = useOverlayStore((s) => s.setAnchorPoint);
18 const routerActive = useMemo(
19 () => !!route && route.startsWith(`/${id}`),
20 [route, id],
21 );
22
23 const update = useCallback(() => {
24 if (!routerActive) return;
25 const anchor = document.getElementById(`__overlayRouter::${id}`);
26 if (anchor) {
27 const rect = anchor.getBoundingClientRect();
28 setAnchorPoint({
29 h: rect.height,
30 w: rect.width,
31 x: rect.x,
32 y: rect.y,
33 });
34 }
35 }, [routerActive, setAnchorPoint, id]);
36
37 useEffect(() => {
38 update();
39 }, [routerActive, update]);
40
41 useEffect(() => {
42 function resizeEvent() {
43 update();
44 }
45 window.addEventListener("resize", resizeEvent);
46 return () => {
47 window.removeEventListener("resize", resizeEvent);
48 };
49 }, [update]);
50}
51
52export function useInternalOverlayRouter(id: string) {
53 const [route, setRoute] = useQueryParam("r");
54 const transition = useOverlayStore((s) => s.transition);
55 const setTransition = useOverlayStore((s) => s.setTransition);
56 const routerActive = !!route && route.startsWith(`/${id}`);
57
58 function makePath(path: string) {
59 return joinPath(splitPath(path, id));
60 }
61
62 function navigate(path: string) {
63 const oldRoute = route;
64 const newRoute = joinPath(splitPath(path, id));
65 setTransition({
66 from: oldRoute ?? "/",
67 to: newRoute,
68 });
69 setRoute(newRoute);
70 }
71
72 function showBackwardsTransition(path: string) {
73 if (!transition) return "none";
74 const current = joinPath(splitPath(path, id));
75
76 if (current === transition.to && transition.from.startsWith(transition.to))
77 return "yes";
78 if (
79 current === transition.from &&
80 transition.to.startsWith(transition.from)
81 )
82 return "yes";
83 return "no";
84 }
85
86 function isCurrentPage(path: string) {
87 return routerActive && route === joinPath(splitPath(path, id));
88 }
89
90 function isOverlayActive() {
91 return routerActive;
92 }
93
94 const close = useCallback(
95 (preventRouteClear?: boolean) => {
96 if (route && !preventRouteClear) setRoute(null);
97 setTransition(null);
98 },
99 [setRoute, route, setTransition],
100 );
101
102 const open = useCallback(
103 (defaultRoute = "/") => {
104 setTransition(null);
105 setRoute(joinPath(splitPath(defaultRoute, id)));
106 },
107 [id, setRoute, setTransition],
108 );
109
110 const activeRoute = routerActive
111 ? joinPath(splitPath(route.slice(`/${id}`.length)))
112 : "/";
113
114 return {
115 activeRoute,
116 showBackwardsTransition,
117 isCurrentPage,
118 isOverlayActive,
119 navigate,
120 close,
121 open,
122 makePath,
123 currentRoute: route,
124 };
125}
126
127export function useOverlayRouter(id: string) {
128 const router = useInternalOverlayRouter(id);
129 return {
130 id,
131 route: router.activeRoute,
132 isRouterActive: router.isOverlayActive(),
133 open: router.open,
134 close: router.close,
135 navigate: router.navigate,
136 };
137}