@nutin/cli 1.2.3 → 1.3.0
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 +36 -1
- package/README.md +23 -15
- package/lib/index.mjs +7 -4
- package/lib/src/context-builder.mjs +35 -0
- package/lib/src/file-generator.mjs +26 -126
- package/lib/src/git-manager.mjs +1 -1
- package/lib/src/json-generator.mjs +31 -0
- package/lib/src/package-manager.mjs +21 -18
- package/lib/src/project-generator.mjs +53 -0
- package/lib/src/prompts.mjs +28 -15
- package/lib/src/template-compiler.mjs +1 -1
- package/lib/src/utils.mjs +9 -9
- package/lib/src/version.mjs +1 -0
- package/package.json +1 -1
- package/templates/base/.gitignore.hbs +1 -0
- package/templates/base/builder.config.js.hbs +7 -0
- package/templates/base/src/app/main.ts.hbs +2 -1
- package/templates/base/src/app/views/home/home.view.test.js.hbs +26 -0
- package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
- package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +1 -1
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +23 -20
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +4 -4
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +26 -13
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +9 -9
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +40 -8
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +16 -16
- package/templates/base/src/core/base-classes/component/component.ts.hbs +12 -12
- package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/service/service.ts.hbs +35 -12
- package/templates/base/src/core/base-classes/view/view.ts.hbs +6 -6
- package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +13 -13
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +18 -18
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +24 -24
- package/templates/base/src/core/services/http-client/http-client.ts.hbs +19 -19
- package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +6 -6
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +13 -13
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +22 -22
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +6 -5
- package/templates/base/src/core/services/router/router.ts.hbs +45 -45
- package/templates/base/src/core/services/store/store.ts.hbs +6 -6
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +6 -8
- package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +9 -11
- package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +11 -5
- package/templates/base/src/libs/pipes/pipes.ts.hbs +1 -1
- package/templates/base/src/libs/popover/popover.ts.hbs +48 -48
- package/templates/base/src/libs/snackbar/snackbar.ts.hbs +1 -1
- package/templates/base/tools/builder/builder.js.hbs +9 -3
- package/templates/base/tools/builder/core/add-tags.js.hbs +46 -0
- package/templates/base/tools/builder/core/copy-static.js.hbs +35 -35
- package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -6
- package/templates/base/tools/builder/core/hash-files.js.hbs +52 -0
- package/templates/base/tools/builder/core/validate-html.js.hbs +12 -32
- package/templates/base/tools/utils/get-files-recursive.js.hbs +17 -8
- package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +56 -0
- package/templates/{base → features/deployHelper}/tools/deployment/Dockerfile.hbs +12 -4
- package/templates/{base → features/deployHelper}/tools/deployment/nginx.conf.hbs +30 -2
- package/templates/features/i18n/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +4 -6
- package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +38 -47
- package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +17 -0
- package/templates/features/template/src/app/views/home/home.view.html.hbs +1 -1
- package/templates/features/template/tools/builder/core/merge-templates.js.hbs +2 -2
- package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +2 -2
- package/templates/features/testinNutin/testin-nutin/runner.js.hbs +1 -0
- package/templates/features/testinNutin/testin-nutin.config.js.hbs +2 -2
- package/templates/base/tools/deployment/gzip.conf.hbs +0 -19
- package/templates/features/i18n/src/app/views/home/locales/fr.json.hbs +0 -9
- /package/templates/{base → features/deployHelper}/.dockerignore.hbs +0 -0
|
@@ -12,11 +12,21 @@ export interface IRequestConfig {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export class HttpBuilder {
|
|
15
|
-
static
|
|
16
|
-
|
|
15
|
+
public static buildRequestOptions(
|
|
16
|
+
method: HttpMethod,
|
|
17
|
+
data: unknown,
|
|
18
|
+
config: IRequestConfig,
|
|
19
|
+
signal: AbortSignal
|
|
20
|
+
): RequestInit {
|
|
21
|
+
return {
|
|
22
|
+
method,
|
|
23
|
+
headers: config.headers,
|
|
24
|
+
body: this.buildRequestBody(data),
|
|
25
|
+
signal
|
|
26
|
+
};
|
|
17
27
|
}
|
|
18
28
|
|
|
19
|
-
static buildRequestUrl(endpoint: string, queryParams?: Record<string, string>): URL {
|
|
29
|
+
public static buildRequestUrl(endpoint: string, queryParams?: Record<string, string>): URL {
|
|
20
30
|
const url = new URL(endpoint);
|
|
21
31
|
|
|
22
32
|
if (queryParams) {
|
|
@@ -26,23 +36,13 @@ export class HttpBuilder {
|
|
|
26
36
|
return url;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
static
|
|
39
|
+
private static buildRequestBody(data: unknown): string | undefined {
|
|
40
|
+
return data ? JSON.stringify(data) : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private static appendQueryParams(url: URL, queryParams: Record<string, string>): void {
|
|
30
44
|
Object.entries(queryParams).forEach(([key, value]) => {
|
|
31
45
|
url.searchParams.append(key, value);
|
|
32
46
|
});
|
|
33
47
|
}
|
|
34
|
-
|
|
35
|
-
static buildRequestOptions(
|
|
36
|
-
method: HttpMethod,
|
|
37
|
-
data: unknown,
|
|
38
|
-
config: IRequestConfig,
|
|
39
|
-
signal: AbortSignal
|
|
40
|
-
): RequestInit {
|
|
41
|
-
return {
|
|
42
|
-
method,
|
|
43
|
-
headers: config.headers,
|
|
44
|
-
body: this.buildRequestBody(data),
|
|
45
|
-
signal
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
48
|
}
|
|
@@ -16,7 +16,7 @@ export class HttpError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export class HttpManager {
|
|
19
|
-
static createAbortController(timeout?: number): IAbortControllerSetup {
|
|
19
|
+
public static createAbortController(timeout?: number): IAbortControllerSetup {
|
|
20
20
|
const controller = new AbortController();
|
|
21
21
|
const timeoutId = timeout ?
|
|
22
22
|
setTimeout(() => controller.abort(), timeout) :
|
|
@@ -25,25 +25,13 @@ export class HttpManager {
|
|
|
25
25
|
return { controller, timeoutId };
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
static async
|
|
29
|
-
try {
|
|
30
|
-
return await response.json();
|
|
31
|
-
} catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
static async validateResponse(response: Response): Promise<void> {
|
|
28
|
+
public static async validateResponse(response: Response): Promise<void> {
|
|
37
29
|
if (!response.ok) {
|
|
38
30
|
await this.handleResponseError(response);
|
|
39
31
|
}
|
|
40
32
|
}
|
|
41
|
-
|
|
42
|
-
static isJsonResponse(contentType: string | null): boolean {
|
|
43
|
-
return contentType?.includes('application/json') ?? false;
|
|
44
|
-
}
|
|
45
33
|
|
|
46
|
-
static async parseSuccessResponse<T>(response: Response): Promise<T> {
|
|
34
|
+
public static async parseSuccessResponse<T>(response: Response): Promise<T> {
|
|
47
35
|
const contentType = response.headers.get('content-type');
|
|
48
36
|
|
|
49
37
|
if (this.isJsonResponse(contentType)) {
|
|
@@ -53,25 +41,37 @@ export class HttpManager {
|
|
|
53
41
|
return await response.text() as unknown as T;
|
|
54
42
|
}
|
|
55
43
|
|
|
56
|
-
static
|
|
57
|
-
return error instanceof DOMException && error.name === 'AbortError';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
static handleRequestError(error: unknown): never {
|
|
44
|
+
public static handleRequestError(error: unknown): never {
|
|
61
45
|
if (this.isTimeoutError(error)) {
|
|
62
46
|
throw new Error('Request timed out');
|
|
63
47
|
}
|
|
64
48
|
throw error;
|
|
65
49
|
}
|
|
66
50
|
|
|
67
|
-
static
|
|
51
|
+
public static cleanupTimeout(timeoutId: TimeoutType | null): void {
|
|
52
|
+
if (timeoutId) {
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private static async handleResponseError(response: Response): Promise<never> {
|
|
68
58
|
const errorData = await this.safeParseErrorResponse(response);
|
|
69
59
|
throw new HttpError(response.status, response.statusText, errorData);
|
|
70
60
|
}
|
|
71
61
|
|
|
72
|
-
static
|
|
73
|
-
|
|
74
|
-
|
|
62
|
+
private static isJsonResponse(contentType: string | null): boolean {
|
|
63
|
+
return contentType?.includes('application/json') ?? false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private static async safeParseErrorResponse(response: Response): Promise<unknown> {
|
|
67
|
+
try {
|
|
68
|
+
return await response.json();
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
75
71
|
}
|
|
76
72
|
}
|
|
73
|
+
|
|
74
|
+
private static isTimeoutError(error: unknown): boolean {
|
|
75
|
+
return error instanceof DOMException && error.name === 'AbortError';
|
|
76
|
+
}
|
|
77
77
|
}
|
|
@@ -17,15 +17,31 @@ export class HttpClient extends Service<HttpClient> {
|
|
|
17
17
|
this._defaultHeaders = { 'Content-Type': 'application/json', ...defaultHeaders };
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
public
|
|
20
|
+
public get<T = unknown>(endpoint: string, config?: IRequestConfig): Promise<T> {
|
|
21
|
+
return this.request<T>('GET', endpoint, null, config);
|
|
22
|
+
}
|
|
23
|
+
public post<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig): Promise<T> {
|
|
24
|
+
return this.request<T>('POST', endpoint, data, config);
|
|
25
|
+
}
|
|
26
|
+
public put<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig): Promise<T> {
|
|
27
|
+
return this.request<T>('PUT', endpoint, data, config);
|
|
28
|
+
}
|
|
29
|
+
public patch<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig): Promise<T> {
|
|
30
|
+
return this.request<T>('PATCH', endpoint, data, config);
|
|
31
|
+
}
|
|
32
|
+
public delete<T = unknown>(endpoint: string, config?: IRequestConfig): Promise<T> {
|
|
33
|
+
return this.request<T>('DELETE', endpoint, undefined, config);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public addRequestInterceptor(fn: (url: string, options: RequestInit) => void): void {
|
|
21
37
|
this._requestInterceptors.push(fn);
|
|
22
38
|
}
|
|
23
39
|
|
|
24
|
-
public addResponseInterceptor(fn: (response: Response) => void) {
|
|
40
|
+
public addResponseInterceptor(fn: (response: Response) => void): void {
|
|
25
41
|
this._responseInterceptors.push(fn);
|
|
26
42
|
}
|
|
27
43
|
|
|
28
|
-
onDestroy(): void {
|
|
44
|
+
protected onDestroy(): void {
|
|
29
45
|
this._baseUrl = '';
|
|
30
46
|
this._defaultHeaders = {};
|
|
31
47
|
this._requestInterceptors = [];
|
|
@@ -60,22 +76,6 @@ export class HttpClient extends Service<HttpClient> {
|
|
|
60
76
|
HttpManager.cleanupTimeout(timeoutId);
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
|
-
|
|
64
|
-
public get<T = unknown>(endpoint: string, config?: IRequestConfig) {
|
|
65
|
-
return this.request<T>('GET', endpoint, null, config);
|
|
66
|
-
}
|
|
67
|
-
public post<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig) {
|
|
68
|
-
return this.request<T>('POST', endpoint, data, config);
|
|
69
|
-
}
|
|
70
|
-
public put<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig) {
|
|
71
|
-
return this.request<T>('PUT', endpoint, data, config);
|
|
72
|
-
}
|
|
73
|
-
public patch<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig) {
|
|
74
|
-
return this.request<T>('PATCH', endpoint, data, config);
|
|
75
|
-
}
|
|
76
|
-
public delete<T = unknown>(endpoint: string, config?: IRequestConfig) {
|
|
77
|
-
return this.request<T>('DELETE', endpoint, undefined, config);
|
|
78
|
-
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
export const AppHttpClient = HttpClient.getInstance();
|
|
@@ -7,11 +7,7 @@ class PipeRegistry extends Service<PipeRegistry> {
|
|
|
7
7
|
|
|
8
8
|
constructor() { super(); }
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
this._pipes = {};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
register(name: string, fn: PipeFunction): void {
|
|
10
|
+
public register(name: string, fn: PipeFunction): void {
|
|
15
11
|
if(this._pipes[name]) {
|
|
16
12
|
console.warn(`Skipping pipe "${name}": A pipe with this name already exists.`);
|
|
17
13
|
return;
|
|
@@ -19,7 +15,7 @@ class PipeRegistry extends Service<PipeRegistry> {
|
|
|
19
15
|
this._pipes[name] = fn;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
apply(name: string, value: any, args: string[] = []): string {
|
|
18
|
+
public apply(name: string, value: any, args: string[] = []): string {
|
|
23
19
|
const pipe = this._pipes[name];
|
|
24
20
|
if (!pipe) {
|
|
25
21
|
console.warn(`Pipe "${name}" not found.`);
|
|
@@ -27,6 +23,10 @@ class PipeRegistry extends Service<PipeRegistry> {
|
|
|
27
23
|
}
|
|
28
24
|
return pipe(value, ...args);
|
|
29
25
|
}
|
|
26
|
+
|
|
27
|
+
protected onDestroy(): void {
|
|
28
|
+
this._pipes = {};
|
|
29
|
+
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export const AppPipeRegistry = PipeRegistry.getInstance();
|
|
@@ -4,20 +4,12 @@ import { {{#if i18n}}I18nService, {{/if}}View } from "../../../index.js";
|
|
|
4
4
|
* Navigation - handles path normalization and history management
|
|
5
5
|
*/
|
|
6
6
|
export class NavigationManager {
|
|
7
|
-
static normalizePath(path: string): string {
|
|
7
|
+
public static normalizePath(path: string): string {
|
|
8
8
|
const url = new URL(path, window.location.origin);
|
|
9
9
|
return (url.pathname || '/').replace(/\/+$/, '') || '/';
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
static
|
|
13
|
-
pushState: boolean,
|
|
14
|
-
currentPath: string,
|
|
15
|
-
normalizedPath: string
|
|
16
|
-
): boolean {
|
|
17
|
-
return pushState && currentPath !== normalizedPath;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
static updateHistory(
|
|
12
|
+
public static updateHistory(
|
|
21
13
|
normalizedPath: string,
|
|
22
14
|
currentPath: string,
|
|
23
15
|
pushState: boolean
|
|
@@ -27,7 +19,7 @@ export class NavigationManager {
|
|
|
27
19
|
}
|
|
28
20
|
}
|
|
29
21
|
|
|
30
|
-
static updateMetaContent(currentView: View): void {
|
|
22
|
+
public static updateMetaContent(currentView: View): void {
|
|
31
23
|
if (!currentView.shouldUpdateMetaContent()) return;
|
|
32
24
|
|
|
33
25
|
const strippedName = currentView.viewName.replace('-view', '');
|
|
@@ -44,11 +36,11 @@ export class NavigationManager {
|
|
|
44
36
|
{{/if}}
|
|
45
37
|
}
|
|
46
38
|
|
|
47
|
-
static getCurrentPath(): string {
|
|
39
|
+
public static getCurrentPath(): string {
|
|
48
40
|
return this.normalizePath(window.location.pathname);
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
static matchPattern(pattern: string, path: string): Record<string, string> | null {
|
|
43
|
+
public static matchPattern(pattern: string, path: string): Record<string, string> | null {
|
|
52
44
|
const paramNames: string[] = [];
|
|
53
45
|
|
|
54
46
|
const regexPattern = pattern
|
|
@@ -75,4 +67,12 @@ export class NavigationManager {
|
|
|
75
67
|
});
|
|
76
68
|
return params;
|
|
77
69
|
}
|
|
70
|
+
|
|
71
|
+
private static shouldPushState(
|
|
72
|
+
pushState: boolean,
|
|
73
|
+
currentPath: string,
|
|
74
|
+
normalizedPath: string
|
|
75
|
+
): boolean {
|
|
76
|
+
return pushState && currentPath !== normalizedPath;
|
|
77
|
+
}
|
|
78
78
|
}
|
|
@@ -25,31 +25,11 @@ export type RouteConfig = (() => View) | {
|
|
|
25
25
|
export type Routes = Record<string, RouteConfig>;
|
|
26
26
|
|
|
27
27
|
export class RouteGuardsManager {
|
|
28
|
-
static getViewConstructor(routeConfig: RouteConfig): () => View {
|
|
28
|
+
public static getViewConstructor(routeConfig: RouteConfig): () => View {
|
|
29
29
|
return typeof routeConfig === 'function' ? routeConfig : routeConfig.view;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
static
|
|
33
|
-
return typeof routeConfig === 'function' ? [] : (routeConfig.guards || []);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Runs all guards in sequence with route parameters and returns the result
|
|
38
|
-
*/
|
|
39
|
-
static async runGuards(
|
|
40
|
-
guards: RouteGuard[],
|
|
41
|
-
params: Record<string, string>
|
|
42
|
-
): Promise<boolean | string> {
|
|
43
|
-
for (const guard of guards) {
|
|
44
|
-
const result = await guard(params);
|
|
45
|
-
if (result !== true) {
|
|
46
|
-
return result; // false or redirect path
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
static async processRouteGuards(
|
|
32
|
+
public static async processRouteGuards(
|
|
53
33
|
routeConfig: RouteConfig,
|
|
54
34
|
targetPath: string,
|
|
55
35
|
params: Record<string, string> = {}
|
|
@@ -71,4 +51,24 @@ export class RouteGuardsManager {
|
|
|
71
51
|
const viewConstructor = this.getViewConstructor(routeConfig);
|
|
72
52
|
return { allowed: true, viewConstructor };
|
|
73
53
|
}
|
|
54
|
+
|
|
55
|
+
private static getRouteGuards(routeConfig: RouteConfig): RouteGuard[] {
|
|
56
|
+
return typeof routeConfig === 'function' ? [] : (routeConfig.guards || []);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Runs all guards in sequence with route parameters and returns the result
|
|
61
|
+
*/
|
|
62
|
+
private static async runGuards(
|
|
63
|
+
guards: RouteGuard[],
|
|
64
|
+
params: Record<string, string>
|
|
65
|
+
): Promise<boolean | string> {
|
|
66
|
+
for (const guard of guards) {
|
|
67
|
+
const result = await guard(params);
|
|
68
|
+
if (result !== true) {
|
|
69
|
+
return result; // false or redirect path
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
74
|
}
|
|
@@ -5,7 +5,7 @@ import { View, AppEventBus } from '../../../index.js';
|
|
|
5
5
|
*/
|
|
6
6
|
export class ViewRenderManager {
|
|
7
7
|
{{#if transition}}
|
|
8
|
-
static renderAnimatedView(view: View): void {
|
|
8
|
+
public static renderAnimatedView(view: View): void {
|
|
9
9
|
const container = document.getElementById('app');
|
|
10
10
|
const el = view.render();
|
|
11
11
|
el.classList.add('app-view-transition');
|
|
@@ -17,7 +17,7 @@ export class ViewRenderManager {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
{{/if}}
|
|
20
|
-
static async transitionOutCurrentView(currentView: View | null): Promise<null> {
|
|
20
|
+
public static async transitionOutCurrentView(currentView: View | null): Promise<null> {
|
|
21
21
|
if (!currentView) return null;
|
|
22
22
|
{{#if transition}}
|
|
23
23
|
const container = document.getElementById('app');
|
|
@@ -33,7 +33,7 @@ export class ViewRenderManager {
|
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
static renderNewView(
|
|
36
|
+
public static renderNewView(
|
|
37
37
|
viewConstructor: () => View,
|
|
38
38
|
params: Record<string, string> = {}
|
|
39
39
|
): View {
|
|
@@ -49,13 +49,14 @@ export class ViewRenderManager {
|
|
|
49
49
|
view.render();
|
|
50
50
|
|
|
51
51
|
{{/if}}
|
|
52
|
+
|
|
52
53
|
this.cleanupOptionalContent();
|
|
53
54
|
|
|
54
55
|
this.emitEvent('view-mount', view.viewName);
|
|
55
56
|
return view;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
static cleanupOptionalContent() {
|
|
59
|
+
public static cleanupOptionalContent(): void {
|
|
59
60
|
const isEmpty = (el: HTMLElement): boolean => {
|
|
60
61
|
const attrName = el.dataset.optional?.trim();
|
|
61
62
|
|
|
@@ -85,7 +86,7 @@ export class ViewRenderManager {
|
|
|
85
86
|
});
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
static emitEvent(event: EventKey, viewName: string): void {
|
|
89
|
+
private static emitEvent(event: EventKey, viewName: string): void {
|
|
89
90
|
AppEventBus.emit(event, viewName);
|
|
90
91
|
}
|
|
91
92
|
{{#if transition}}
|
|
@@ -26,6 +26,51 @@ class Router {
|
|
|
26
26
|
this.navigate(NavigationManager.getCurrentPath());
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
public async reload(): Promise<void> {
|
|
30
|
+
const currentRoute = NavigationManager.getCurrentPath();
|
|
31
|
+
await this.navigate(currentRoute, false);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async navigate(path: string | '', pushState: boolean = true): Promise<void> {
|
|
35
|
+
const normalizedPath = NavigationManager.normalizePath(path);
|
|
36
|
+
const currentPath = NavigationManager.getCurrentPath();
|
|
37
|
+
|
|
38
|
+
// Try to match the route with parameters
|
|
39
|
+
const routeMatch = this.matchRoute(normalizedPath);
|
|
40
|
+
|
|
41
|
+
if (!routeMatch) {
|
|
42
|
+
await this.handleNotFound(normalizedPath, currentPath, pushState);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const guardResult = await this.handleGuards(
|
|
47
|
+
normalizedPath,
|
|
48
|
+
routeMatch.route,
|
|
49
|
+
routeMatch.params,
|
|
50
|
+
pushState
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (!guardResult) return;
|
|
54
|
+
|
|
55
|
+
this._currentView = await ViewRenderManager.transitionOutCurrentView(this._currentView);
|
|
56
|
+
this._currentParams = routeMatch.params;
|
|
57
|
+
this._currentView = ViewRenderManager.renderNewView(
|
|
58
|
+
guardResult.viewConstructor!,
|
|
59
|
+
routeMatch.params
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
NavigationManager.updateMetaContent(this._currentView);
|
|
63
|
+
NavigationManager.updateHistory(normalizedPath, currentPath, pushState);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public getCurrentParams(): Record<string, string> {
|
|
67
|
+
return { ...this._currentParams };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public getParam(key: string): string | undefined {
|
|
71
|
+
return this._currentParams[key];
|
|
72
|
+
}
|
|
73
|
+
|
|
29
74
|
private initializeEventListeners(): void {
|
|
30
75
|
window.addEventListener('popstate', () => this.handlePopState());
|
|
31
76
|
AppEventBus.subscribe('navigate', (path: string) => this.navigate(path));
|
|
@@ -95,51 +140,6 @@ class Router {
|
|
|
95
140
|
|
|
96
141
|
return guardResult;
|
|
97
142
|
}
|
|
98
|
-
|
|
99
|
-
public async reload(): Promise<void> {
|
|
100
|
-
const currentRoute = NavigationManager.getCurrentPath();
|
|
101
|
-
await this.navigate(currentRoute, false);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
public async navigate(path: string | '', pushState: boolean = true): Promise<void> {
|
|
105
|
-
const normalizedPath = NavigationManager.normalizePath(path);
|
|
106
|
-
const currentPath = NavigationManager.getCurrentPath();
|
|
107
|
-
|
|
108
|
-
// Try to match the route with parameters
|
|
109
|
-
const routeMatch = this.matchRoute(normalizedPath);
|
|
110
|
-
|
|
111
|
-
if (!routeMatch) {
|
|
112
|
-
await this.handleNotFound(normalizedPath, currentPath, pushState);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const guardResult = await this.handleGuards(
|
|
117
|
-
normalizedPath,
|
|
118
|
-
routeMatch.route,
|
|
119
|
-
routeMatch.params,
|
|
120
|
-
pushState
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
if (!guardResult) return;
|
|
124
|
-
|
|
125
|
-
this._currentView = await ViewRenderManager.transitionOutCurrentView(this._currentView);
|
|
126
|
-
this._currentParams = routeMatch.params;
|
|
127
|
-
this._currentView = ViewRenderManager.renderNewView(
|
|
128
|
-
guardResult.viewConstructor!,
|
|
129
|
-
routeMatch.params
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
NavigationManager.updateMetaContent(this._currentView);
|
|
133
|
-
NavigationManager.updateHistory(normalizedPath, currentPath, pushState);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
public getCurrentParams(): Record<string, string> {
|
|
137
|
-
return { ...this._currentParams };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
public getParam(key: string): string | undefined {
|
|
141
|
-
return this._currentParams[key];
|
|
142
|
-
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
export const AppRouter = (routes: Routes) => new Router(routes);
|
|
@@ -10,10 +10,6 @@ export class Store extends Service<Store> {
|
|
|
10
10
|
this.registerCleanup(this.clear);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
protected onDestroy(): void {
|
|
14
|
-
this.clear();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
13
|
public set<T = any>(key: string, value: T): void {
|
|
18
14
|
this._state[key] = value;
|
|
19
15
|
AppEventBus.emit(`store:${key}`, value);
|
|
@@ -27,12 +23,16 @@ export class Store extends Service<Store> {
|
|
|
27
23
|
AppEventBus.subscribe(`store:${key}`, callback);
|
|
28
24
|
}
|
|
29
25
|
|
|
26
|
+
public unsubscribe<T = any>(key: string, callback: (value: T) => void): void {
|
|
27
|
+
AppEventBus.off(`store:${key}`, callback);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
30
|
public clear(): void {
|
|
31
31
|
this._state = {};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
protected onDestroy(): void {
|
|
35
|
+
this.clear();
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -3,9 +3,9 @@ import { AttributesHelper, IAttributesConfig } from "../helpers/attributes.helpe
|
|
|
3
3
|
/**
|
|
4
4
|
* ```typescript
|
|
5
5
|
interface IAttributesConfig {
|
|
6
|
-
{{#if i18n}}
|
|
6
|
+
{{#if i18n}}
|
|
7
7
|
i18nKey?: string;
|
|
8
|
-
{{/if}}
|
|
8
|
+
{{/if}}
|
|
9
9
|
textContent?: string;
|
|
10
10
|
className?: string;
|
|
11
11
|
style?: string;
|
|
@@ -42,8 +42,8 @@ export class AnchorManager {
|
|
|
42
42
|
target.appendChild(container);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
private createAnchorContainer() {
|
|
46
|
-
const container = document.createElement('
|
|
45
|
+
private createAnchorContainer(): HTMLSpanElement {
|
|
46
|
+
const container = document.createElement('span');
|
|
47
47
|
const anchor = this.createAnchor();
|
|
48
48
|
|
|
49
49
|
container.appendChild(anchor);
|
|
@@ -56,16 +56,14 @@ export class AnchorManager {
|
|
|
56
56
|
|
|
57
57
|
this.setAnchorRef(anchor);
|
|
58
58
|
|
|
59
|
-
AttributesHelper.
|
|
60
|
-
AttributesHelper.setStyle(anchor, this.config);
|
|
61
|
-
AttributesHelper.setPipes(anchor, this.config);
|
|
59
|
+
AttributesHelper.setAttributes(anchor, this.config);
|
|
62
60
|
|
|
63
61
|
this.handleClick(anchor);
|
|
64
62
|
|
|
65
63
|
return anchor;
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
private setAnchorRef(anchor: HTMLAnchorElement) {
|
|
66
|
+
private setAnchorRef(anchor: HTMLAnchorElement): void {
|
|
69
67
|
anchor.setAttribute('href', this.config.href);
|
|
70
68
|
if (this.config.target) {
|
|
71
69
|
anchor.setAttribute('target', this.config.target);
|
|
@@ -69,19 +69,24 @@ export class ButtonManager {
|
|
|
69
69
|
return container;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
public appendTo(target: HTMLElement): void {
|
|
73
|
+
const container = this.createButtonContainer();
|
|
74
|
+
if (container) {
|
|
75
|
+
target.appendChild(container);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
private createButton(config: BaseButton, index: number): HTMLElement {
|
|
73
80
|
const button = document.createElement('button');
|
|
74
81
|
|
|
75
|
-
AttributesHelper.
|
|
76
|
-
AttributesHelper.setStyle(button, config);
|
|
77
|
-
AttributesHelper.setPipes(button, config);
|
|
82
|
+
AttributesHelper.setAttributes(button, config);
|
|
78
83
|
|
|
79
84
|
this.setDataEvent(button, index);
|
|
80
85
|
|
|
81
86
|
return button;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
private setDataEvent(button: HTMLButtonElement, index: number) {
|
|
89
|
+
private setDataEvent(button: HTMLButtonElement, index: number): void {
|
|
85
90
|
button.setAttribute('data-event', `click:onButtonClick_${index}`);
|
|
86
91
|
}
|
|
87
92
|
|
|
@@ -95,13 +100,6 @@ export class ButtonManager {
|
|
|
95
100
|
};
|
|
96
101
|
}
|
|
97
102
|
|
|
98
|
-
public appendTo(target: HTMLElement): void {
|
|
99
|
-
const container = this.createButtonContainer();
|
|
100
|
-
if (container) {
|
|
101
|
-
target.appendChild(container);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
103
|
/** Note : doesn't track container
|
|
106
104
|
*/
|
|
107
105
|
public updateButtons(newButtons: BaseButton[]): void {
|
|
@@ -10,21 +10,27 @@ export interface IAttributesConfig {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export class AttributesHelper {
|
|
13
|
-
static
|
|
13
|
+
public static setAttributes(element: HTMLElement, config: IAttributesConfig): void {
|
|
14
|
+
this.setContent(element, config);
|
|
15
|
+
this.setPipes(element, config);
|
|
16
|
+
this.setStyle(element, config);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private static setContent(element: HTMLElement, config: IAttributesConfig): void {
|
|
14
20
|
element.textContent = config.textContent || '';
|
|
15
|
-
|
|
21
|
+
{{#if i18n}}
|
|
16
22
|
if (config.i18nKey) {
|
|
17
23
|
element.setAttribute('data-i18n', config.i18nKey);
|
|
18
24
|
}
|
|
19
|
-
|
|
25
|
+
{{/if}}
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
static setPipes(element: HTMLElement, config: IAttributesConfig) {
|
|
28
|
+
private static setPipes(element: HTMLElement, config: IAttributesConfig): void {
|
|
23
29
|
if (!config.pipes) return;
|
|
24
30
|
element.setAttribute('data-pipe', config.pipes);
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
static setStyle(element: HTMLElement, config: IAttributesConfig) {
|
|
33
|
+
private static setStyle(element: HTMLElement, config: IAttributesConfig): void {
|
|
28
34
|
const classes = [
|
|
29
35
|
config.className
|
|
30
36
|
].filter(Boolean).join(' ');
|