amateras 0.10.0 → 0.10.2
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/README.md +29 -25
- package/build/core.js +1 -0
- package/build/css.js +1 -0
- package/build/for.js +1 -0
- package/build/i18n.js +1 -0
- package/build/idb.js +1 -0
- package/build/if.js +1 -0
- package/build/import-map.js +1 -0
- package/build/markdown.js +1 -0
- package/build/match.js +1 -0
- package/build/meta.js +1 -0
- package/build/prefetch.js +1 -0
- package/build/router.js +1 -0
- package/build/signal.js +1 -0
- package/build/ui.js +1 -0
- package/build/widget.js +1 -0
- package/package.json +7 -5
- package/packages/core/package.json +19 -0
- package/packages/core/src/env.browser.ts +21 -0
- package/packages/core/src/env.node.ts +21 -0
- package/packages/core/src/global.ts +5 -0
- package/packages/core/src/index.ts +184 -0
- package/packages/core/src/lib/hmr.ts +145 -0
- package/packages/core/src/lib/symbols.ts +2 -0
- package/packages/core/src/structure/ElementProto.ts +95 -0
- package/packages/core/src/structure/GlobalState.ts +9 -0
- package/packages/core/src/structure/NodeProto.ts +18 -0
- package/packages/core/src/structure/Proto.ts +90 -0
- package/packages/core/src/structure/ProxyProto.ts +20 -0
- package/packages/core/src/structure/TextProto.ts +22 -0
- package/packages/core/src/structure/WidgetEvent.ts +17 -0
- package/packages/css/README.md +128 -0
- package/packages/css/package.json +15 -0
- package/packages/css/src/ext/colors/amber.ts +25 -0
- package/packages/css/src/ext/colors/blackwhite.ts +13 -0
- package/packages/css/src/ext/colors/blue.ts +25 -0
- package/packages/css/src/ext/colors/cyan.ts +25 -0
- package/packages/css/src/ext/colors/emerald.ts +25 -0
- package/packages/css/src/ext/colors/fuchsia.ts +25 -0
- package/packages/css/src/ext/colors/gray.ts +25 -0
- package/packages/css/src/ext/colors/green.ts +25 -0
- package/packages/css/src/ext/colors/indigo.ts +25 -0
- package/packages/css/src/ext/colors/lime.ts +25 -0
- package/packages/css/src/ext/colors/neutral.ts +25 -0
- package/packages/css/src/ext/colors/orange.ts +25 -0
- package/packages/css/src/ext/colors/pink.ts +25 -0
- package/packages/css/src/ext/colors/purple.ts +25 -0
- package/packages/css/src/ext/colors/red.ts +25 -0
- package/packages/css/src/ext/colors/rose.ts +25 -0
- package/packages/css/src/ext/colors/sky.ts +25 -0
- package/packages/css/src/ext/colors/slate.ts +25 -0
- package/packages/css/src/ext/colors/stone.ts +25 -0
- package/packages/css/src/ext/colors/teal.ts +25 -0
- package/packages/css/src/ext/colors/violet.ts +25 -0
- package/packages/css/src/ext/colors/yellow.ts +25 -0
- package/packages/css/src/ext/colors/zinc.ts +25 -0
- package/packages/css/src/ext/colors.ts +23 -0
- package/packages/css/src/ext/keyframes.ts +37 -0
- package/packages/css/src/ext/property.ts +68 -0
- package/packages/css/src/ext/variable.ts +51 -0
- package/packages/css/src/index.ts +103 -0
- package/packages/css/src/lib/cache.ts +27 -0
- package/packages/css/src/lib/colorAssign.ts +6 -0
- package/packages/css/src/lib/createRule.ts +31 -0
- package/packages/css/src/lib/utils.ts +1 -0
- package/packages/css/src/structure/$CSS.ts +4 -0
- package/packages/css/src/structure/$CSSKeyframes.ts +13 -0
- package/packages/css/src/structure/$CSSProperty.ts +21 -0
- package/packages/css/src/structure/$CSSRule.ts +39 -0
- package/packages/css/src/structure/$CSSVariable.ts +34 -0
- package/packages/css/src/types.ts +300 -0
- package/packages/for/package.json +15 -0
- package/packages/for/src/global.ts +7 -0
- package/packages/for/src/index.ts +15 -0
- package/packages/for/src/structure/For.ts +74 -0
- package/packages/hmr/package.json +13 -0
- package/packages/hmr/src/index.ts +27 -0
- package/packages/i18n/README.md +73 -0
- package/packages/i18n/package.json +15 -0
- package/packages/i18n/src/index.ts +78 -0
- package/packages/i18n/src/structure/I18n.ts +51 -0
- package/packages/i18n/src/structure/I18nDictionary.ts +31 -0
- package/packages/i18n/src/structure/I18nTranslation.ts +51 -0
- package/packages/i18n/src/types.ts +77 -0
- package/packages/idb/README.md +127 -0
- package/packages/idb/package.json +16 -0
- package/packages/idb/src/core.ts +6 -0
- package/packages/idb/src/index.ts +17 -0
- package/packages/idb/src/lib/$IDBRequest.ts +8 -0
- package/packages/idb/src/structure/$IDB.ts +63 -0
- package/packages/idb/src/structure/$IDBCursor.ts +34 -0
- package/packages/idb/src/structure/$IDBIndex.ts +48 -0
- package/packages/idb/src/structure/$IDBStore.ts +103 -0
- package/packages/idb/src/structure/$IDBStoreBase.ts +30 -0
- package/packages/idb/src/structure/$IDBTransaction.ts +38 -0
- package/packages/idb/src/structure/builder/$IDBBuilder.ts +229 -0
- package/packages/idb/src/structure/builder/$IDBStoreBuilder.ts +100 -0
- package/packages/if/package.json +15 -0
- package/packages/if/src/global.ts +15 -0
- package/packages/if/src/index.ts +51 -0
- package/packages/if/src/structure/Condition.ts +44 -0
- package/packages/if/src/structure/ConditionStatement.ts +25 -0
- package/packages/if/src/structure/Else.ts +6 -0
- package/packages/if/src/structure/ElseIf.ts +6 -0
- package/packages/if/src/structure/If.ts +6 -0
- package/packages/markdown/README.md +53 -0
- package/packages/markdown/package.json +15 -0
- package/packages/markdown/src/index.ts +3 -0
- package/packages/markdown/src/lib/type.ts +26 -0
- package/packages/markdown/src/lib/util.ts +21 -0
- package/packages/markdown/src/structure/Markdown.ts +57 -0
- package/packages/markdown/src/structure/MarkdownLexer.ts +111 -0
- package/packages/markdown/src/structure/MarkdownParser.ts +34 -0
- package/packages/markdown/src/syntax/alert.ts +46 -0
- package/packages/markdown/src/syntax/blockquote.ts +35 -0
- package/packages/markdown/src/syntax/bold.ts +11 -0
- package/packages/markdown/src/syntax/code.ts +11 -0
- package/packages/markdown/src/syntax/codeblock.ts +44 -0
- package/packages/markdown/src/syntax/heading.ts +14 -0
- package/packages/markdown/src/syntax/horizontalRule.ts +11 -0
- package/packages/markdown/src/syntax/image.ts +23 -0
- package/packages/markdown/src/syntax/italic.ts +11 -0
- package/packages/markdown/src/syntax/link.ts +46 -0
- package/packages/markdown/src/syntax/list.ts +121 -0
- package/packages/markdown/src/syntax/table.ts +67 -0
- package/packages/markdown/src/syntax/text.ts +19 -0
- package/packages/match/package.json +15 -0
- package/packages/match/src/global.ts +14 -0
- package/packages/match/src/index.ts +33 -0
- package/packages/match/src/structure/Case.ts +15 -0
- package/packages/match/src/structure/Default.ts +12 -0
- package/packages/match/src/structure/Match.ts +78 -0
- package/packages/meta/package.json +14 -0
- package/packages/meta/src/index.ts +36 -0
- package/packages/meta/src/lib/resolveMeta.ts +27 -0
- package/packages/meta/src/types.ts +36 -0
- package/packages/prefetch/package.json +14 -0
- package/packages/prefetch/src/index.ts +70 -0
- package/packages/router/README.md +18 -0
- package/packages/router/package.json +16 -0
- package/packages/router/src/global.ts +22 -0
- package/packages/router/src/index.ts +106 -0
- package/packages/router/src/structure/Link.ts +17 -0
- package/packages/router/src/structure/NavLink.ts +19 -0
- package/packages/router/src/structure/Page.ts +30 -0
- package/packages/router/src/structure/Route.ts +123 -0
- package/packages/router/src/structure/RouteGroup.ts +24 -0
- package/packages/router/src/structure/RouteNode.ts +54 -0
- package/packages/router/src/structure/RouteSlot.ts +34 -0
- package/packages/router/src/structure/Router.ts +192 -0
- package/packages/router/src/structure/RouterConstructor.ts +18 -0
- package/packages/router/src/types.ts +41 -0
- package/packages/signal/README.md +93 -0
- package/packages/signal/package.json +15 -0
- package/packages/signal/src/index.ts +97 -0
- package/packages/signal/src/lib/track.ts +18 -0
- package/packages/signal/src/structure/Signal.ts +59 -0
- package/packages/ui/package.json +14 -0
- package/packages/ui/src/index.ts +4 -0
- package/packages/ui/src/lib/slideshowAnimations.ts +39 -0
- package/packages/ui/src/structure/Radio.ts +77 -0
- package/packages/ui/src/structure/Slide.ts +11 -0
- package/packages/ui/src/structure/Slideshow.ts +99 -0
- package/packages/utils/package.json +18 -0
- package/packages/utils/src/global.ts +39 -0
- package/packages/utils/src/index.bun.ts +3 -0
- package/packages/utils/src/index.ts +2 -0
- package/packages/utils/src/lib/debugger.ts +14 -0
- package/packages/utils/src/lib/utils.ts +119 -0
- package/packages/utils/src/structure/UID.ts +18 -0
- package/packages/widget/README.md +29 -0
- package/packages/widget/package.json +14 -0
- package/packages/widget/src/index.ts +82 -0
- package/packages/widget/src/structure/Widget.ts +42 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { onclient, onserver } from "@amateras/core";
|
|
2
|
+
import { GlobalState } from "@amateras/core";
|
|
3
|
+
import { Proto } from "@amateras/core";
|
|
4
|
+
import { _null, _Object_assign, isAsyncFunction, toURL } from "@amateras/utils";
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
export namespace $ {
|
|
8
|
+
export function fetch<T = any>(url: string | URL, options?: RequestInit & FetchOptions<T>): Promise<T>
|
|
9
|
+
}
|
|
10
|
+
export var prefetch: {[key: string]: { expired: number, data: any }}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare module "@amateras/core" {
|
|
14
|
+
export interface GlobalState {
|
|
15
|
+
prefetch: {
|
|
16
|
+
fetches: Set<Promise<any>>,
|
|
17
|
+
caches: {[key: string]: { expired: number, data: any }}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type FetchOptions<T> = {
|
|
23
|
+
record?: (res: Response) => Promise<T> | void;
|
|
24
|
+
then?: (result: T) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_Object_assign(GlobalState.prototype, {
|
|
28
|
+
prefetch: {
|
|
29
|
+
fetches: new Set(),
|
|
30
|
+
caches: {}
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
GlobalState.disposers.add(global => {
|
|
35
|
+
global.prefetch.fetches.clear();
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
if (!globalThis.prefetch) globalThis.prefetch = {}
|
|
39
|
+
|
|
40
|
+
_Object_assign($, {
|
|
41
|
+
// 将资料注册到原型全局变量中:global.prefetch
|
|
42
|
+
// 保证每次全局渲染都在抓取完毕之后:将 Promise 添加到 global.prefetch.fetches 让根原型能确保所有 fetch 运行结束
|
|
43
|
+
// 将已抓取的资料发送到客户端:从 record 函数回传的资料将会被记录在 global.prefetch.caches 当中,并且以抓取 URL 作为索引。
|
|
44
|
+
// 客户端不会用到过时的资料:每个发送到客户端的资料缓存都附上了过期时间
|
|
45
|
+
async fetch<T>(url: string | URL, options?: RequestInit & FetchOptions<T>) {
|
|
46
|
+
url = toURL(url);
|
|
47
|
+
let proto = Proto.proto;
|
|
48
|
+
let cache = onclient() ? prefetch[url.href] : _null;
|
|
49
|
+
let then = options?.then;
|
|
50
|
+
let request = new Promise(async (resolve) => {
|
|
51
|
+
if (cache && Date.now() < cache.expired) {
|
|
52
|
+
then?.(cache.data);
|
|
53
|
+
resolve(cache);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let response = await fetch(url, options);
|
|
57
|
+
let record = options?.record;
|
|
58
|
+
if (record) {
|
|
59
|
+
const result = isAsyncFunction(record) ? await record(response) : record(response);
|
|
60
|
+
if (onserver() && proto) proto.global.prefetch.caches[url.href] = { data: result, expired: Date.now() + 30_000 };
|
|
61
|
+
$.context(Proto, proto, () => {
|
|
62
|
+
then?.(result);
|
|
63
|
+
})
|
|
64
|
+
resolve(result);
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
if (onserver()) proto?.global.prefetch.fetches.add(request)
|
|
68
|
+
return request
|
|
69
|
+
}
|
|
70
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Amateras Router
|
|
2
|
+
Router 是实现路由的标准模块,使用此模块就能轻松规划页面路径。
|
|
3
|
+
|
|
4
|
+
## 优势
|
|
5
|
+
- 路径参数类型保护
|
|
6
|
+
- 页面路径别名机制
|
|
7
|
+
- 路径分组功能
|
|
8
|
+
- 组件化页面
|
|
9
|
+
|
|
10
|
+
## 使用方式
|
|
11
|
+
使用 `$.router` 函数定义路由构造器(Router Constructor)。
|
|
12
|
+
```ts
|
|
13
|
+
import 'amateras';
|
|
14
|
+
import 'amateras/router';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 文档
|
|
18
|
+
[路由器](/docs/Router.md)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amateras/router",
|
|
3
|
+
"peerDependencies": {
|
|
4
|
+
"@amateras/core": "workspace:*",
|
|
5
|
+
"@amateras/widget": "workspace:*",
|
|
6
|
+
"@amateras/utils": "workspace:*"
|
|
7
|
+
},
|
|
8
|
+
"imports": {
|
|
9
|
+
"#structure/*": "./src/structure/*.ts",
|
|
10
|
+
"#lib/*": "./src/lib/*.ts",
|
|
11
|
+
"#node/*": "./src/node/*.ts"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/index.ts"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as _Link from "#structure/Link";
|
|
2
|
+
import * as _NavLink from "#structure/NavLink";
|
|
3
|
+
import * as _Router from "#structure/Router";
|
|
4
|
+
import type { Router, RouterHandle } from "#structure/RouterConstructor";
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
export var Link: typeof _Link.Link;
|
|
8
|
+
export var NavLink: typeof _NavLink.NavLink;
|
|
9
|
+
export function $(Router: Router): _Router.RouterProto;
|
|
10
|
+
|
|
11
|
+
export namespace $ {
|
|
12
|
+
export function router(handle: RouterHandle): Router;
|
|
13
|
+
|
|
14
|
+
export function open(path: string, target?: string): void;
|
|
15
|
+
export function forward(): void;
|
|
16
|
+
export function back(): void;
|
|
17
|
+
export function replace(path: string): void;
|
|
18
|
+
export function scrollRestoration(): void;
|
|
19
|
+
|
|
20
|
+
export function title(title: string): void;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Link } from '#structure/Link';
|
|
2
|
+
import { NavLink } from '#structure/NavLink';
|
|
3
|
+
import { Page } from '#structure/Page';
|
|
4
|
+
import { Route } from '#structure/Route';
|
|
5
|
+
import { RouteGroup } from '#structure/RouteGroup';
|
|
6
|
+
import { RouteNode } from '#structure/RouteNode';
|
|
7
|
+
import { RouterProto } from '#structure/Router';
|
|
8
|
+
import { RouterConstructor } from '#structure/RouterConstructor';
|
|
9
|
+
import { symbol_ProtoType } from '@amateras/core';
|
|
10
|
+
import { GlobalState } from '@amateras/core';
|
|
11
|
+
import { Proto } from '@amateras/core';
|
|
12
|
+
import { _instanceof, _Object_assign, isFunction, map } from '@amateras/utils';
|
|
13
|
+
import './global';
|
|
14
|
+
import type { PageLayout } from './types';
|
|
15
|
+
|
|
16
|
+
declare module "@amateras/core" {
|
|
17
|
+
export interface GlobalState {
|
|
18
|
+
title: string | null
|
|
19
|
+
router: {
|
|
20
|
+
routers: Set<RouterProto>;
|
|
21
|
+
resolve: (path: string) => Promise<void>[];
|
|
22
|
+
href: URL;
|
|
23
|
+
routes: Route[];
|
|
24
|
+
matchPaths: string[];
|
|
25
|
+
navlinks: Set<NavLink>;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let routePlannerPrototype = {
|
|
31
|
+
route(this: Route | RouterProto, path: string, layout: PageLayout<string>, handle?: (route: Route) => void) {
|
|
32
|
+
let route = new RouteNode(path, layout);
|
|
33
|
+
this.routes.set(path, route);
|
|
34
|
+
handle?.(route);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
group(this: Route | RouterProto, path: string, handle?: (route: Route) => void) {
|
|
38
|
+
let group = new RouteGroup(path);
|
|
39
|
+
this.routes.set(path, group);
|
|
40
|
+
handle?.(group);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
notfound() {
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_Object_assign(Route.prototype, routePlannerPrototype);
|
|
49
|
+
_Object_assign(RouterProto.prototype, routePlannerPrototype);
|
|
50
|
+
_Object_assign(GlobalState.prototype, {
|
|
51
|
+
router: {
|
|
52
|
+
routers: new Set<RouterProto>(),
|
|
53
|
+
resolve(this, path: string) {
|
|
54
|
+
return map(this.routers, router => router.resolve(path));
|
|
55
|
+
},
|
|
56
|
+
href: new URL('http://localhost'),
|
|
57
|
+
routes: [],
|
|
58
|
+
matchPaths: [],
|
|
59
|
+
navlinks: new Set()
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
GlobalState.disposers.add(({router}) => {
|
|
64
|
+
router.routers.clear();
|
|
65
|
+
router.routes = [];
|
|
66
|
+
router.matchPaths = [];
|
|
67
|
+
router.navlinks.clear();
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
_Object_assign($, {
|
|
71
|
+
router: (handle: ($$: RouterProto) => void) => RouterConstructor(handle),
|
|
72
|
+
open: RouterProto.open,
|
|
73
|
+
replace: RouterProto.replace,
|
|
74
|
+
back: RouterProto.back,
|
|
75
|
+
forward: RouterProto.forward,
|
|
76
|
+
scrollRestoration: RouterProto.scrollRestoration,
|
|
77
|
+
|
|
78
|
+
title(title: string) {
|
|
79
|
+
let parent = Proto.proto;
|
|
80
|
+
if (_instanceof(parent, Page)) {
|
|
81
|
+
parent.title = title;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
globalThis.Link = Link;
|
|
87
|
+
globalThis.NavLink = NavLink;
|
|
88
|
+
|
|
89
|
+
$.process.craft.add((value) => {
|
|
90
|
+
if (isFunction(value) && value[symbol_ProtoType] === 'Router') {
|
|
91
|
+
let proto = Proto.proto;
|
|
92
|
+
let router = new value() as RouterProto;
|
|
93
|
+
proto?.global.router.routers.add(router);
|
|
94
|
+
router.parent = proto;
|
|
95
|
+
return router;
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
export * from "#structure/Link";
|
|
100
|
+
export * from "#structure/Page";
|
|
101
|
+
export * from "#structure/Route";
|
|
102
|
+
export * from "#structure/RouteGroup";
|
|
103
|
+
export * from "#structure/RouteNode";
|
|
104
|
+
export * from "#structure/Router";
|
|
105
|
+
export * from "#structure/RouteSlot";
|
|
106
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ElementProto } from "@amateras/core";
|
|
2
|
+
|
|
3
|
+
export class Link extends ElementProto<HTMLAnchorElement> {
|
|
4
|
+
constructor(attr: $.Props | null, layout?: $.Layout<ElementProto<HTMLAnchorElement>>) {
|
|
5
|
+
super('a', attr, layout);
|
|
6
|
+
this.on('click', e => {
|
|
7
|
+
if (e.shiftKey || e.ctrlKey) return;
|
|
8
|
+
e.preventDefault();
|
|
9
|
+
let target = this.attr('target');
|
|
10
|
+
let href = this.attr('href');
|
|
11
|
+
if (href) {
|
|
12
|
+
if (target === '_replace') $.replace(href)
|
|
13
|
+
else $.open(href, target)
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Link } from "#structure/Link";
|
|
2
|
+
import type { ElementProto } from "@amateras/core";
|
|
3
|
+
import { _null, toURL } from "@amateras/utils";
|
|
4
|
+
|
|
5
|
+
export class NavLink extends Link {
|
|
6
|
+
constructor(attr: $.Props | null, layout?: $.Layout<ElementProto<HTMLAnchorElement>>) {
|
|
7
|
+
super(attr, layout);
|
|
8
|
+
this.global.router.navlinks.add(this);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
checkActive() {
|
|
12
|
+
let href = this.attr('href');
|
|
13
|
+
if (!href) return;
|
|
14
|
+
for (let path of this.global.router.matchPaths) {
|
|
15
|
+
if (toURL(path).href === toURL(href).href) return this.attr('active', '');
|
|
16
|
+
}
|
|
17
|
+
this.attr('active', _null);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Proto } from "@amateras/core";
|
|
2
|
+
import { _null } from "@amateras/utils";
|
|
3
|
+
import type { AsyncWidget, PageLayout } from "../types";
|
|
4
|
+
import type { RouteNode } from "./RouteNode";
|
|
5
|
+
import { RouteSlot } from "./RouteSlot";
|
|
6
|
+
|
|
7
|
+
export class Page extends Proto {
|
|
8
|
+
slot = new RouteSlot();
|
|
9
|
+
builded = false;
|
|
10
|
+
route: RouteNode;
|
|
11
|
+
title: string | null = _null;
|
|
12
|
+
declare layout: () => void | AsyncWidget[0];
|
|
13
|
+
constructor(route: RouteNode, layout: PageLayout, params: Record<string, string>) {
|
|
14
|
+
super(() => layout({
|
|
15
|
+
params,
|
|
16
|
+
slot: this.slot
|
|
17
|
+
}));
|
|
18
|
+
this.route = route;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override build() {
|
|
22
|
+
if (!this.builded) this.builded = true;
|
|
23
|
+
return super.build();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Page {
|
|
28
|
+
get parent(): RouteSlot;
|
|
29
|
+
set parent(proto: Proto);
|
|
30
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { _undefined, isFunction, isUndefined, map } from "@amateras/utils";
|
|
2
|
+
import type { Widget } from "@amateras/widget";
|
|
3
|
+
import type { AliasRequired, AsyncWidget, PageLayout, PathConcat, PathToParamsMap, RouteParams, RoutePath, ValidatePath } from "../types";
|
|
4
|
+
import type { Page } from "./Page";
|
|
5
|
+
import type { RouteSlot } from "./RouteSlot";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export abstract class Route<ParentPath extends RoutePath = any, Path extends RoutePath = any, Params = any> {
|
|
9
|
+
declare protos: Set<Page | Route>;
|
|
10
|
+
declare parentPath: ParentPath;
|
|
11
|
+
declare params: Params;
|
|
12
|
+
routes = new Map<string, Route>();
|
|
13
|
+
path: string;
|
|
14
|
+
paths = new Map<string, RouteParams | (() => RouteParams) | undefined>();
|
|
15
|
+
validPaths: string[] = []
|
|
16
|
+
constructor(path: Path) {
|
|
17
|
+
this.path = path;
|
|
18
|
+
this.paths.set(path, _undefined);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
abstract resolve(path: string, slot: RouteSlot, params: Record<string, string>): Promise<Route[] | void>;
|
|
22
|
+
|
|
23
|
+
routing(path: string) {
|
|
24
|
+
let pathSegList = path.split('/');
|
|
25
|
+
let params: Record<string, string> = {};
|
|
26
|
+
let passPath = '';
|
|
27
|
+
|
|
28
|
+
skipPath: for (let [selfPath, getParams] of this.paths) {
|
|
29
|
+
params = {};
|
|
30
|
+
let selfSegList = selfPath.split('/');
|
|
31
|
+
let segList: [string | undefined, string | undefined][] = [];
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < Math.max(pathSegList.length, selfSegList.length); i++)
|
|
34
|
+
segList.push([selfSegList[i], pathSegList[i]])
|
|
35
|
+
|
|
36
|
+
skipSeg: for (let [selfSeg, pathSeg] of segList) {
|
|
37
|
+
let skip = () => {
|
|
38
|
+
passPath = '';
|
|
39
|
+
}
|
|
40
|
+
let pass = () => {
|
|
41
|
+
passPath += (passPath !== '/' ? '/' : '') + pathSeg;
|
|
42
|
+
}
|
|
43
|
+
if (isUndefined(selfSeg)) {
|
|
44
|
+
// all path segment matched;
|
|
45
|
+
break skipSeg;
|
|
46
|
+
}
|
|
47
|
+
if (isUndefined(pathSeg)) {
|
|
48
|
+
// this route path is longer than target path;
|
|
49
|
+
skip();
|
|
50
|
+
continue skipPath;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (selfSeg?.includes(':')) {
|
|
54
|
+
let [prefix, name] = selfSeg.split(':') as [string, string];
|
|
55
|
+
if (!pathSeg.startsWith(prefix)) {
|
|
56
|
+
skip();
|
|
57
|
+
continue skipPath;
|
|
58
|
+
}
|
|
59
|
+
// path params
|
|
60
|
+
params[name] = pathSeg.replace(prefix, '');
|
|
61
|
+
pass();
|
|
62
|
+
continue skipSeg;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (selfSeg !== pathSeg) {
|
|
66
|
+
// this segment not matched
|
|
67
|
+
skip();
|
|
68
|
+
continue skipPath;
|
|
69
|
+
}
|
|
70
|
+
pass();
|
|
71
|
+
}
|
|
72
|
+
if (!passPath) continue skipPath;
|
|
73
|
+
params = {...params, ...isFunction(getParams) ? getParams() : getParams}
|
|
74
|
+
break skipPath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!passPath) return;
|
|
78
|
+
let pathId = Route.resolvePath(this.path, params);
|
|
79
|
+
this.validPaths = map(this.paths, path => Route.resolvePath(path[0], params));
|
|
80
|
+
return [pathId, passPath, params] as const
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private static resolvePath(path: string, params: any) {
|
|
84
|
+
return path.replaceAll(/:([^/]+)/g, (_, $1) => `${params[$1]}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
alias<
|
|
88
|
+
_Path extends RoutePath,
|
|
89
|
+
_Params extends AliasRequired<Params, PathToParamsMap<_Path>>,
|
|
90
|
+
Required extends keyof _Params extends [never] ? [] : [_Params | (() => _Params)]
|
|
91
|
+
>(path: _Path, ...required: Required): void;
|
|
92
|
+
alias(path: string, required?: RouteParams | (() => RouteParams)) {
|
|
93
|
+
this.paths.set(path, required)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface Route<ParentPath extends RoutePath = any, Path extends RoutePath = any, Params = any> {
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
route<_Path extends string, Props>(
|
|
101
|
+
path: ValidatePath<_Path, Props, PathConcat<ParentPath, Path, _Path>>,
|
|
102
|
+
widget: Widget<any, Props>,
|
|
103
|
+
handle?: (route: Route<PathConcat<ParentPath, Path>, _Path, PathToParamsMap<PathConcat<Path, _Path>>>) => void): void
|
|
104
|
+
|
|
105
|
+
route<_Path extends string, Props>(
|
|
106
|
+
path: ValidatePath<_Path, Props, PathConcat<ParentPath, Path, _Path>>,
|
|
107
|
+
widget: AsyncWidget<Props>,
|
|
108
|
+
handle?: (route: Route<PathConcat<ParentPath, Path>, _Path, PathToParamsMap<PathConcat<Path, _Path>>>) => void): void
|
|
109
|
+
|
|
110
|
+
route<
|
|
111
|
+
_Path extends RoutePath,
|
|
112
|
+
Layout extends PageLayout<PathConcat<ParentPath, Path, _Path>>
|
|
113
|
+
>(path: _Path, layout: Layout, handle?: (route: Route<PathConcat<ParentPath, Path>, _Path, PathToParamsMap<PathConcat<Path, _Path>>>) => void): void
|
|
114
|
+
|
|
115
|
+
group<
|
|
116
|
+
_Path extends RoutePath
|
|
117
|
+
>(path: _Path, handle: (route: Route<ParentPath, _Path, PathToParamsMap<PathConcat<Path, _Path>>>) => void): this;
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Route } from "./Route";
|
|
2
|
+
import type { RouteSlot } from "./RouteSlot";
|
|
3
|
+
|
|
4
|
+
export class RouteGroup extends Route {
|
|
5
|
+
constructor(path: string) {
|
|
6
|
+
super(path);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async resolve(path: string, slot: RouteSlot, params: Record<string, string>): Promise<Route[] | void> {
|
|
10
|
+
let result = this.routing(path);
|
|
11
|
+
if (!result) return;
|
|
12
|
+
let [, passPath, selfParams] = result;
|
|
13
|
+
|
|
14
|
+
params = {...params, ...selfParams};
|
|
15
|
+
let restPath = path.replace(passPath, '');
|
|
16
|
+
// handler '/' at path end
|
|
17
|
+
|
|
18
|
+
for (let [_name, route] of this.routes) {
|
|
19
|
+
let result = await route.resolve(restPath || '/', slot, params)
|
|
20
|
+
if (result) return [this, ...result];
|
|
21
|
+
}
|
|
22
|
+
return [this];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { symbol_ProtoType } from "@amateras/core";
|
|
2
|
+
import { _null, isArray } from "@amateras/utils";
|
|
3
|
+
import type { Widget } from "@amateras/widget";
|
|
4
|
+
import type { AsyncWidget, PageLayout } from "../types";
|
|
5
|
+
import { Page } from "./Page";
|
|
6
|
+
import { Route } from "./Route";
|
|
7
|
+
import type { RouteSlot } from "./RouteSlot";
|
|
8
|
+
|
|
9
|
+
export class RouteNode extends Route {
|
|
10
|
+
pages = new Map<string, Page>();
|
|
11
|
+
page: Page | null = _null;
|
|
12
|
+
#layout: Widget | PageLayout | AsyncWidget;
|
|
13
|
+
constructor(path: string, layout: Widget | PageLayout | AsyncWidget) {
|
|
14
|
+
super(path);
|
|
15
|
+
this.#layout = layout;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async resolve(path: string, slot: RouteSlot, params: Record<string, string>): Promise<Route[] | void> {
|
|
19
|
+
let result = this.routing(path);
|
|
20
|
+
if (!result) return;
|
|
21
|
+
let [pathId, passPath, selfParams] = result;
|
|
22
|
+
params = { ...params, ...selfParams };
|
|
23
|
+
let page = await this.usePage(pathId, params, slot);
|
|
24
|
+
let restPath = path.replace(passPath, '');
|
|
25
|
+
for (let [_name, route] of this.routes) {
|
|
26
|
+
let result = await route.resolve(restPath || '/', page.slot, params)
|
|
27
|
+
if (result) return [this, ...result];
|
|
28
|
+
}
|
|
29
|
+
if (!restPath) return [this];
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async usePage(path: string, params: Record<string, string>, slot: RouteSlot) {
|
|
34
|
+
let page = this.pages.get(path);
|
|
35
|
+
if (!page) {
|
|
36
|
+
let layout = this.#layout;
|
|
37
|
+
let _layout;
|
|
38
|
+
if (isArray(layout)) {
|
|
39
|
+
let widget = await layout[0]().then(mod => mod.default);
|
|
40
|
+
_layout = () => $(widget, params, () => $(page!.slot));
|
|
41
|
+
} else {
|
|
42
|
+
//@ts-ignore
|
|
43
|
+
_layout = this.#layout[symbol_ProtoType] === 'Widget' // is widget constructor
|
|
44
|
+
? () => $(this.#layout as Widget, params, () => $(page!.slot))
|
|
45
|
+
: this.#layout as PageLayout;
|
|
46
|
+
}
|
|
47
|
+
page = new Page(this, _layout, params);
|
|
48
|
+
this.pages.set(path, page);
|
|
49
|
+
}
|
|
50
|
+
this.page = page;
|
|
51
|
+
slot.render(page);
|
|
52
|
+
return page;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { onserver } from "@amateras/core";
|
|
2
|
+
import { ProxyProto } from "@amateras/core";
|
|
3
|
+
import { _null } from "@amateras/utils";
|
|
4
|
+
import type { Page } from "./Page";
|
|
5
|
+
|
|
6
|
+
export class RouteSlot extends ProxyProto {
|
|
7
|
+
page: Page | null = _null;
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
render(page: Page) {
|
|
13
|
+
if (this.page === page) return;
|
|
14
|
+
this.clear();
|
|
15
|
+
this.layout = () => $(page);
|
|
16
|
+
page.parent = this;
|
|
17
|
+
if (this.page !== page) this.page?.removeNode();
|
|
18
|
+
this.page = page;
|
|
19
|
+
if (this.node) {
|
|
20
|
+
if (!page.builded) page.build();
|
|
21
|
+
let nodes = this.toDOM();
|
|
22
|
+
this.node.replaceWith(...nodes);
|
|
23
|
+
// set title from page
|
|
24
|
+
if (page.title) {
|
|
25
|
+
document.title = page.title;
|
|
26
|
+
page.global.title = page.title;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (onserver()) {
|
|
30
|
+
if (!page.builded) page.build();
|
|
31
|
+
page.global.title = page.title;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|