akanjs 2.2.7 → 2.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -2
- package/client/clientRuntime.ts +3 -0
- package/client/csrTypes.ts +1 -0
- package/client/makePageProto.tsx +5 -2
- package/client/translator.ts +7 -4
- package/common/fileUpload.ts +1 -1
- package/common/index.ts +5 -1
- package/constant/getDefault.ts +1 -1
- package/dictionary/base.dictionary.ts +0 -1
- package/fetch/client/fetchClient.ts +21 -24
- package/fetch/client/wsClient.ts +8 -0
- package/fetch/serializer/fetch.serializer.ts +1 -0
- package/package.json +1 -5
- package/server/hmr/devHmrController.ts +1 -0
- package/server/routeTreeBuilder.ts +1 -0
- package/server/ssrFromRscRenderer.tsx +34 -12
- package/server/ssrTypes.ts +5 -6
- package/service/base.service.ts +0 -4
- package/service/injectInfo.ts +49 -12
- package/service/predefinedAdaptor/cache.adaptor.ts +13 -0
- package/service/predefinedAdaptor/database.adaptor.ts +74 -16
- package/service/predefinedAdaptor/solidCache.adaptor.ts +23 -0
- package/signal/base.signal.ts +0 -5
- package/signal/serializer/fetch.serializer.ts +1 -0
- package/signal/types.ts +3 -0
- package/store/action.ts +15 -3
- package/store/storeInstance.ts +50 -3
- package/types/client/csrTypes.d.ts +1 -0
- package/types/client/translator.d.ts +1 -0
- package/types/common/fileUpload.d.ts +1 -1
- package/types/common/index.d.ts +1 -1
- package/types/server/ssrTypes.d.ts +5 -6
- package/types/service/base.service.d.ts +0 -1
- package/types/service/injectInfo.d.ts +8 -2
- package/types/service/predefinedAdaptor/cache.adaptor.d.ts +6 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +3 -1
- package/types/service/predefinedAdaptor/solidCache.adaptor.d.ts +3 -0
- package/types/signal/base.signal.d.ts +0 -3
- package/types/signal/types.d.ts +3 -0
- package/types/ui/Dialog/Modal.d.ts +1 -1
- package/types/ui/Dialog/index.d.ts +1 -1
- package/types/ui/Modal.d.ts +1 -12
- package/types/ui/System/CSR.d.ts +2 -2
- package/types/ui/System/Client.d.ts +5 -4
- package/types/ui/System/Common.d.ts +2 -0
- package/types/ui/System/SSR.d.ts +2 -2
- package/ui/Dialog/Modal.tsx +181 -70
- package/ui/Dialog/Provider.tsx +3 -6
- package/ui/Modal.tsx +0 -44
- package/ui/System/CSR.tsx +9 -1
- package/ui/System/Client.tsx +27 -62
- package/ui/System/Common.tsx +2 -0
- package/ui/System/SSR.tsx +9 -1
- package/webkit/bootCsr.tsx +1 -0
package/ui/Dialog/Modal.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import * as Dialog from "@radix-ui/react-dialog";
|
|
3
2
|
import { useDrag } from "@use-gesture/react";
|
|
4
3
|
import { clsx, usePage } from "akanjs/client";
|
|
5
4
|
import { animated } from "akanjs/ui";
|
|
6
|
-
import { type ReactNode, useContext, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { type ReactNode, useCallback, useContext, useEffect, useId, useRef, useState } from "react";
|
|
6
|
+
import { createPortal } from "react-dom";
|
|
7
7
|
import { BiX } from "react-icons/bi";
|
|
8
8
|
import { config, useSpring } from "react-spring";
|
|
9
9
|
|
|
@@ -11,6 +11,8 @@ import { DialogContext } from "./context";
|
|
|
11
11
|
|
|
12
12
|
const MODAL_MARGIN = 0;
|
|
13
13
|
const OPACITY = { START: 0, END: 1 };
|
|
14
|
+
let bodyScrollLockCount = 0;
|
|
15
|
+
let previousBodyOverflow = "";
|
|
14
16
|
|
|
15
17
|
const interpolate = (o: number, i: number, t: number) => {
|
|
16
18
|
return o + (i - o) * t;
|
|
@@ -25,41 +27,80 @@ export interface ModalProps {
|
|
|
25
27
|
}
|
|
26
28
|
export const Modal = ({ className, bodyClassName, confirmClose, children, onCancel }: ModalProps) => {
|
|
27
29
|
const { open, setOpen, title, action } = useContext(DialogContext);
|
|
28
|
-
const openRef = useRef<boolean>(open);
|
|
29
30
|
const { l } = usePage();
|
|
30
31
|
const ref = useRef<HTMLDivElement>(null);
|
|
32
|
+
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
33
|
+
const closingRef = useRef(false);
|
|
34
|
+
const focusedElementRef = useRef<HTMLElement | null>(null);
|
|
35
|
+
const titleId = useId();
|
|
36
|
+
const contentId = useId();
|
|
31
37
|
const [{ translate }, api] = useSpring(() => ({ translate: 1 }));
|
|
38
|
+
const [portalElement, setPortalElement] = useState<HTMLElement | null>(null);
|
|
39
|
+
const [isMounted, setIsMounted] = useState(open);
|
|
32
40
|
const [showBackground, setShowBackground] = useState(false);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
|
|
42
|
+
const openModal = useCallback(
|
|
43
|
+
async ({ canceled }: { canceled?: boolean } = {}) => {
|
|
44
|
+
closingRef.current = false;
|
|
45
|
+
setIsMounted(true);
|
|
46
|
+
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
47
|
+
closeTimerRef.current = setTimeout(() => {
|
|
48
|
+
setShowBackground(true);
|
|
49
|
+
}, 100);
|
|
50
|
+
await Promise.all(api.start({ translate: 0, immediate: false, config: canceled ? config.wobbly : config.stiff }));
|
|
51
|
+
},
|
|
52
|
+
[api],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const closeModal = useCallback(
|
|
56
|
+
async ({
|
|
57
|
+
velocity = 0,
|
|
58
|
+
confirmClose,
|
|
59
|
+
notifyCancel = true,
|
|
60
|
+
}: {
|
|
61
|
+
velocity?: number;
|
|
62
|
+
confirmClose?: boolean;
|
|
63
|
+
notifyCancel?: boolean;
|
|
64
|
+
}) => {
|
|
65
|
+
if (closingRef.current) return;
|
|
66
|
+
if (confirmClose && !window.confirm(l("base.confirmClose"))) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
closingRef.current = true;
|
|
71
|
+
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
72
|
+
closeTimerRef.current = setTimeout(() => {
|
|
73
|
+
setShowBackground(false);
|
|
74
|
+
}, 100);
|
|
75
|
+
await Promise.all(api.start({ translate: 1, immediate: false, config: { ...config.stiff, velocity } }));
|
|
76
|
+
setIsMounted(false);
|
|
77
|
+
setOpen(false);
|
|
78
|
+
if (notifyCancel) onCancel?.();
|
|
79
|
+
closingRef.current = false;
|
|
80
|
+
},
|
|
81
|
+
[api, l, onCancel, setOpen],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const requestClose = useCallback(
|
|
85
|
+
(options?: { velocity?: number }) => {
|
|
86
|
+
void closeModal({ velocity: options?.velocity, confirmClose });
|
|
87
|
+
},
|
|
88
|
+
[closeModal, confirmClose],
|
|
89
|
+
);
|
|
90
|
+
|
|
50
91
|
const bind = useDrag(
|
|
51
92
|
({ last, velocity: [, vy], direction: [, dy], offset: [, oy], movement: [, my], cancel, canceled }) => {
|
|
52
93
|
if (!ref.current) return;
|
|
53
|
-
const height = (ref.current.clientHeight || MODAL_MARGIN) - MODAL_MARGIN;
|
|
94
|
+
const height = Math.max((ref.current.clientHeight || MODAL_MARGIN) - MODAL_MARGIN, 1);
|
|
54
95
|
if (my > 70) cancel();
|
|
55
96
|
if (last) {
|
|
56
|
-
if (my > height * 0.5 || (vy > 0.5 && dy > 0))
|
|
57
|
-
void closeModal({ velocity: vy / height, confirmClose: confirmClose });
|
|
97
|
+
if (my > height * 0.5 || (vy > 0.5 && dy > 0)) requestClose({ velocity: vy / height });
|
|
58
98
|
else void openModal({ canceled });
|
|
59
99
|
} else void api.start({ translate: oy / height, immediate: true });
|
|
60
100
|
},
|
|
61
101
|
{ from: () => [0, translate.get()], filterTaps: true, bounds: { top: 0 }, rubberband: true },
|
|
62
102
|
);
|
|
103
|
+
|
|
63
104
|
const opacity = translate.to((t) => {
|
|
64
105
|
return interpolate(OPACITY.END, OPACITY.START, t);
|
|
65
106
|
});
|
|
@@ -68,33 +109,104 @@ export const Modal = ({ className, bodyClassName, confirmClose, children, onCanc
|
|
|
68
109
|
});
|
|
69
110
|
|
|
70
111
|
useEffect(() => {
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
112
|
+
if (typeof document === "undefined") return;
|
|
113
|
+
setPortalElement(document.body);
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (open) {
|
|
118
|
+
void openModal();
|
|
119
|
+
}
|
|
120
|
+
}, [open, openModal]);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!open && isMounted) {
|
|
124
|
+
void closeModal({ notifyCancel: false });
|
|
125
|
+
}
|
|
126
|
+
}, [closeModal, isMounted, open]);
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (!isMounted || typeof document === "undefined") return;
|
|
130
|
+
|
|
131
|
+
bodyScrollLockCount += 1;
|
|
132
|
+
if (bodyScrollLockCount === 1) {
|
|
133
|
+
previousBodyOverflow = document.body.style.overflow;
|
|
134
|
+
document.body.style.overflow = "hidden";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
bodyScrollLockCount -= 1;
|
|
139
|
+
if (bodyScrollLockCount === 0) {
|
|
140
|
+
document.body.style.overflow = previousBodyOverflow;
|
|
141
|
+
previousBodyOverflow = "";
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}, [isMounted]);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (!isMounted || !portalElement || typeof document === "undefined") return;
|
|
148
|
+
|
|
149
|
+
focusedElementRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
150
|
+
queueMicrotask(() => {
|
|
151
|
+
ref.current?.focus();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return () => {
|
|
155
|
+
if (focusedElementRef.current && document.contains(focusedElementRef.current)) {
|
|
156
|
+
focusedElementRef.current.focus();
|
|
157
|
+
}
|
|
158
|
+
focusedElementRef.current = null;
|
|
159
|
+
};
|
|
160
|
+
}, [isMounted, portalElement]);
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!isMounted) return;
|
|
164
|
+
|
|
165
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
166
|
+
if (event.key !== "Escape") return;
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
requestClose();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
window.addEventListener("keydown", onKeyDown);
|
|
172
|
+
return () => {
|
|
173
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
174
|
+
};
|
|
175
|
+
}, [isMounted, requestClose]);
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
return () => {
|
|
179
|
+
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
180
|
+
};
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
if (!isMounted || !portalElement) return null;
|
|
184
|
+
|
|
185
|
+
return createPortal(
|
|
186
|
+
<>
|
|
187
|
+
<div
|
|
188
|
+
className={clsx("fixed inset-0 z-10", showBackground && "animate-fadeIn bg-black/50 backdrop-blur-md")}
|
|
189
|
+
onClick={(event) => {
|
|
190
|
+
if (event.target !== event.currentTarget) return;
|
|
191
|
+
requestClose();
|
|
82
192
|
}}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<div className={"fixed inset-0 z-10 bg-base-content/50 backdrop-blur-md data-[state=open]:animate-fadeIn"} />
|
|
86
|
-
) : null}
|
|
87
|
-
</Dialog.Overlay>
|
|
88
|
-
<Dialog.Content
|
|
89
|
-
className="fixed top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center justify-center"
|
|
90
|
-
asChild
|
|
91
|
-
forceMount
|
|
92
|
-
>
|
|
193
|
+
/>
|
|
194
|
+
<div className="fixed top-1/2 left-1/2 z-10 flex -translate-x-1/2 -translate-y-1/2 items-center justify-center">
|
|
93
195
|
<div className="z-10">
|
|
94
|
-
<animated.div
|
|
196
|
+
<animated.div
|
|
197
|
+
ref={ref}
|
|
198
|
+
style={{ translateY, opacity }}
|
|
199
|
+
role="dialog"
|
|
200
|
+
aria-modal="true"
|
|
201
|
+
aria-labelledby={title ? titleId : undefined}
|
|
202
|
+
aria-describedby={contentId}
|
|
203
|
+
tabIndex={-1}
|
|
204
|
+
>
|
|
95
205
|
<button
|
|
206
|
+
type="button"
|
|
207
|
+
aria-label="Close"
|
|
96
208
|
className="btn btn-circle btn-sm absolute top-[-16px] right-0 z-20 md:top-[-40px]"
|
|
97
|
-
onClick={() =>
|
|
209
|
+
onClick={() => requestClose()}
|
|
98
210
|
>
|
|
99
211
|
<BiX className="text-3xl" />
|
|
100
212
|
</button>
|
|
@@ -104,34 +216,33 @@ export const Modal = ({ className, bodyClassName, confirmClose, children, onCanc
|
|
|
104
216
|
className,
|
|
105
217
|
)}
|
|
106
218
|
>
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
</div>
|
|
118
|
-
</animated.div>
|
|
119
|
-
</Dialog.Title>
|
|
120
|
-
<Dialog.Description asChild>
|
|
121
|
-
<div
|
|
122
|
-
className={clsx(
|
|
123
|
-
"scrollbar-none relative m-2 flex size-full min-w-[90vw] overflow-x-hidden overflow-y-scroll border-base-content/30 border-t-[0.1px] p-4 sm:p-4 md:min-w-[384px] md:px-8 lg:min-w-[576px] xl:min-w-[768px]",
|
|
124
|
-
bodyClassName,
|
|
125
|
-
)}
|
|
126
|
-
>
|
|
127
|
-
{children}
|
|
219
|
+
<animated.div
|
|
220
|
+
{...bind()}
|
|
221
|
+
id={titleId}
|
|
222
|
+
className="relative z-10 flex w-full animate-fadeIn cursor-pointer touch-pan-y flex-col items-center justify-center px-4 pt-1"
|
|
223
|
+
>
|
|
224
|
+
<div className="flex w-full cursor-pointer items-center justify-center pt-1 opacity-50">
|
|
225
|
+
<div className="h-1 w-24 rounded-full bg-gray-500" />
|
|
226
|
+
</div>
|
|
227
|
+
<div className="flex w-full items-center justify-start">
|
|
228
|
+
<div className="w-full text-start font-bold text-lg">{title}</div>
|
|
128
229
|
</div>
|
|
129
|
-
</
|
|
230
|
+
</animated.div>
|
|
231
|
+
<div
|
|
232
|
+
id={contentId}
|
|
233
|
+
className={clsx(
|
|
234
|
+
"scrollbar-none relative m-2 flex size-full min-w-[90vw] overflow-x-hidden overflow-y-scroll border-base-content/30 border-t-[0.1px] p-4 sm:p-4 md:min-w-[384px] md:px-8 lg:min-w-[576px] xl:min-w-[768px]",
|
|
235
|
+
bodyClassName,
|
|
236
|
+
)}
|
|
237
|
+
>
|
|
238
|
+
{children}
|
|
239
|
+
</div>
|
|
130
240
|
{action ? <div className="w-full">{action}</div> : null}
|
|
131
241
|
</div>
|
|
132
242
|
</animated.div>
|
|
133
243
|
</div>
|
|
134
|
-
</
|
|
135
|
-
|
|
244
|
+
</div>
|
|
245
|
+
</>,
|
|
246
|
+
portalElement,
|
|
136
247
|
);
|
|
137
248
|
};
|
package/ui/Dialog/Provider.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import * as Dialog from "@radix-ui/react-dialog";
|
|
3
2
|
import { clsx } from "akanjs/client";
|
|
4
3
|
import { type ReactNode, useEffect, useState } from "react";
|
|
5
4
|
|
|
@@ -23,11 +22,9 @@ export const Provider = ({ className, defaultOpen = false, open = defaultOpen, c
|
|
|
23
22
|
}, [open]);
|
|
24
23
|
return (
|
|
25
24
|
<DialogContext.Provider value={{ open: openState, setOpen: setOpenState, title, setTitle, action, setAction }}>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</div>
|
|
30
|
-
</Dialog.Root>
|
|
25
|
+
<div data-open={openState} className={clsx("group/dialog", className)}>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
31
28
|
</DialogContext.Provider>
|
|
32
29
|
);
|
|
33
30
|
};
|
package/ui/Modal.tsx
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import * as RadixDialog from "@radix-ui/react-dialog";
|
|
3
2
|
import type { ReactNode } from "react";
|
|
4
|
-
import { BiX } from "react-icons/bi";
|
|
5
3
|
|
|
6
4
|
import { Dialog } from "./Dialog";
|
|
7
5
|
|
|
@@ -43,45 +41,3 @@ export const Modal = ({
|
|
|
43
41
|
</Dialog>
|
|
44
42
|
);
|
|
45
43
|
};
|
|
46
|
-
|
|
47
|
-
interface WindowProps {
|
|
48
|
-
open: boolean;
|
|
49
|
-
onCancel: () => void;
|
|
50
|
-
title: ReactNode;
|
|
51
|
-
children: ReactNode;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const Window = ({ open, onCancel, title, children }: WindowProps) => {
|
|
55
|
-
if (!open) return null;
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<RadixDialog.Root open={open}>
|
|
59
|
-
<RadixDialog.Portal>
|
|
60
|
-
<RadixDialog.Overlay className="fixed inset-0 bg-black/40" />
|
|
61
|
-
<RadixDialog.Content
|
|
62
|
-
className="fixed top-1/2 left-1/2 z-[2] w-[90%] min-w-auto -translate-x-1/2 -translate-y-1/2 animate-fadeIn rounded-[10px] border-[3px] border-black text-black backdrop-blur-lg md:w-fit"
|
|
63
|
-
style={{
|
|
64
|
-
background: `rgba(255, 255, 255, 0.3)`,
|
|
65
|
-
width: "406px",
|
|
66
|
-
}}
|
|
67
|
-
>
|
|
68
|
-
<RadixDialog.Title className="height-[36px] relative overflow-hidden rounded-t-[6px] border-black border-b-2 bg-white/60 text-center">
|
|
69
|
-
<div className="m-0 text-[22px]">{title}</div>
|
|
70
|
-
<RadixDialog.Close
|
|
71
|
-
onClick={() => {
|
|
72
|
-
onCancel();
|
|
73
|
-
}}
|
|
74
|
-
className="absolute top-0 right-0 flex h-[34px] w-[40px] cursor-pointer items-center justify-center border-black border-l-2"
|
|
75
|
-
>
|
|
76
|
-
<BiX className="text-[32px]" />
|
|
77
|
-
</RadixDialog.Close>
|
|
78
|
-
</RadixDialog.Title>
|
|
79
|
-
<RadixDialog.Description className="overflow-y-hidden rounded-b-[10px] p-2">
|
|
80
|
-
{children}
|
|
81
|
-
</RadixDialog.Description>
|
|
82
|
-
</RadixDialog.Content>
|
|
83
|
-
</RadixDialog.Portal>
|
|
84
|
-
</RadixDialog.Root>
|
|
85
|
-
);
|
|
86
|
-
};
|
|
87
|
-
Modal.Window = Window;
|
package/ui/System/CSR.tsx
CHANGED
|
@@ -45,6 +45,7 @@ const CSRProvider = ({
|
|
|
45
45
|
fonts,
|
|
46
46
|
layoutStyle = "web",
|
|
47
47
|
reconnect = getEnv().operationMode === "local",
|
|
48
|
+
wsConnect = true,
|
|
48
49
|
of,
|
|
49
50
|
}: CSRProviderProps) => {
|
|
50
51
|
return (
|
|
@@ -72,7 +73,14 @@ const CSRProvider = ({
|
|
|
72
73
|
</Client.Wrapper>
|
|
73
74
|
<Client.Inner />
|
|
74
75
|
<CSRInner />
|
|
75
|
-
<Client.Bridge
|
|
76
|
+
<Client.Bridge
|
|
77
|
+
lang={lang}
|
|
78
|
+
env={env}
|
|
79
|
+
theme={theme}
|
|
80
|
+
prefix={prefix}
|
|
81
|
+
gaTrackingId={gaTrackingId}
|
|
82
|
+
wsConnect={wsConnect}
|
|
83
|
+
/>
|
|
76
84
|
<CSRBridge lang={lang} prefix={prefix} />
|
|
77
85
|
</>
|
|
78
86
|
)}
|
package/ui/System/Client.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
clsx,
|
|
6
6
|
Device,
|
|
7
7
|
defaultPageState,
|
|
8
|
+
fetch,
|
|
8
9
|
getPathInfo,
|
|
9
10
|
initAuth,
|
|
10
11
|
type Location,
|
|
@@ -55,8 +56,10 @@ export const ClientWrapper = ({
|
|
|
55
56
|
reconnect = true,
|
|
56
57
|
}: ClientWrapperProps) => {
|
|
57
58
|
|
|
58
|
-
if (dictionary)
|
|
59
|
-
|
|
59
|
+
if (dictionary) {
|
|
60
|
+
Translator.seed(lang, dictionary);
|
|
61
|
+
|
|
62
|
+
if (typeof window !== "undefined") Translator.setActiveLocale(lang);
|
|
60
63
|
}
|
|
61
64
|
useLayoutEffect(() => {
|
|
62
65
|
Logger.rawLog(logo);
|
|
@@ -71,8 +74,7 @@ export const ClientWrapper = ({
|
|
|
71
74
|
};
|
|
72
75
|
Client.Wrapper = ClientWrapper;
|
|
73
76
|
|
|
74
|
-
interface ClientPathWrapperProps
|
|
75
|
-
extends Omit<HTMLAttributes<HTMLDivElement>, "style"> {
|
|
77
|
+
interface ClientPathWrapperProps extends Omit<HTMLAttributes<HTMLDivElement>, "style"> {
|
|
76
78
|
bind?: () => HTMLAttributes<HTMLDivElement>;
|
|
77
79
|
wrapperRef?: RefObject<HTMLDivElement | null> | null;
|
|
78
80
|
pageType?: "current" | "prev" | "cached";
|
|
@@ -93,18 +95,12 @@ export const ClientPathWrapper = ({
|
|
|
93
95
|
layoutStyle = "web",
|
|
94
96
|
...props
|
|
95
97
|
}: ClientPathWrapperProps) => {
|
|
96
|
-
const href =
|
|
97
|
-
|
|
98
|
-
(typeof window !== "undefined" ? window.location.href : "");
|
|
99
|
-
const hash =
|
|
100
|
-
location?.hash ??
|
|
101
|
-
(typeof window !== "undefined" ? window.location.hash : "");
|
|
98
|
+
const href = location?.href ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
99
|
+
const hash = location?.hash ?? (typeof window !== "undefined" ? window.location.hash : "");
|
|
102
100
|
const pathname = location?.pathname ?? "/";
|
|
103
101
|
const params = location?.params ?? {};
|
|
104
102
|
const searchParams = location?.searchParams ?? {};
|
|
105
|
-
const search =
|
|
106
|
-
location?.search ??
|
|
107
|
-
(typeof window !== "undefined" ? window.location.search : "");
|
|
103
|
+
const search = location?.search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
108
104
|
const lang = params.lang;
|
|
109
105
|
const firstPath = pathname.split("/")[2];
|
|
110
106
|
const pathRoute: PathRoute = location?.pathRoute ?? {
|
|
@@ -137,9 +133,7 @@ export const ClientPathWrapper = ({
|
|
|
137
133
|
}}
|
|
138
134
|
>
|
|
139
135
|
<animated.div
|
|
140
|
-
{...(bind && pathRoute.pageState.gesture && gestureEnabled
|
|
141
|
-
? bind()
|
|
142
|
-
: {})}
|
|
136
|
+
{...(bind && pathRoute.pageState.gesture && gestureEnabled ? bind() : {})}
|
|
143
137
|
className={clsx("group/path", className)}
|
|
144
138
|
ref={wrapperRef}
|
|
145
139
|
{...props}
|
|
@@ -159,15 +153,10 @@ interface ClientBridgeProps {
|
|
|
159
153
|
theme?: AkanTheme;
|
|
160
154
|
prefix?: string;
|
|
161
155
|
gaTrackingId?: string;
|
|
156
|
+
wsConnect?: boolean;
|
|
162
157
|
}
|
|
163
158
|
|
|
164
|
-
export const ClientBridge = ({
|
|
165
|
-
env,
|
|
166
|
-
lang,
|
|
167
|
-
theme,
|
|
168
|
-
prefix,
|
|
169
|
-
gaTrackingId,
|
|
170
|
-
}: ClientBridgeProps) => {
|
|
159
|
+
export const ClientBridge = ({ env, lang, theme, prefix, gaTrackingId, wsConnect = true }: ClientBridgeProps) => {
|
|
171
160
|
const uiOperation = st.use.uiOperation();
|
|
172
161
|
const pathname = st.use.pathname();
|
|
173
162
|
const params = st.use.params();
|
|
@@ -187,6 +176,11 @@ export const ClientBridge = ({
|
|
|
187
176
|
}, 2000);
|
|
188
177
|
}, []);
|
|
189
178
|
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (!wsConnect) return;
|
|
181
|
+
(fetch.instance as { connect: () => void }).connect();
|
|
182
|
+
}, [wsConnect]);
|
|
183
|
+
|
|
190
184
|
useEffect(() => {
|
|
191
185
|
if (getThemeCookie() !== undefined) return;
|
|
192
186
|
applyThemePolicy(theme ?? "system");
|
|
@@ -234,26 +228,18 @@ function applyThemePolicy(theme: AkanTheme): void {
|
|
|
234
228
|
}
|
|
235
229
|
if (theme === "system") {
|
|
236
230
|
const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
237
|
-
document.documentElement.setAttribute(
|
|
238
|
-
"data-theme",
|
|
239
|
-
dark ? "dark" : "light",
|
|
240
|
-
);
|
|
231
|
+
document.documentElement.setAttribute("data-theme", dark ? "dark" : "light");
|
|
241
232
|
return;
|
|
242
233
|
}
|
|
243
234
|
document.documentElement.setAttribute("data-theme", theme);
|
|
244
235
|
}
|
|
245
236
|
|
|
246
|
-
function buildSearchParams(
|
|
247
|
-
entries: Iterable<[string, string]>,
|
|
248
|
-
): Record<string, string | string[]> {
|
|
237
|
+
function buildSearchParams(entries: Iterable<[string, string]>): Record<string, string | string[]> {
|
|
249
238
|
const params: Record<string, string | string[]> = {};
|
|
250
239
|
for (const [key, value] of entries) {
|
|
251
240
|
const current = params[key];
|
|
252
241
|
if (current === undefined) params[key] = value;
|
|
253
|
-
else
|
|
254
|
-
params[key] = Array.isArray(current)
|
|
255
|
-
? [...current, value]
|
|
256
|
-
: [current, value];
|
|
242
|
+
else params[key] = Array.isArray(current) ? [...current, value] : [current, value];
|
|
257
243
|
}
|
|
258
244
|
return params;
|
|
259
245
|
}
|
|
@@ -273,10 +259,7 @@ interface ClientSsrBridgeProps {
|
|
|
273
259
|
lang: string;
|
|
274
260
|
prefix?: string;
|
|
275
261
|
}
|
|
276
|
-
export const ClientSsrBridge = ({
|
|
277
|
-
lang,
|
|
278
|
-
prefix = "",
|
|
279
|
-
}: ClientSsrBridgeProps) => {
|
|
262
|
+
export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) => {
|
|
280
263
|
useEffect(() => {
|
|
281
264
|
const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
|
|
282
265
|
const navigateRscWithFallback = (
|
|
@@ -290,19 +273,13 @@ export const ClientSsrBridge = ({
|
|
|
290
273
|
return;
|
|
291
274
|
}
|
|
292
275
|
void navigation.catch((error) => {
|
|
293
|
-
Logger.warn(
|
|
294
|
-
`RSC navigation failed, falling back to document navigation: ${String(error)}`,
|
|
295
|
-
);
|
|
276
|
+
Logger.warn(`RSC navigation failed, falling back to document navigation: ${String(error)}`);
|
|
296
277
|
fallback();
|
|
297
278
|
});
|
|
298
279
|
};
|
|
299
280
|
const syncHref = (href: string) => {
|
|
300
281
|
const url = new URL(href, window.location.origin);
|
|
301
|
-
const { path } = getPathInfo(
|
|
302
|
-
`${url.pathname}${url.search}${url.hash}`,
|
|
303
|
-
lang,
|
|
304
|
-
visiblePrefix,
|
|
305
|
-
);
|
|
282
|
+
const { path } = getPathInfo(`${url.pathname}${url.search}${url.hash}`, lang, visiblePrefix);
|
|
306
283
|
const searchParams = buildSearchParams(url.searchParams.entries());
|
|
307
284
|
st.set({ pathname: url.pathname, path, searchParams });
|
|
308
285
|
};
|
|
@@ -314,17 +291,11 @@ export const ClientSsrBridge = ({
|
|
|
314
291
|
router: {
|
|
315
292
|
push: (href, routeOptions) => {
|
|
316
293
|
syncHref(href);
|
|
317
|
-
navigateRscWithFallback(href, routeOptions, () =>
|
|
318
|
-
window.location.assign(href),
|
|
319
|
-
);
|
|
294
|
+
navigateRscWithFallback(href, routeOptions, () => window.location.assign(href));
|
|
320
295
|
},
|
|
321
296
|
replace: (href, routeOptions) => {
|
|
322
297
|
syncHref(href);
|
|
323
|
-
navigateRscWithFallback(
|
|
324
|
-
href,
|
|
325
|
-
{ ...routeOptions, replace: true },
|
|
326
|
-
() => window.location.replace(href),
|
|
327
|
-
);
|
|
298
|
+
navigateRscWithFallback(href, { ...routeOptions, replace: true }, () => window.location.replace(href));
|
|
328
299
|
},
|
|
329
300
|
back: () => {
|
|
330
301
|
window.history.back();
|
|
@@ -346,14 +317,8 @@ export const ClientSsrBridge = ({
|
|
|
346
317
|
const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
|
|
347
318
|
const sync = () => {
|
|
348
319
|
const { pathname, search, hash } = window.location;
|
|
349
|
-
const { path } = getPathInfo(
|
|
350
|
-
|
|
351
|
-
lang,
|
|
352
|
-
visiblePrefix,
|
|
353
|
-
);
|
|
354
|
-
const searchParams = buildSearchParams(
|
|
355
|
-
new URLSearchParams(search).entries(),
|
|
356
|
-
);
|
|
320
|
+
const { path } = getPathInfo(`${pathname}${search}${hash}`, lang, visiblePrefix);
|
|
321
|
+
const searchParams = buildSearchParams(new URLSearchParams(search).entries());
|
|
357
322
|
st.set({ pathname: window.location.pathname, path, searchParams });
|
|
358
323
|
};
|
|
359
324
|
sync();
|
package/ui/System/Common.tsx
CHANGED
|
@@ -30,6 +30,8 @@ export interface ProviderProps {
|
|
|
30
30
|
layoutStyle?: "mobile" | "web";
|
|
31
31
|
/** Enable reconnect helper. Defaults to local operation mode in CSR. */
|
|
32
32
|
reconnect?: boolean;
|
|
33
|
+
/** Connect the client WebSocket runtime after the browser loads. */
|
|
34
|
+
wsConnect?: boolean;
|
|
33
35
|
/** Active-locale dictionary injected by the server (SSR only) to seed the client Translator. */
|
|
34
36
|
dictionary?: Record<string, Record<string, unknown>>;
|
|
35
37
|
/**
|
package/ui/System/SSR.tsx
CHANGED
|
@@ -29,6 +29,7 @@ const SSRProvider = ({
|
|
|
29
29
|
fonts,
|
|
30
30
|
layoutStyle = "web",
|
|
31
31
|
reconnect = getEnv().operationMode === "local",
|
|
32
|
+
wsConnect = true,
|
|
32
33
|
dictionary,
|
|
33
34
|
allDictionary,
|
|
34
35
|
of,
|
|
@@ -65,7 +66,14 @@ const SSRProvider = ({
|
|
|
65
66
|
<ClientInner />
|
|
66
67
|
</Suspense>
|
|
67
68
|
<Suspense key="client-bridge" fallback={null}>
|
|
68
|
-
<ClientBridge
|
|
69
|
+
<ClientBridge
|
|
70
|
+
key="bridge"
|
|
71
|
+
env={env}
|
|
72
|
+
theme={theme}
|
|
73
|
+
prefix={prefix}
|
|
74
|
+
gaTrackingId={gaTrackingId}
|
|
75
|
+
wsConnect={wsConnect}
|
|
76
|
+
/>
|
|
69
77
|
<ClientSsrBridge key="ssr-bridge" lang={lang} prefix={prefix} />
|
|
70
78
|
</Suspense>
|
|
71
79
|
</ClientWrapper>
|