akanjs 2.2.3 → 2.2.4-rc.1
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 +7 -0
- package/client/makePageProto.tsx +5 -2
- package/client/translator.ts +8 -0
- package/fetch/client/fetchClient.ts +14 -11
- package/fetch/serializer/fetch.serializer.ts +1 -0
- package/package.json +1 -1
- package/service/injectInfo.ts +4 -3
- package/signal/fileUpload.ts +35 -0
- package/signal/index.ts +1 -0
- package/signal/serializer/fetch.serializer.ts +1 -0
- package/signal/types.ts +3 -0
- package/store/action.ts +6 -2
- package/types/client/translator.d.ts +2 -0
- package/types/service/injectInfo.d.ts +2 -2
- package/types/signal/fileUpload.d.ts +27 -0
- package/types/signal/index.d.ts +1 -0
- package/types/signal/types.d.ts +3 -0
- package/types/ui/System/Client.d.ts +4 -4
- package/ui/System/Client.tsx +20 -62
package/CHANGELOG.md
CHANGED
package/client/makePageProto.tsx
CHANGED
|
@@ -25,8 +25,11 @@ const getPageInfo = (): { locale: string; path: string } => {
|
|
|
25
25
|
const localeSet = new Set(locales);
|
|
26
26
|
if (getEnv().side !== "server") {
|
|
27
27
|
const [, firstSegment = "", ...rest] = window.location.pathname.split("/");
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const hasLocalePrefix = localeSet.has(firstSegment);
|
|
29
|
+
|
|
30
|
+
const activeLocale = Translator.getActiveLocale();
|
|
31
|
+
const locale = activeLocale ?? (hasLocalePrefix ? firstSegment : defaultLocale);
|
|
32
|
+
return { locale, path: hasLocalePrefix ? `/${rest.join("/")}` : window.location.pathname };
|
|
30
33
|
}
|
|
31
34
|
const h = headers();
|
|
32
35
|
|
package/client/translator.ts
CHANGED
|
@@ -13,6 +13,14 @@ export class Translator {
|
|
|
13
13
|
static #langDictionaryMap = new Map<string, Dictionary>();
|
|
14
14
|
|
|
15
15
|
static #seededDicts = new WeakSet<object>();
|
|
16
|
+
|
|
17
|
+
static #activeLocale: string | undefined;
|
|
18
|
+
static setActiveLocale(lang: string | undefined) {
|
|
19
|
+
if (lang) Translator.#activeLocale = lang;
|
|
20
|
+
}
|
|
21
|
+
static getActiveLocale(): string | undefined {
|
|
22
|
+
return Translator.#activeLocale;
|
|
23
|
+
}
|
|
16
24
|
constructor(dictionary: Record<string, Record<string, Record<string, unknown>>>) {
|
|
17
25
|
Object.entries(dictionary).forEach(([lang, dictionary]) => {
|
|
18
26
|
this.#setDictionary(lang, dictionary);
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
SerializedSlice,
|
|
11
11
|
ServiceSignal,
|
|
12
12
|
} from "akanjs/signal";
|
|
13
|
+
import { fileUploadContract, resolveFileUploadCapability } from "akanjs/signal/fileUpload";
|
|
13
14
|
import type { ClientSignal, MergeAllFetchTypes, SliceMeta } from "../fetchType";
|
|
14
15
|
import { memoizeRequestQuery, cookies as requestCookies, headers as requestHeaders } from "../requestStorage";
|
|
15
16
|
import type { GetSliceMetaObjFromDatabaseSignals } from "../types";
|
|
@@ -362,18 +363,20 @@ export class FetchClient {
|
|
|
362
363
|
|
|
363
364
|
Object.assign(this.handler, {
|
|
364
365
|
[names.addModelFiles]: async (fileList: FileList, parentId?: string, option?: FetchPolicy) => {
|
|
366
|
+
const cap = resolveFileUploadCapability(this.serializedSignal);
|
|
367
|
+
const endpoint = cap ? this.serializedSignal[cap.refName]?.endpoint[cap.endpointKey] : undefined;
|
|
368
|
+
if (!cap || !endpoint)
|
|
369
|
+
throw new Error(
|
|
370
|
+
"File upload is not configured. Mark an upload mutation with { fileUpload: true } (e.g. shared FileEndpoint.addFiles).",
|
|
371
|
+
);
|
|
372
|
+
const { fields, buildMetas } = fileUploadContract;
|
|
365
373
|
const formData = new FormData();
|
|
366
|
-
for (let i = 0; i < fileList.length; i++) formData.append(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
formData.append("type", refName);
|
|
373
|
-
if (parentId) formData.append("parentId", parentId);
|
|
374
|
-
return await this.http.post(`/file/addFilesRestApi`, formData, {
|
|
375
|
-
headers: { ...(this.jwt ? { Authorization: `Bearer ${this.jwt}` } : {}) },
|
|
376
|
-
});
|
|
374
|
+
for (let i = 0; i < fileList.length; i++) formData.append(fields.files, fileList[i]);
|
|
375
|
+
formData.append(fields.metas, JSON.stringify(buildMetas(fileList)));
|
|
376
|
+
formData.append(fields.type, refName);
|
|
377
|
+
if (parentId) formData.append(fields.parentId, parentId);
|
|
378
|
+
const url = FetchClient.makeHttpUrl(cap.endpointKey, endpoint, cap.prefix, new Map());
|
|
379
|
+
return await this.http.post(url, formData, { headers: this.#makeAuthHeaders(option) });
|
|
377
380
|
},
|
|
378
381
|
});
|
|
379
382
|
}
|
|
@@ -67,6 +67,7 @@ export class FetchSerializer {
|
|
|
67
67
|
...(endpointInfo.signalOption.globalPrefix !== undefined
|
|
68
68
|
? { globalPrefix: endpointInfo.signalOption.globalPrefix }
|
|
69
69
|
: {}),
|
|
70
|
+
...(endpointInfo.signalOption.fileUpload ? { fileUpload: true } : {}),
|
|
70
71
|
...(guards?.length ? { guards } : {}),
|
|
71
72
|
};
|
|
72
73
|
}
|
package/package.json
CHANGED
package/service/injectInfo.ts
CHANGED
|
@@ -259,7 +259,7 @@ export class InjectInfo<
|
|
|
259
259
|
get: async (key: string) => {
|
|
260
260
|
const getter = injectInfo.get as unknown as (value: unknown) => unknown;
|
|
261
261
|
const value = await cacheAdaptor.hget(`akan:memory:${injectInfo.parentRefName}`, propKey, key);
|
|
262
|
-
return value === null ?
|
|
262
|
+
return value === null ? undefined : getter(value);
|
|
263
263
|
},
|
|
264
264
|
set: async (key: string, value: unknown) => {
|
|
265
265
|
const setter = injectInfo.set as unknown as (value: unknown) => string | number | Buffer;
|
|
@@ -346,6 +346,7 @@ export const injectionBuilder = (parentRefName: string) => ({
|
|
|
346
346
|
const isMap = modelRef === Map;
|
|
347
347
|
if (isMap && !opts.of) throw new Error("of should be provided when modelRef is Map");
|
|
348
348
|
type FieldValue = never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>;
|
|
349
|
+
type MapFieldValue = never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>;
|
|
349
350
|
type IsNullable = DefaultValue extends never ? true : false;
|
|
350
351
|
type UseValue = IsNullable extends true ? FieldValue | null : FieldValue;
|
|
351
352
|
return new InjectInfo<
|
|
@@ -356,8 +357,8 @@ export const injectionBuilder = (parentRefName: string) => ({
|
|
|
356
357
|
: UseValue
|
|
357
358
|
: MapConstructor extends ValueRef
|
|
358
359
|
? {
|
|
359
|
-
get: (key: string) => Promise<
|
|
360
|
-
set: (key: string, value:
|
|
360
|
+
get: (key: string) => Promise<MapFieldValue | undefined>;
|
|
361
|
+
set: (key: string, value: MapFieldValue) => Promise<void>;
|
|
361
362
|
delete: (key: string) => Promise<void>;
|
|
362
363
|
}
|
|
363
364
|
: { get: () => Promise<UseValue>; set: (value: UseValue) => Promise<void>; delete: () => Promise<void> },
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SerializedSignal } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Framework-owned file-upload contract. A plugin opts in by marking exactly one
|
|
5
|
+
* upload mutation with `{ fileUpload: true }`; the multipart form must use these
|
|
6
|
+
* field names and the metas shape below.
|
|
7
|
+
*/
|
|
8
|
+
export const fileUploadContract = {
|
|
9
|
+
fields: { files: "files", metas: "metas", type: "type", parentId: "parentId" },
|
|
10
|
+
buildMetas: (fileList: FileList) =>
|
|
11
|
+
Array.from(fileList).map((f) => ({ lastModifiedAt: new Date(f.lastModified).toISOString(), size: f.size })),
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export interface FileUploadCapability {
|
|
15
|
+
refName: string;
|
|
16
|
+
endpointKey: string;
|
|
17
|
+
prefix?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Discovers the upload endpoint marked with `{ fileUpload: true }` from the serialized signal. */
|
|
21
|
+
export const resolveFileUploadCapability = (serializedSignal: {
|
|
22
|
+
[key: string]: SerializedSignal;
|
|
23
|
+
}): FileUploadCapability | null => {
|
|
24
|
+
const matches: FileUploadCapability[] = [];
|
|
25
|
+
for (const [refName, signal] of Object.entries(serializedSignal))
|
|
26
|
+
for (const [endpointKey, endpoint] of Object.entries(signal.endpoint))
|
|
27
|
+
if (endpoint.fileUpload) matches.push({ refName, endpointKey, prefix: signal.prefix });
|
|
28
|
+
if (matches.length > 1)
|
|
29
|
+
console.warn(
|
|
30
|
+
`[akan] Multiple fileUpload endpoints found (${matches
|
|
31
|
+
.map((m) => `${m.refName}.${m.endpointKey}`)
|
|
32
|
+
.join(", ")}). Using the first; mark only one mutation with { fileUpload: true }.`,
|
|
33
|
+
);
|
|
34
|
+
return matches[0] ?? null;
|
|
35
|
+
};
|
package/signal/index.ts
CHANGED
|
@@ -64,6 +64,7 @@ export class FetchSerializer {
|
|
|
64
64
|
args: endpointInfo.args.map(FetchSerializer.#serializeArg),
|
|
65
65
|
returns: FetchSerializer.#serializeReturns(endpointInfo),
|
|
66
66
|
...(endpointInfo.signalOption.path ? { path: endpointInfo.signalOption.path } : {}),
|
|
67
|
+
...(endpointInfo.signalOption.fileUpload ? { fileUpload: true } : {}),
|
|
67
68
|
...(guards?.length ? { guards } : {}),
|
|
68
69
|
};
|
|
69
70
|
}
|
package/signal/types.ts
CHANGED
|
@@ -70,6 +70,8 @@ export interface SignalOption<Response = any, Nullable extends boolean = false,
|
|
|
70
70
|
middlewares?: MiddlewareCls[];
|
|
71
71
|
prefix?: false | string;
|
|
72
72
|
globalPrefix?: false;
|
|
73
|
+
/** Marks this mutation as the framework file-upload endpoint (see resolveFileUploadCapability). */
|
|
74
|
+
fileUpload?: boolean;
|
|
73
75
|
|
|
74
76
|
scheduleType?: "init" | "destroy" | "cron" | "interval" | "timeout";
|
|
75
77
|
scheduleCron?: string;
|
|
@@ -84,6 +86,7 @@ interface SerializedSignalOption {
|
|
|
84
86
|
prefix?: false | string;
|
|
85
87
|
globalPrefix?: false;
|
|
86
88
|
guards?: string[];
|
|
89
|
+
fileUpload?: boolean;
|
|
87
90
|
}
|
|
88
91
|
export interface SerializedSlice extends SerializedSignalOption {}
|
|
89
92
|
|
package/store/action.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import type { BaseFilterSortKey, ExtractSort, FilterInstance } from "akanjs/document";
|
|
13
13
|
import type { FetchInitForm, FetchProxy } from "akanjs/fetch";
|
|
14
14
|
import type { SerializedSlice, SliceCls, SliceInfoArgs } from "akanjs/signal";
|
|
15
|
+
import { resolveFileUploadCapability } from "akanjs/signal/fileUpload";
|
|
15
16
|
import type { SliceStateKey } from "./state";
|
|
16
17
|
import type { SetGet } from "./types";
|
|
17
18
|
|
|
@@ -277,6 +278,7 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
|
|
|
277
278
|
type Light = BaseObject;
|
|
278
279
|
const [fieldName, className] = [refName, capitalize(refName)];
|
|
279
280
|
const modelRef = ConstantRegistry.getDatabase(refName).full;
|
|
281
|
+
const fileUploadRefName = resolveFileUploadCapability(fetch.serializedSignal)?.refName;
|
|
280
282
|
|
|
281
283
|
const names = {
|
|
282
284
|
model: fieldName,
|
|
@@ -356,7 +358,7 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
|
|
|
356
358
|
},
|
|
357
359
|
}
|
|
358
360
|
: {}),
|
|
359
|
-
...(field.isClass && ConstantRegistry.getRefName(field.modelRef) ===
|
|
361
|
+
...(field.isClass && !!fileUploadRefName && ConstantRegistry.getRefName(field.modelRef) === fileUploadRefName
|
|
360
362
|
? {
|
|
361
363
|
[namesOfField.uploadFieldOnModel]: async function (this: SetGet, fileList: FileList, index?: number) {
|
|
362
364
|
const form = (this.get() as { [key: string]: any })[names.modelForm] as { [key: string]: any };
|
|
@@ -383,7 +385,9 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
|
|
|
383
385
|
const intervalKey = setInterval(() => {
|
|
384
386
|
void (async () => {
|
|
385
387
|
const currentFile = await (
|
|
386
|
-
(fetch as { [key: string]: any })
|
|
388
|
+
(fetch as { [key: string]: any })[fileUploadRefName as string] as (
|
|
389
|
+
id: string,
|
|
390
|
+
) => Promise<ProtoFile>
|
|
387
391
|
)(file.id);
|
|
388
392
|
if (field.isArray)
|
|
389
393
|
this.set((state: { [key: string]: { [key: string]: ProtoFile[] } }) => {
|
|
@@ -8,6 +8,8 @@ export interface AllDictionary {
|
|
|
8
8
|
}
|
|
9
9
|
export declare class Translator {
|
|
10
10
|
#private;
|
|
11
|
+
static setActiveLocale(lang: string | undefined): void;
|
|
12
|
+
static getActiveLocale(): string | undefined;
|
|
11
13
|
constructor(dictionary: Record<string, Record<string, Record<string, unknown>>>);
|
|
12
14
|
hasDictionary(lang: string): boolean;
|
|
13
15
|
static seed(lang: string, dict: Dictionary | undefined): void;
|
|
@@ -76,8 +76,8 @@ export declare const injectionBuilder: (parentRefName: string) => {
|
|
|
76
76
|
get?: GetFn;
|
|
77
77
|
set?: (value: ReturnType<GetFn>) => GetFieldValue<ValueRef, ExplicitType>;
|
|
78
78
|
}) => InjectInfo<"memory", Local extends true ? MapConstructor extends ValueRef ? Map<string, FieldToValue<MapValue>> : (DefaultValue extends never ? true : false) extends true ? (never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>) | null : never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn> : MapConstructor extends ValueRef ? {
|
|
79
|
-
get: (key: string) => Promise<FieldToValue<MapValue
|
|
80
|
-
set: (key: string, value: FieldToValue<MapValue>) => Promise<void>;
|
|
79
|
+
get: (key: string) => Promise<(never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>) | undefined>;
|
|
80
|
+
set: (key: string, value: never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>) => Promise<void>;
|
|
81
81
|
delete: (key: string) => Promise<void>;
|
|
82
82
|
} : {
|
|
83
83
|
get: () => Promise<(DefaultValue extends never ? true : false) extends true ? (never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>) | null : never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SerializedSignal } from "./types.d.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Framework-owned file-upload contract. A plugin opts in by marking exactly one
|
|
4
|
+
* upload mutation with `{ fileUpload: true }`; the multipart form must use these
|
|
5
|
+
* field names and the metas shape below.
|
|
6
|
+
*/
|
|
7
|
+
export declare const fileUploadContract: {
|
|
8
|
+
readonly fields: {
|
|
9
|
+
readonly files: "files";
|
|
10
|
+
readonly metas: "metas";
|
|
11
|
+
readonly type: "type";
|
|
12
|
+
readonly parentId: "parentId";
|
|
13
|
+
};
|
|
14
|
+
readonly buildMetas: (fileList: FileList) => {
|
|
15
|
+
lastModifiedAt: string;
|
|
16
|
+
size: number;
|
|
17
|
+
}[];
|
|
18
|
+
};
|
|
19
|
+
export interface FileUploadCapability {
|
|
20
|
+
refName: string;
|
|
21
|
+
endpointKey: string;
|
|
22
|
+
prefix?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Discovers the upload endpoint marked with `{ fileUpload: true }` from the serialized signal. */
|
|
25
|
+
export declare const resolveFileUploadCapability: (serializedSignal: {
|
|
26
|
+
[key: string]: SerializedSignal;
|
|
27
|
+
}) => FileUploadCapability | null;
|
package/types/signal/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./base.signal";
|
|
|
2
2
|
export * from "./endpoint.d.ts";
|
|
3
3
|
export * from "./endpointInfo.d.ts";
|
|
4
4
|
export * from "./exception.d.ts";
|
|
5
|
+
export * from "./fileUpload.d.ts";
|
|
5
6
|
export * from "./guard.d.ts";
|
|
6
7
|
export * from "./guards.d.ts";
|
|
7
8
|
export * from "./intercept.d.ts";
|
package/types/signal/types.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface SignalOption<Response = any, Nullable extends boolean = false,
|
|
|
60
60
|
middlewares?: MiddlewareCls[];
|
|
61
61
|
prefix?: false | string;
|
|
62
62
|
globalPrefix?: false;
|
|
63
|
+
/** Marks this mutation as the framework file-upload endpoint (see resolveFileUploadCapability). */
|
|
64
|
+
fileUpload?: boolean;
|
|
63
65
|
scheduleType?: "init" | "destroy" | "cron" | "interval" | "timeout";
|
|
64
66
|
scheduleCron?: string;
|
|
65
67
|
scheduleTime?: number;
|
|
@@ -72,6 +74,7 @@ interface SerializedSignalOption {
|
|
|
72
74
|
prefix?: false | string;
|
|
73
75
|
globalPrefix?: false;
|
|
74
76
|
guards?: string[];
|
|
77
|
+
fileUpload?: boolean;
|
|
75
78
|
}
|
|
76
79
|
export interface SerializedSlice extends SerializedSignalOption {
|
|
77
80
|
}
|
|
@@ -6,9 +6,9 @@ import { type HTMLAttributes, type ReactNode, type RefObject } from "react";
|
|
|
6
6
|
export declare const Client: {
|
|
7
7
|
(): import("react/jsx-runtime").JSX.Element;
|
|
8
8
|
Wrapper: ({ children, theme, lang, dictionary, signals, reconnect, }: ClientWrapperProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
Bridge: ({ env, lang, theme, prefix, gaTrackingId
|
|
9
|
+
Bridge: ({ env, lang, theme, prefix, gaTrackingId }: ClientBridgeProps) => "" | import("react/jsx-runtime").JSX.Element | undefined;
|
|
10
10
|
Inner: () => import("react/jsx-runtime").JSX.Element;
|
|
11
|
-
SsrBridge: ({ lang, prefix
|
|
11
|
+
SsrBridge: ({ lang, prefix }: ClientSsrBridgeProps) => null;
|
|
12
12
|
};
|
|
13
13
|
interface ClientWrapperProps {
|
|
14
14
|
children: ReactNode;
|
|
@@ -37,11 +37,11 @@ interface ClientBridgeProps {
|
|
|
37
37
|
prefix?: string;
|
|
38
38
|
gaTrackingId?: string;
|
|
39
39
|
}
|
|
40
|
-
export declare const ClientBridge: ({ env, lang, theme, prefix, gaTrackingId
|
|
40
|
+
export declare const ClientBridge: ({ env, lang, theme, prefix, gaTrackingId }: ClientBridgeProps) => "" | import("react/jsx-runtime").JSX.Element | undefined;
|
|
41
41
|
export declare const ClientInner: () => import("react/jsx-runtime").JSX.Element;
|
|
42
42
|
interface ClientSsrBridgeProps {
|
|
43
43
|
lang: string;
|
|
44
44
|
prefix?: string;
|
|
45
45
|
}
|
|
46
|
-
export declare const ClientSsrBridge: ({ lang, prefix
|
|
46
|
+
export declare const ClientSsrBridge: ({ lang, prefix }: ClientSsrBridgeProps) => null;
|
|
47
47
|
export {};
|
package/ui/System/Client.tsx
CHANGED
|
@@ -55,8 +55,10 @@ export const ClientWrapper = ({
|
|
|
55
55
|
reconnect = true,
|
|
56
56
|
}: ClientWrapperProps) => {
|
|
57
57
|
|
|
58
|
-
if (dictionary)
|
|
59
|
-
|
|
58
|
+
if (dictionary) {
|
|
59
|
+
Translator.seed(lang, dictionary);
|
|
60
|
+
|
|
61
|
+
if (typeof window !== "undefined") Translator.setActiveLocale(lang);
|
|
60
62
|
}
|
|
61
63
|
useLayoutEffect(() => {
|
|
62
64
|
Logger.rawLog(logo);
|
|
@@ -71,8 +73,7 @@ export const ClientWrapper = ({
|
|
|
71
73
|
};
|
|
72
74
|
Client.Wrapper = ClientWrapper;
|
|
73
75
|
|
|
74
|
-
interface ClientPathWrapperProps
|
|
75
|
-
extends Omit<HTMLAttributes<HTMLDivElement>, "style"> {
|
|
76
|
+
interface ClientPathWrapperProps extends Omit<HTMLAttributes<HTMLDivElement>, "style"> {
|
|
76
77
|
bind?: () => HTMLAttributes<HTMLDivElement>;
|
|
77
78
|
wrapperRef?: RefObject<HTMLDivElement | null> | null;
|
|
78
79
|
pageType?: "current" | "prev" | "cached";
|
|
@@ -93,18 +94,12 @@ export const ClientPathWrapper = ({
|
|
|
93
94
|
layoutStyle = "web",
|
|
94
95
|
...props
|
|
95
96
|
}: 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 : "");
|
|
97
|
+
const href = location?.href ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
98
|
+
const hash = location?.hash ?? (typeof window !== "undefined" ? window.location.hash : "");
|
|
102
99
|
const pathname = location?.pathname ?? "/";
|
|
103
100
|
const params = location?.params ?? {};
|
|
104
101
|
const searchParams = location?.searchParams ?? {};
|
|
105
|
-
const search =
|
|
106
|
-
location?.search ??
|
|
107
|
-
(typeof window !== "undefined" ? window.location.search : "");
|
|
102
|
+
const search = location?.search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
108
103
|
const lang = params.lang;
|
|
109
104
|
const firstPath = pathname.split("/")[2];
|
|
110
105
|
const pathRoute: PathRoute = location?.pathRoute ?? {
|
|
@@ -137,9 +132,7 @@ export const ClientPathWrapper = ({
|
|
|
137
132
|
}}
|
|
138
133
|
>
|
|
139
134
|
<animated.div
|
|
140
|
-
{...(bind && pathRoute.pageState.gesture && gestureEnabled
|
|
141
|
-
? bind()
|
|
142
|
-
: {})}
|
|
135
|
+
{...(bind && pathRoute.pageState.gesture && gestureEnabled ? bind() : {})}
|
|
143
136
|
className={clsx("group/path", className)}
|
|
144
137
|
ref={wrapperRef}
|
|
145
138
|
{...props}
|
|
@@ -161,13 +154,7 @@ interface ClientBridgeProps {
|
|
|
161
154
|
gaTrackingId?: string;
|
|
162
155
|
}
|
|
163
156
|
|
|
164
|
-
export const ClientBridge = ({
|
|
165
|
-
env,
|
|
166
|
-
lang,
|
|
167
|
-
theme,
|
|
168
|
-
prefix,
|
|
169
|
-
gaTrackingId,
|
|
170
|
-
}: ClientBridgeProps) => {
|
|
157
|
+
export const ClientBridge = ({ env, lang, theme, prefix, gaTrackingId }: ClientBridgeProps) => {
|
|
171
158
|
const uiOperation = st.use.uiOperation();
|
|
172
159
|
const pathname = st.use.pathname();
|
|
173
160
|
const params = st.use.params();
|
|
@@ -234,26 +221,18 @@ function applyThemePolicy(theme: AkanTheme): void {
|
|
|
234
221
|
}
|
|
235
222
|
if (theme === "system") {
|
|
236
223
|
const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
237
|
-
document.documentElement.setAttribute(
|
|
238
|
-
"data-theme",
|
|
239
|
-
dark ? "dark" : "light",
|
|
240
|
-
);
|
|
224
|
+
document.documentElement.setAttribute("data-theme", dark ? "dark" : "light");
|
|
241
225
|
return;
|
|
242
226
|
}
|
|
243
227
|
document.documentElement.setAttribute("data-theme", theme);
|
|
244
228
|
}
|
|
245
229
|
|
|
246
|
-
function buildSearchParams(
|
|
247
|
-
entries: Iterable<[string, string]>,
|
|
248
|
-
): Record<string, string | string[]> {
|
|
230
|
+
function buildSearchParams(entries: Iterable<[string, string]>): Record<string, string | string[]> {
|
|
249
231
|
const params: Record<string, string | string[]> = {};
|
|
250
232
|
for (const [key, value] of entries) {
|
|
251
233
|
const current = params[key];
|
|
252
234
|
if (current === undefined) params[key] = value;
|
|
253
|
-
else
|
|
254
|
-
params[key] = Array.isArray(current)
|
|
255
|
-
? [...current, value]
|
|
256
|
-
: [current, value];
|
|
235
|
+
else params[key] = Array.isArray(current) ? [...current, value] : [current, value];
|
|
257
236
|
}
|
|
258
237
|
return params;
|
|
259
238
|
}
|
|
@@ -273,10 +252,7 @@ interface ClientSsrBridgeProps {
|
|
|
273
252
|
lang: string;
|
|
274
253
|
prefix?: string;
|
|
275
254
|
}
|
|
276
|
-
export const ClientSsrBridge = ({
|
|
277
|
-
lang,
|
|
278
|
-
prefix = "",
|
|
279
|
-
}: ClientSsrBridgeProps) => {
|
|
255
|
+
export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) => {
|
|
280
256
|
useEffect(() => {
|
|
281
257
|
const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
|
|
282
258
|
const navigateRscWithFallback = (
|
|
@@ -290,19 +266,13 @@ export const ClientSsrBridge = ({
|
|
|
290
266
|
return;
|
|
291
267
|
}
|
|
292
268
|
void navigation.catch((error) => {
|
|
293
|
-
Logger.warn(
|
|
294
|
-
`RSC navigation failed, falling back to document navigation: ${String(error)}`,
|
|
295
|
-
);
|
|
269
|
+
Logger.warn(`RSC navigation failed, falling back to document navigation: ${String(error)}`);
|
|
296
270
|
fallback();
|
|
297
271
|
});
|
|
298
272
|
};
|
|
299
273
|
const syncHref = (href: string) => {
|
|
300
274
|
const url = new URL(href, window.location.origin);
|
|
301
|
-
const { path } = getPathInfo(
|
|
302
|
-
`${url.pathname}${url.search}${url.hash}`,
|
|
303
|
-
lang,
|
|
304
|
-
visiblePrefix,
|
|
305
|
-
);
|
|
275
|
+
const { path } = getPathInfo(`${url.pathname}${url.search}${url.hash}`, lang, visiblePrefix);
|
|
306
276
|
const searchParams = buildSearchParams(url.searchParams.entries());
|
|
307
277
|
st.set({ pathname: url.pathname, path, searchParams });
|
|
308
278
|
};
|
|
@@ -314,17 +284,11 @@ export const ClientSsrBridge = ({
|
|
|
314
284
|
router: {
|
|
315
285
|
push: (href, routeOptions) => {
|
|
316
286
|
syncHref(href);
|
|
317
|
-
navigateRscWithFallback(href, routeOptions, () =>
|
|
318
|
-
window.location.assign(href),
|
|
319
|
-
);
|
|
287
|
+
navigateRscWithFallback(href, routeOptions, () => window.location.assign(href));
|
|
320
288
|
},
|
|
321
289
|
replace: (href, routeOptions) => {
|
|
322
290
|
syncHref(href);
|
|
323
|
-
navigateRscWithFallback(
|
|
324
|
-
href,
|
|
325
|
-
{ ...routeOptions, replace: true },
|
|
326
|
-
() => window.location.replace(href),
|
|
327
|
-
);
|
|
291
|
+
navigateRscWithFallback(href, { ...routeOptions, replace: true }, () => window.location.replace(href));
|
|
328
292
|
},
|
|
329
293
|
back: () => {
|
|
330
294
|
window.history.back();
|
|
@@ -346,14 +310,8 @@ export const ClientSsrBridge = ({
|
|
|
346
310
|
const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
|
|
347
311
|
const sync = () => {
|
|
348
312
|
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
|
-
);
|
|
313
|
+
const { path } = getPathInfo(`${pathname}${search}${hash}`, lang, visiblePrefix);
|
|
314
|
+
const searchParams = buildSearchParams(new URLSearchParams(search).entries());
|
|
357
315
|
st.set({ pathname: window.location.pathname, path, searchParams });
|
|
358
316
|
};
|
|
359
317
|
sync();
|