akanjs 2.2.7 → 2.2.8
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/makePageProto.tsx +5 -2
- package/common/fileUpload.ts +1 -1
- package/common/index.ts +5 -1
- package/constant/getDefault.ts +1 -1
- package/fetch/client/fetchClient.ts +2 -9
- package/fetch/serializer/fetch.serializer.ts +1 -0
- package/package.json +1 -5
- 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/serializer/fetch.serializer.ts +1 -0
- package/signal/types.ts +3 -0
- package/store/action.ts +15 -3
- package/types/common/fileUpload.d.ts +1 -1
- package/types/common/index.d.ts +1 -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/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/Client.d.ts +4 -4
- package/ui/Dialog/Modal.tsx +181 -70
- package/ui/Dialog/Provider.tsx +3 -6
- package/ui/Modal.tsx +0 -44
- package/ui/System/Client.tsx +20 -62
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
- fix: base dictionary translation failed in some cases
|
|
8
|
-
- fix: file upload contract workaround on shared Field.Img component
|
|
7
|
+
- bf51564: fix: base dictionary translation failed in some cases
|
|
8
|
+
- bf51564: fix: file upload contract workaround on shared Field.Img component
|
|
9
|
+
|
|
10
|
+
## 2.2.5
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- d636456: add rich Map methods on memory() helper service
|
|
15
|
+
- a1ee4e8: fill nested constant defaults for arrays on document save and load, normalize date fields to a consistent epoch representation on store (accepting legacy ISO-string values on read), and correct falsy defaults in getDefault
|
|
16
|
+
- 5cdb05e: reverse dependency of file upload api
|
|
17
|
+
- a7da50e: remove dependency from radix dialog
|
|
9
18
|
|
|
10
19
|
## 2.2.3
|
|
11
20
|
|
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/common/fileUpload.ts
CHANGED
package/common/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export { applyMixins } from "./applyMixins";
|
|
2
2
|
export { capitalize } from "./capitalize";
|
|
3
3
|
export { deepObjectify } from "./deepObjectify";
|
|
4
|
-
export
|
|
4
|
+
export {
|
|
5
|
+
type FileUploadCapability,
|
|
6
|
+
fileUploadContract,
|
|
7
|
+
resolveFileUploadCapability,
|
|
8
|
+
} from "./fileUpload";
|
|
5
9
|
export { formatNumber } from "./formatNumber";
|
|
6
10
|
export { formatPhone } from "./formatPhone";
|
|
7
11
|
export { getAllPropertyDescriptors } from "./getAllPropertyDescriptors";
|
package/constant/getDefault.ts
CHANGED
|
@@ -6,7 +6,7 @@ export const getDefault = <T>(fieldObj: FieldObject): DefaultOf<T> => {
|
|
|
6
6
|
const result: Record<string, unknown> = {};
|
|
7
7
|
for (const [key, field] of Object.entries(fieldObj)) {
|
|
8
8
|
if (field.fieldType === "hidden") result[key] = null;
|
|
9
|
-
else if (field.default) {
|
|
9
|
+
else if (field.default !== undefined && field.default !== null) {
|
|
10
10
|
if (typeof field.default === "function") result[key] = (field.default as () => object)();
|
|
11
11
|
else result[key] = field.default as object;
|
|
12
12
|
} else if (field.isArray) result[key] = [];
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { DataList, getEnv, PrimitiveRegistry, type PromiseOrObject } from "akanjs/base";
|
|
2
|
-
import {
|
|
3
|
-
capitalize,
|
|
4
|
-
type FetchPolicy,
|
|
5
|
-
type FileUploadSerializedSignal,
|
|
6
|
-
fileUploadContract,
|
|
7
|
-
Logger,
|
|
8
|
-
resolveFileUploadCapability,
|
|
9
|
-
} from "akanjs/common";
|
|
2
|
+
import { capitalize, type FetchPolicy, fileUploadContract, Logger, resolveFileUploadCapability } from "akanjs/common";
|
|
10
3
|
import { type BaseInsight, type BaseObject, ConstantRegistry, deserialize, serialize } from "akanjs/constant";
|
|
11
4
|
import type {
|
|
12
5
|
DatabaseSignal,
|
|
@@ -479,7 +472,7 @@ export class FetchClient {
|
|
|
479
472
|
names.addModelFiles,
|
|
480
473
|
() =>
|
|
481
474
|
(async (fileList: FileList, parentId?: string, option?: FetchPolicy) => {
|
|
482
|
-
const cap = resolveFileUploadCapability(this.serializedSignal
|
|
475
|
+
const cap = resolveFileUploadCapability(this.serializedSignal);
|
|
483
476
|
const endpoint = cap ? this.serializedSignal[cap.refName]?.endpoint[cap.endpointKey] : undefined;
|
|
484
477
|
if (!cap || !endpoint)
|
|
485
478
|
throw new Error(
|
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akanjs",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.8",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -175,7 +175,6 @@
|
|
|
175
175
|
"@capgo/capacitor-updater": "^8.46.1",
|
|
176
176
|
"@libsql/client": "^0.17.3",
|
|
177
177
|
"@playwright/test": "^1.60.0",
|
|
178
|
-
"@radix-ui/react-dialog": "^1.1.15",
|
|
179
178
|
"@react-spring/web": "^10.1.0",
|
|
180
179
|
"@use-gesture/react": "^10.3.1",
|
|
181
180
|
"bullmq": "^5.76.10",
|
|
@@ -250,9 +249,6 @@
|
|
|
250
249
|
"@playwright/test": {
|
|
251
250
|
"optional": true
|
|
252
251
|
},
|
|
253
|
-
"@radix-ui/react-dialog": {
|
|
254
|
-
"optional": true
|
|
255
|
-
},
|
|
256
252
|
"@react-spring/web": {
|
|
257
253
|
"optional": true
|
|
258
254
|
},
|
package/service/injectInfo.ts
CHANGED
|
@@ -254,20 +254,47 @@ export class InjectInfo<
|
|
|
254
254
|
enumerable: true,
|
|
255
255
|
});
|
|
256
256
|
} else if (injectInfo.isMap) {
|
|
257
|
+
const topic = `akan:memory:${injectInfo.parentRefName}`;
|
|
258
|
+
const getter = injectInfo.get as unknown as (value: unknown) => unknown;
|
|
259
|
+
const setter = injectInfo.set as unknown as (value: unknown) => string | number | Buffer;
|
|
260
|
+
const get = async (key: string) => {
|
|
261
|
+
const value = await cacheAdaptor.hget(topic, propKey, key);
|
|
262
|
+
return value === undefined || value === null ? undefined : getter(value);
|
|
263
|
+
};
|
|
264
|
+
const set = async (key: string, value: unknown) => {
|
|
265
|
+
const setValue = setter(value);
|
|
266
|
+
await cacheAdaptor.hset(topic, propKey, key, setValue);
|
|
267
|
+
};
|
|
257
268
|
Object.defineProperty(instance, propKey, {
|
|
258
269
|
value: {
|
|
259
|
-
get
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
270
|
+
get,
|
|
271
|
+
set,
|
|
272
|
+
delete: async (key: string) => {
|
|
273
|
+
await cacheAdaptor.hdelete(topic, propKey, key);
|
|
263
274
|
},
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
await
|
|
275
|
+
getOrInsert: async (key: string, value: unknown) => {
|
|
276
|
+
const existingValue = await get(key);
|
|
277
|
+
if (existingValue !== undefined) return existingValue;
|
|
278
|
+
await set(key, value);
|
|
279
|
+
return value;
|
|
268
280
|
},
|
|
269
|
-
|
|
270
|
-
await
|
|
281
|
+
getOrInsertComputed: async (key: string, compute: (key: string) => unknown | Promise<unknown>) => {
|
|
282
|
+
const existingValue = await get(key);
|
|
283
|
+
if (existingValue !== undefined) return existingValue;
|
|
284
|
+
const value = await compute(key);
|
|
285
|
+
await set(key, value);
|
|
286
|
+
return value;
|
|
287
|
+
},
|
|
288
|
+
keys: async () => await cacheAdaptor.hkeys(topic, propKey),
|
|
289
|
+
entries: async () => {
|
|
290
|
+
const entries = await cacheAdaptor.hentries(topic, propKey);
|
|
291
|
+
return entries.map(([key, value]) => [key, getter(value)]);
|
|
292
|
+
},
|
|
293
|
+
forEach: async (callback: (value: unknown, key: string) => void | Promise<void>) => {
|
|
294
|
+
for (const [key, value] of await cacheAdaptor.hentries(topic, propKey)) await callback(getter(value), key);
|
|
295
|
+
},
|
|
296
|
+
clear: async () => {
|
|
297
|
+
await cacheAdaptor.hclear(topic, propKey);
|
|
271
298
|
},
|
|
272
299
|
},
|
|
273
300
|
});
|
|
@@ -346,6 +373,7 @@ export const injectionBuilder = (parentRefName: string) => ({
|
|
|
346
373
|
const isMap = modelRef === Map;
|
|
347
374
|
if (isMap && !opts.of) throw new Error("of should be provided when modelRef is Map");
|
|
348
375
|
type FieldValue = never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>;
|
|
376
|
+
type MapFieldValue = never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>;
|
|
349
377
|
type IsNullable = DefaultValue extends never ? true : false;
|
|
350
378
|
type UseValue = IsNullable extends true ? FieldValue | null : FieldValue;
|
|
351
379
|
return new InjectInfo<
|
|
@@ -356,9 +384,18 @@ export const injectionBuilder = (parentRefName: string) => ({
|
|
|
356
384
|
: UseValue
|
|
357
385
|
: MapConstructor extends ValueRef
|
|
358
386
|
? {
|
|
359
|
-
get: (key: string) => Promise<
|
|
360
|
-
set: (key: string, value:
|
|
387
|
+
get: (key: string) => Promise<MapFieldValue | undefined>;
|
|
388
|
+
set: (key: string, value: MapFieldValue) => Promise<void>;
|
|
361
389
|
delete: (key: string) => Promise<void>;
|
|
390
|
+
getOrInsert: (key: string, value: MapFieldValue) => Promise<MapFieldValue>;
|
|
391
|
+
getOrInsertComputed: (
|
|
392
|
+
key: string,
|
|
393
|
+
compute: (key: string) => MapFieldValue | Promise<MapFieldValue>,
|
|
394
|
+
) => Promise<MapFieldValue>;
|
|
395
|
+
keys: () => Promise<string[]>;
|
|
396
|
+
entries: () => Promise<[string, MapFieldValue][]>;
|
|
397
|
+
forEach: (callback: (value: MapFieldValue, key: string) => void | Promise<void>) => Promise<void>;
|
|
398
|
+
clear: () => Promise<void>;
|
|
362
399
|
}
|
|
363
400
|
: { get: () => Promise<UseValue>; set: (value: UseValue) => Promise<void>; delete: () => Promise<void> },
|
|
364
401
|
never,
|
|
@@ -16,6 +16,9 @@ export interface CacheAdaptor {
|
|
|
16
16
|
): Promise<void>;
|
|
17
17
|
hget<T extends string | number | Buffer>(topic: string, key: string, subKey: string): Promise<T | undefined>;
|
|
18
18
|
hdelete(topic: string, key: string, subKey: string): Promise<void>;
|
|
19
|
+
hkeys(topic: string, key: string): Promise<string[]>;
|
|
20
|
+
hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]>;
|
|
21
|
+
hclear(topic: string, key: string): Promise<void>;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
interface RedisEnv extends BaseEnv {
|
|
@@ -96,6 +99,16 @@ export class RedisCache
|
|
|
96
99
|
async hdelete(topic: string, key: string, subKey: string): Promise<void> {
|
|
97
100
|
await this.redis.hdel(`${topic}:${key}`, subKey);
|
|
98
101
|
}
|
|
102
|
+
async hkeys(topic: string, key: string): Promise<string[]> {
|
|
103
|
+
return await this.redis.hkeys(`${topic}:${key}`);
|
|
104
|
+
}
|
|
105
|
+
async hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]> {
|
|
106
|
+
const values = await this.redis.hgetall(`${topic}:${key}`);
|
|
107
|
+
return Object.entries(values) as [string, T][];
|
|
108
|
+
}
|
|
109
|
+
async hclear(topic: string, key: string): Promise<void> {
|
|
110
|
+
await this.redis.del(`${topic}:${key}`);
|
|
111
|
+
}
|
|
99
112
|
getClient(): Redis {
|
|
100
113
|
return this.redis;
|
|
101
114
|
}
|
|
@@ -4,7 +4,7 @@ import { mkdir } from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import type { InArgs, InValue, Client as LibsqlClient } from "@libsql/client";
|
|
6
6
|
import { type BaseEnv, dayjs, FIELD_META, type PromiseOrObject } from "akanjs/base";
|
|
7
|
-
import type
|
|
7
|
+
import { type ConstantModel, getDefault } from "akanjs/constant";
|
|
8
8
|
import {
|
|
9
9
|
createDocumentId,
|
|
10
10
|
type DatabaseModel,
|
|
@@ -300,6 +300,13 @@ const jsonPath = (path: string) =>
|
|
|
300
300
|
.map((part) => part.replaceAll('"', '\\"'))
|
|
301
301
|
.join(".")}`;
|
|
302
302
|
const encodeSqlValue = (value: unknown) => encodeDocumentValue(value);
|
|
303
|
+
|
|
304
|
+
const decodeDateValue = (value: unknown) => {
|
|
305
|
+
if (value === null || value === undefined) return value;
|
|
306
|
+
if (typeof value === "number") return dayjs(value);
|
|
307
|
+
const epoch = Number(value);
|
|
308
|
+
return Number.isNaN(epoch) ? dayjs(value as never) : dayjs(epoch);
|
|
309
|
+
};
|
|
303
310
|
const QUERY_OPERATOR_KEYS = new Set([
|
|
304
311
|
"eq",
|
|
305
312
|
"ne",
|
|
@@ -773,6 +780,9 @@ export class SqliteDocumentStore {
|
|
|
773
780
|
} else {
|
|
774
781
|
doc[key] = value;
|
|
775
782
|
}
|
|
783
|
+
if (doc[key] !== undefined && doc[key] !== null) {
|
|
784
|
+
doc[key] = this.normalizeWriteValue(doc[key], props);
|
|
785
|
+
}
|
|
776
786
|
if (props.enum && doc[key] !== undefined && doc[key] !== null) {
|
|
777
787
|
const values = Array.isArray(doc[key]) ? doc[key] : [doc[key]];
|
|
778
788
|
const fieldEnum = props.enum as { has: (value: unknown) => boolean } | undefined;
|
|
@@ -958,12 +968,28 @@ export class SqliteDocumentStore {
|
|
|
958
968
|
|
|
959
969
|
private decodeDocumentPayload(payload: Record<string, unknown>) {
|
|
960
970
|
const fields = this.database.doc[FIELD_META] as unknown as FieldMap;
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
971
|
+
const result: Record<string, unknown> = {};
|
|
972
|
+
for (const [key, fieldMeta] of Object.entries(fields)) {
|
|
973
|
+
if (BASE_COLUMNS.has(key)) continue;
|
|
974
|
+
const props = fieldMeta.getProps();
|
|
975
|
+
const value = payload[key];
|
|
976
|
+
if (value === undefined) {
|
|
977
|
+
const def = props.default;
|
|
978
|
+
if (def !== undefined && def !== null) {
|
|
979
|
+
result[key] = typeof def === "function" ? (def as (data: unknown) => unknown)(payload) : def;
|
|
980
|
+
} else if (props.nullable) {
|
|
981
|
+
result[key] = null;
|
|
982
|
+
}
|
|
983
|
+
} else {
|
|
984
|
+
result[key] = this.decodeFieldValue(value, props);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
988
|
+
if (key in result || BASE_COLUMNS.has(key)) continue;
|
|
989
|
+
const props = fields[key]?.getProps?.();
|
|
990
|
+
result[key] = props ? this.decodeFieldValue(value, props) : value;
|
|
991
|
+
}
|
|
992
|
+
return result;
|
|
967
993
|
}
|
|
968
994
|
|
|
969
995
|
private decodeFieldValue(value: unknown, props: Record<string, unknown>): unknown {
|
|
@@ -973,8 +999,8 @@ export class SqliteDocumentStore {
|
|
|
973
999
|
return new Map(entries.map(([key, item]) => [key, this.decodeMapValue(item, props)]));
|
|
974
1000
|
}
|
|
975
1001
|
if (props.modelRef === Date) {
|
|
976
|
-
if (Array.isArray(value)) return value.map((item) => (item === null ? item :
|
|
977
|
-
return
|
|
1002
|
+
if (Array.isArray(value)) return value.map((item) => (item === null ? item : decodeDateValue(item)));
|
|
1003
|
+
return decodeDateValue(value);
|
|
978
1004
|
}
|
|
979
1005
|
if (Array.isArray(value)) return value.map((item) => this.decodeNestedValue(item, props));
|
|
980
1006
|
return this.decodeNestedValue(value, props);
|
|
@@ -982,7 +1008,7 @@ export class SqliteDocumentStore {
|
|
|
982
1008
|
|
|
983
1009
|
private decodeMapValue(value: unknown, props: Record<string, unknown>) {
|
|
984
1010
|
if (value === undefined || value === null) return value;
|
|
985
|
-
if (props.of === Date) return
|
|
1011
|
+
if (props.of === Date) return decodeDateValue(value);
|
|
986
1012
|
return value;
|
|
987
1013
|
}
|
|
988
1014
|
|
|
@@ -991,12 +1017,44 @@ export class SqliteDocumentStore {
|
|
|
991
1017
|
if (!props.isClass || !props.isScalar) return value;
|
|
992
1018
|
const scalarFields = (props.modelRef as { [FIELD_META]?: FieldMap } | undefined)?.[FIELD_META];
|
|
993
1019
|
if (!scalarFields) return value;
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1020
|
+
const source = value as Record<string, unknown>;
|
|
1021
|
+
const defaults = getDefault(scalarFields as never) as Record<string, unknown>;
|
|
1022
|
+
const result: Record<string, unknown> = {};
|
|
1023
|
+
for (const [key, fieldMeta] of Object.entries(scalarFields)) {
|
|
1024
|
+
const nestedProps = fieldMeta.getProps();
|
|
1025
|
+
const nested = source[key];
|
|
1026
|
+
result[key] = nested === undefined ? defaults[key] : this.decodeFieldValue(nested, nestedProps);
|
|
1027
|
+
}
|
|
1028
|
+
for (const [key, nested] of Object.entries(source)) {
|
|
1029
|
+
if (!(key in result)) result[key] = nested;
|
|
1030
|
+
}
|
|
1031
|
+
return result;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
private normalizeWriteValue(value: unknown, props: Record<string, unknown>): unknown {
|
|
1035
|
+
if (value === undefined || value === null) return value;
|
|
1036
|
+
if (props.modelRef === Date) {
|
|
1037
|
+
if (Array.isArray(value))
|
|
1038
|
+
return value.map((item) => (item === null || item === undefined ? item : dayjs(item as never)));
|
|
1039
|
+
return dayjs(value as never);
|
|
1040
|
+
}
|
|
1041
|
+
if (!props.isClass || !props.isScalar) return value;
|
|
1042
|
+
if (Array.isArray(value)) return value.map((item) => this.fillScalarDefaults(item, props));
|
|
1043
|
+
return this.fillScalarDefaults(value, props);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
private fillScalarDefaults(value: unknown, props: Record<string, unknown>): unknown {
|
|
1047
|
+
if (!value || typeof value !== "object") return value;
|
|
1048
|
+
const scalarFields = (props.modelRef as { [FIELD_META]?: FieldMap } | undefined)?.[FIELD_META];
|
|
1049
|
+
if (!scalarFields) return value;
|
|
1050
|
+
const defaults = getDefault(scalarFields as never) as Record<string, unknown>;
|
|
1051
|
+
const result = { ...(value as Record<string, unknown>) };
|
|
1052
|
+
for (const [key, fieldMeta] of Object.entries(scalarFields)) {
|
|
1053
|
+
const nestedProps = fieldMeta.getProps();
|
|
1054
|
+
if (result[key] === undefined) result[key] = defaults[key];
|
|
1055
|
+
else result[key] = this.normalizeWriteValue(result[key], nestedProps);
|
|
1056
|
+
}
|
|
1057
|
+
return result;
|
|
1000
1058
|
}
|
|
1001
1059
|
|
|
1002
1060
|
hydrate(data: DocumentRecord, originalData: DocumentRecord = data) {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "./solidSqlite";
|
|
15
15
|
|
|
16
16
|
type CacheRow = { value: string | Buffer | null; valueType: SolidValueType; expiresAt: number | null };
|
|
17
|
+
type CacheEntryRow = CacheRow & { subKey: string };
|
|
17
18
|
|
|
18
19
|
export class SolidCache
|
|
19
20
|
extends adapt("solidCache", ({ env }) => ({
|
|
@@ -135,6 +136,28 @@ export class SolidCache
|
|
|
135
136
|
.run(topic, key, subKey);
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
async hkeys(topic: string, key: string): Promise<string[]> {
|
|
140
|
+
this.#cleanup();
|
|
141
|
+
const rows = this.#db
|
|
142
|
+
.query(`SELECT "subKey" FROM "_akan_solid_cache_hash" WHERE "topic" = ? AND "key" = ? ORDER BY "subKey" ASC`)
|
|
143
|
+
.all(topic, key) as { subKey: string }[];
|
|
144
|
+
return rows.map((row) => row.subKey);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]> {
|
|
148
|
+
this.#cleanup();
|
|
149
|
+
const rows = this.#db
|
|
150
|
+
.query(
|
|
151
|
+
`SELECT "subKey", "value", "valueType", "expiresAt" FROM "_akan_solid_cache_hash" WHERE "topic" = ? AND "key" = ? ORDER BY "subKey" ASC`,
|
|
152
|
+
)
|
|
153
|
+
.all(topic, key) as CacheEntryRow[];
|
|
154
|
+
return rows.map((row) => [row.subKey, decodeSolidValue<T>(row.valueType, row.value) as T]);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async hclear(topic: string, key: string): Promise<void> {
|
|
158
|
+
this.#db.query(`DELETE FROM "_akan_solid_cache_hash" WHERE "topic" = ? AND "key" = ?`).run(topic, key);
|
|
159
|
+
}
|
|
160
|
+
|
|
138
161
|
#cleanup() {
|
|
139
162
|
const now = Date.now();
|
|
140
163
|
this.#db.query(`DELETE FROM "_akan_solid_cache" WHERE "expiresAt" IS NOT NULL AND "expiresAt" <= ?`).run(now);
|
|
@@ -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
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { DataList, type Dayjs, FIELD_META, type GetStateObject, type SLICE_META } from "akanjs/base";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
capitalize,
|
|
4
|
+
deepObjectify,
|
|
5
|
+
type FetchPolicy,
|
|
6
|
+
isQueryEqual,
|
|
7
|
+
Logger,
|
|
8
|
+
lowerlize,
|
|
9
|
+
pathSet,
|
|
10
|
+
resolveFileUploadCapability,
|
|
11
|
+
} from "akanjs/common";
|
|
3
12
|
import {
|
|
4
13
|
type BaseInsight,
|
|
5
14
|
type BaseObject,
|
|
@@ -277,6 +286,7 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
|
|
|
277
286
|
type Light = BaseObject;
|
|
278
287
|
const [fieldName, className] = [refName, capitalize(refName)];
|
|
279
288
|
const modelRef = ConstantRegistry.getDatabase(refName).full;
|
|
289
|
+
const fileUploadRefName = resolveFileUploadCapability(fetch.serializedSignal)?.refName;
|
|
280
290
|
|
|
281
291
|
const names = {
|
|
282
292
|
model: fieldName,
|
|
@@ -356,7 +366,7 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
|
|
|
356
366
|
},
|
|
357
367
|
}
|
|
358
368
|
: {}),
|
|
359
|
-
...(field.isClass && ConstantRegistry.getRefName(field.modelRef) ===
|
|
369
|
+
...(field.isClass && !!fileUploadRefName && ConstantRegistry.getRefName(field.modelRef) === fileUploadRefName
|
|
360
370
|
? {
|
|
361
371
|
[namesOfField.uploadFieldOnModel]: async function (this: SetGet, fileList: FileList, index?: number) {
|
|
362
372
|
const form = (this.get() as { [key: string]: any })[names.modelForm] as { [key: string]: any };
|
|
@@ -383,7 +393,9 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
|
|
|
383
393
|
const intervalKey = setInterval(() => {
|
|
384
394
|
void (async () => {
|
|
385
395
|
const currentFile = await (
|
|
386
|
-
(fetch as { [key: string]: any })
|
|
396
|
+
(fetch as { [key: string]: any })[fileUploadRefName as string] as (
|
|
397
|
+
id: string,
|
|
398
|
+
) => Promise<ProtoFile>
|
|
387
399
|
)(file.id);
|
|
388
400
|
if (field.isArray)
|
|
389
401
|
this.set((state: { [key: string]: { [key: string]: ProtoFile[] } }) => {
|
package/types/common/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { applyMixins } from "./applyMixins.d.ts";
|
|
2
2
|
export { capitalize } from "./capitalize.d.ts";
|
|
3
3
|
export { deepObjectify } from "./deepObjectify.d.ts";
|
|
4
|
-
export
|
|
4
|
+
export { type FileUploadCapability, fileUploadContract, resolveFileUploadCapability, } from "./fileUpload.d.ts";
|
|
5
5
|
export { formatNumber } from "./formatNumber.d.ts";
|
|
6
6
|
export { formatPhone } from "./formatPhone.d.ts";
|
|
7
7
|
export { getAllPropertyDescriptors } from "./getAllPropertyDescriptors.d.ts";
|
|
@@ -76,9 +76,15 @@ 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
|
+
getOrInsert: (key: string, value: never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>) => Promise<never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>>;
|
|
83
|
+
getOrInsertComputed: (key: string, compute: (key: string) => (never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>) | Promise<never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>>) => Promise<never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>>;
|
|
84
|
+
keys: () => Promise<string[]>;
|
|
85
|
+
entries: () => Promise<[string, never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>][]>;
|
|
86
|
+
forEach: (callback: (value: never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>, key: string) => void | Promise<void>) => Promise<void>;
|
|
87
|
+
clear: () => Promise<void>;
|
|
82
88
|
} : {
|
|
83
89
|
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>>;
|
|
84
90
|
set: (value: (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>) => Promise<void>;
|
|
@@ -12,6 +12,9 @@ export interface CacheAdaptor {
|
|
|
12
12
|
}): Promise<void>;
|
|
13
13
|
hget<T extends string | number | Buffer>(topic: string, key: string, subKey: string): Promise<T | undefined>;
|
|
14
14
|
hdelete(topic: string, key: string, subKey: string): Promise<void>;
|
|
15
|
+
hkeys(topic: string, key: string): Promise<string[]>;
|
|
16
|
+
hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]>;
|
|
17
|
+
hclear(topic: string, key: string): Promise<void>;
|
|
15
18
|
}
|
|
16
19
|
interface RedisEnv extends BaseEnv {
|
|
17
20
|
redis?: {
|
|
@@ -34,6 +37,9 @@ export declare class RedisCache extends RedisCache_base implements CacheAdaptor
|
|
|
34
37
|
}): Promise<void>;
|
|
35
38
|
hget<T extends string | number | Buffer>(topic: string, key: string, subKey: string): Promise<T | undefined>;
|
|
36
39
|
hdelete(topic: string, key: string, subKey: string): Promise<void>;
|
|
40
|
+
hkeys(topic: string, key: string): Promise<string[]>;
|
|
41
|
+
hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]>;
|
|
42
|
+
hclear(topic: string, key: string): Promise<void>;
|
|
37
43
|
getClient(): Redis;
|
|
38
44
|
onDestroy(): Promise<void>;
|
|
39
45
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import type { Client as LibsqlClient } from "@libsql/client";
|
|
3
3
|
import { type BaseEnv, type PromiseOrObject } from "akanjs/base";
|
|
4
|
-
import type
|
|
4
|
+
import { type ConstantModel } from "akanjs/constant";
|
|
5
5
|
import { type DatabaseModel, type DocumentQuery, type DocumentSchema, type DocumentUpdate, type DocumentUpdateOptions, type SchemaOf } from "akanjs/document";
|
|
6
6
|
import type { Sql } from "postgres";
|
|
7
7
|
export interface SqliteDatabaseConfig {
|
|
@@ -256,6 +256,8 @@ export declare class SqliteDocumentStore {
|
|
|
256
256
|
private decodeFieldValue;
|
|
257
257
|
private decodeMapValue;
|
|
258
258
|
private decodeNestedValue;
|
|
259
|
+
private normalizeWriteValue;
|
|
260
|
+
private fillScalarDefaults;
|
|
259
261
|
hydrate(data: DocumentRecord, originalData?: DocumentRecord): any;
|
|
260
262
|
private runHooks;
|
|
261
263
|
private insertStmt;
|
|
@@ -18,5 +18,8 @@ export declare class SolidCache extends SolidCache_base implements CacheAdaptor
|
|
|
18
18
|
}): Promise<void>;
|
|
19
19
|
hget<T extends string | number | Buffer>(topic: string, key: string, subKey: string): Promise<T | undefined>;
|
|
20
20
|
hdelete(topic: string, key: string, subKey: string): Promise<void>;
|
|
21
|
+
hkeys(topic: string, key: string): Promise<string[]>;
|
|
22
|
+
hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]>;
|
|
23
|
+
hclear(topic: string, key: string): Promise<void>;
|
|
21
24
|
}
|
|
22
25
|
export {};
|
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,4 +6,4 @@ export interface ModalProps {
|
|
|
6
6
|
children?: ReactNode;
|
|
7
7
|
onCancel?: () => void;
|
|
8
8
|
}
|
|
9
|
-
export declare const Modal: ({ className, bodyClassName, confirmClose, children, onCancel }: ModalProps) => import("react
|
|
9
|
+
export declare const Modal: ({ className, bodyClassName, confirmClose, children, onCancel }: ModalProps) => import("react").ReactPortal | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ProviderProps } from "./Provider.d.ts";
|
|
2
2
|
export declare const Dialog: {
|
|
3
3
|
({ children, ...props }: ProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
-
Modal: ({ className, bodyClassName, confirmClose, children, onCancel }: import("./Modal.d.ts").ModalProps) => import("react
|
|
4
|
+
Modal: ({ className, bodyClassName, confirmClose, children, onCancel }: import("./Modal.d.ts").ModalProps) => import("react").ReactPortal | null;
|
|
5
5
|
Title: ({ children }: import("./Title.d.ts").TitleProps) => null;
|
|
6
6
|
Action: ({ children }: import("./Action.d.ts").ActionProps) => null;
|
|
7
7
|
Trigger: ({ className, children }: import("./Trigger.d.ts").TriggerProps) => import("react/jsx-runtime").JSX.Element;
|
package/types/ui/Modal.d.ts
CHANGED
|
@@ -16,15 +16,4 @@ export interface ModalProps {
|
|
|
16
16
|
/** Ask for close confirmation before dismissing. */
|
|
17
17
|
confirmClose?: boolean;
|
|
18
18
|
}
|
|
19
|
-
export declare const Modal: {
|
|
20
|
-
({ className, title, action, open, onCancel, bodyClassName, children, confirmClose, }: ModalProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
-
Window: ({ open, onCancel, title, children }: WindowProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
22
|
-
};
|
|
23
|
-
interface WindowProps {
|
|
24
|
-
open: boolean;
|
|
25
|
-
onCancel: () => void;
|
|
26
|
-
title: ReactNode;
|
|
27
|
-
children: ReactNode;
|
|
28
|
-
}
|
|
29
|
-
export declare const Window: ({ open, onCancel, title, children }: WindowProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
30
|
-
export {};
|
|
19
|
+
export declare const Modal: ({ className, title, action, open, onCancel, bodyClassName, children, confirmClose, }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -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/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/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();
|