@asor-studio/asor-cli 1.0.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.
Files changed (33) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +293 -0
  3. package/cli-command/generate-atom.js +2 -0
  4. package/cli-command/generate-molecule.js +2 -0
  5. package/cli-command/generate-organism.js +2 -0
  6. package/cli-command/generate-page.js +2 -0
  7. package/cli-command/setup-asor-core.js +2 -0
  8. package/cli.js +2 -0
  9. package/gen-template/generate-atom/atom.html.template +26 -0
  10. package/gen-template/generate-atom/atom.ts.template +69 -0
  11. package/gen-template/generate-molecule/molecule.html.template +41 -0
  12. package/gen-template/generate-molecule/molecule.ts.template +69 -0
  13. package/gen-template/generate-organism/organism.html.template +41 -0
  14. package/gen-template/generate-organism/organism.ts.template +69 -0
  15. package/gen-template/generate-page/page.component.html.template +63 -0
  16. package/gen-template/generate-page/page.component.ts.template +56 -0
  17. package/gen-template/setup-asor-core/src/app/app.config.ts.template +46 -0
  18. package/gen-template/setup-asor-core/src/app/app.routes.ts.template +40 -0
  19. package/gen-template/setup-asor-core/src/app/config/app-asor.config.ts.template +64 -0
  20. package/gen-template/setup-asor-core/src/app/config/app-cache.config.ts.template +26 -0
  21. package/gen-template/setup-asor-core/src/app/config/app-state.config.ts.template +26 -0
  22. package/gen-template/setup-asor-core/src/app/config/app.config.ts.template +62 -0
  23. package/gen-template/setup-asor-core/src/app/config/interfaces/app-state.interfaces.ts.template +5 -0
  24. package/gen-template/setup-asor-core/src/app/molecules/molecule-sample-widget/sample-widget.molecule.html.template +84 -0
  25. package/gen-template/setup-asor-core/src/app/molecules/molecule-sample-widget/sample-widget.molecule.ts.template +56 -0
  26. package/gen-template/setup-asor-core/src/app/pages/page-home/asor-logo.ts.template +1 -0
  27. package/gen-template/setup-asor-core/src/app/pages/page-home/home.component.html.template +521 -0
  28. package/gen-template/setup-asor-core/src/app/pages/page-home/home.component.ts.template +99 -0
  29. package/gen-template/setup-asor-core/src/assets/i18n/app/molecule-sample-widget/en.json.template +7 -0
  30. package/gen-template/setup-asor-core/src/assets/i18n/app/molecule-sample-widget/it.json.template +7 -0
  31. package/gen-template/setup-asor-core/src/assets/i18n/app/page-home/en.json.template +19 -0
  32. package/gen-template/setup-asor-core/src/assets/i18n/app/page-home/it.json.template +19 -0
  33. package/package.json +31 -0
@@ -0,0 +1,41 @@
1
+ <style>
2
+ /*
3
+ * IMPORTANT:
4
+ * This CSS block is only a minimal base generated by the template.
5
+ * In a real project, styles should be moved to a dedicated file
6
+ * based on the standard chosen by the team: CSS, SCSS, Sass, Less,
7
+ * or handled through global styles, utility classes, a design system, or inline styles.
8
+ */
9
+ .__KEBAB__-organism {
10
+ display: grid;
11
+ gap: 0.65rem;
12
+ width: min(100%, 40rem);
13
+ padding: 1.15rem;
14
+ border: 1px solid rgba(226, 232, 240, 0.95);
15
+ border-radius: 1rem;
16
+ background: #ffffff;
17
+ color: #0f172a;
18
+ }
19
+
20
+ .__KEBAB__-title,
21
+ .__KEBAB__-description {
22
+ margin: 0;
23
+ }
24
+
25
+ .__KEBAB__-title {
26
+ font-size: 1.15rem;
27
+ line-height: 1.2;
28
+ }
29
+
30
+ .__KEBAB__-description {
31
+ max-width: 46ch;
32
+ color: #475569;
33
+ font-size: 0.94rem;
34
+ line-height: 1.5;
35
+ }
36
+ </style>
37
+
38
+ <section class="__KEBAB__-organism">
39
+ <h2 class="__KEBAB__-title">{{ '__ROUTE_KEY__.TITLE' | translate }}</h2>
40
+ <p class="__KEBAB__-description">{{ '__ROUTE_KEY__.WELCOME' | translate }}</p>
41
+ </section>
@@ -0,0 +1,69 @@
1
+ import { Component } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { __BASE_CLASS_NAME__, TranslatePipe } from '@asor-studio/asor-core';
4
+
5
+ /**
6
+ * Organism Component: __PASCAL__Organism
7
+ *
8
+ * ATOMIC DESIGN ROLE:
9
+ * An Organism is a complex component composed of molecules and/or atoms, forming a distinct section of the interface (e.g., a header, a sidebar).
10
+ * It represents the final level of reusable UI blocks before the Page level.
11
+ *
12
+ * I18N / TRANSLATIONS:
13
+ * Translations for this organism can be found (or added) in:
14
+ * `assets/i18n/<prefix>/organism-__KEBAB__/<lang>.json`
15
+ *
16
+ * ROUTE CONFIGURATION & PAGE BELONGING:
17
+ * To see which page(s) this organism belongs to, check `src/app/app.routes.ts`.
18
+ * It is injected into a page's metadata via the `data.Organisms` array.
19
+ * Look for the following placeholder in the routes file:
20
+ * `// [ASOR-INJECT:ORGANISM-DEFS(<page-name>)]`
21
+ */
22
+ @Component({
23
+ selector: 'app-__KEBAB__',
24
+ standalone: true,
25
+ imports: [CommonModule, TranslatePipe],
26
+ templateUrl: './__KEBAB__.organism.html',
27
+ })
28
+ export class __PASCAL__Organism __BASE_CLASS_EXTENDS__ {
29
+ /**
30
+ * Unique identifier for the component class.
31
+ * CRITICAL for asor-core: used by the framework to identify this component
32
+ * within route metadata and correctly resolve its configuration/i18n paths.
33
+ */
34
+ public static override readonly className: string = '__PASCAL__Organism';
35
+
36
+ constructor() {
37
+ super();
38
+ }
39
+
40
+ /**
41
+ * Lifecycle hook called when the component view is about to enter at a generic level.
42
+ * Used for system-wide initialization logic common to all asor components.
43
+ */
44
+ override baseCompViewEnter(): void { }
45
+
46
+ /**
47
+ * Lifecycle hook called when the component view is about to leave at a generic level.
48
+ * Used for system-wide cleanup logic common to all asor components.
49
+ * Calls super.baseCompViewLeave() to ensure proper cleanup of base class / storage resources.
50
+ */
51
+ override baseCompViewLeave(): void {
52
+ super.baseCompViewLeave();
53
+ }
54
+
55
+ /**
56
+ * Specialized lifecycle hook for Organism components called when the view is about to enter.
57
+ * Use this for organism-specific initialization logic (e.g. orchestration of children datasets).
58
+ */
59
+ override baseOrganismViewWillEnter(): void { }
60
+
61
+ /**
62
+ * Specialized lifecycle hook for Organism components called when the view is about to leave.
63
+ * Use this for organism-specific cleanup logic.
64
+ * Calls super.baseOrganismViewWillLeave() to ensure proper cleanup of base class / storage resources.
65
+ */
66
+ override baseOrganismViewWillLeave(): void {
67
+ super.baseOrganismViewWillLeave();
68
+ }
69
+ }
@@ -0,0 +1,63 @@
1
+ <style>
2
+ /*
3
+ * IMPORTANT:
4
+ * This CSS block is only a minimal base generated by the template.
5
+ * In a real project, styles should be moved to a dedicated file
6
+ * based on the standard chosen by the team: CSS, SCSS, Sass, Less,
7
+ * or handled through global styles, utility classes, a design system, or inline styles.
8
+ */
9
+ .__KEBAB__-page {
10
+ display: grid;
11
+ gap: 1rem;
12
+ min-height: 100%;
13
+ padding: 1.25rem;
14
+ justify-items: center;
15
+ background: #f8fafc;
16
+ color: #111827;
17
+ }
18
+
19
+ .__KEBAB__-surface {
20
+ display: grid;
21
+ gap: 0.65rem;
22
+ width: min(100%, 44rem);
23
+ padding: 1.25rem;
24
+ border: 1px solid rgba(226, 232, 240, 0.9);
25
+ border-radius: 1rem;
26
+ background: #ffffff;
27
+ color: inherit;
28
+ }
29
+
30
+ .__KEBAB__-eyebrow {
31
+ margin: 0;
32
+ font-size: 0.72rem;
33
+ font-weight: 700;
34
+ letter-spacing: 0.08em;
35
+ color: #8b5cf6;
36
+ text-transform: uppercase;
37
+ }
38
+
39
+ .__KEBAB__-title,
40
+ .__KEBAB__-description {
41
+ margin: 0;
42
+ }
43
+
44
+ .__KEBAB__-title {
45
+ font-size: 1.5rem;
46
+ line-height: 1.2;
47
+ }
48
+
49
+ .__KEBAB__-description {
50
+ max-width: 52ch;
51
+ color: #4b5563;
52
+ font-size: 0.94rem;
53
+ line-height: 1.5;
54
+ }
55
+ </style>
56
+
57
+ <main class="__KEBAB__-page">
58
+ <section class="__KEBAB__-surface">
59
+ <p class="__KEBAB__-eyebrow">Page</p>
60
+ <h1 class="__KEBAB__-title">{{ '__ROUTE_KEY__.TITLE' | translate }}</h1>
61
+ <p class="__KEBAB__-description">{{ '__ROUTE_KEY__.WELCOME' | translate }}</p>
62
+ </section>
63
+ </main>
@@ -0,0 +1,56 @@
1
+ import { Component } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { TranslatePipe } from '@asor-studio/asor-core';
4
+ import { __BASE_CLASS_NAME__, ConsoleLogsUtility } from '@asor-studio/asor-core';
5
+
6
+ /**
7
+ * Page Component: Page__PASCAL__Component
8
+ *
9
+ * ATOMIC DESIGN ROLE:
10
+ * A Page is the highest level of the atomic design hierarchy. It orchestrates atoms, molecules, and organisms
11
+ * to form a complete functional view or screen of the application.
12
+ *
13
+ * I18N / TRANSLATIONS:
14
+ * Translations for this page can be found (or added) in:
15
+ * `assets/i18n/<prefix>/page-__KEBAB__/<lang>.json`
16
+ *
17
+ * ROUTE CONFIGURATION:
18
+ * This page is directly mapped to a route in `src/app/app.routes.ts`.
19
+ * Configuration for its components (Atoms, Molecules, Organisms) is managed via the `data` property of the route.
20
+ */
21
+ @Component({
22
+ selector: 'app-__KEBAB__-page',
23
+ standalone: true,
24
+ imports: [CommonModule, TranslatePipe],
25
+ templateUrl: './__KEBAB__.component.html',
26
+ })
27
+ export class Page__PASCAL__Component __BASE_CLASS_EXTENDS__ {
28
+ /**
29
+ * Unique identifier for the component class.
30
+ * CRITICAL for asor-core: used by the framework to identify this component
31
+ * within route metadata and correctly resolve its configuration/i18n paths.
32
+ */
33
+ public static override readonly className: string = 'Page__PASCAL__Component';
34
+
35
+ constructor() {
36
+ super();
37
+ }
38
+
39
+ /**
40
+ * Lifecycle hook called when the component view is about to enter at a generic level.
41
+ * Used for system-wide initialization logic common to all asor components.
42
+ */
43
+ override baseCompViewEnter(): void {
44
+ super.baseCompViewEnter();
45
+ ConsoleLogsUtility.info(this, '__PASCAL__ page loaded!');
46
+ }
47
+
48
+ /**
49
+ * Lifecycle hook called when the component view is about to leave at a generic level.
50
+ * Used for system-wide cleanup logic common to all asor components.
51
+ * Calls super.baseCompViewLeave() to ensure proper cleanup of base class / storage resources.
52
+ */
53
+ override baseCompViewLeave(): void {
54
+ super.baseCompViewLeave();
55
+ }
56
+ }
@@ -0,0 +1,46 @@
1
+ import { ApplicationConfig, APP_INITIALIZER, provideZonelessChangeDetection } from '@angular/core';
2
+ import { provideRouter, withComponentInputBinding } from '@angular/router';
3
+ import { provideHttpClient, withInterceptorsFromDi, HTTP_INTERCEPTORS } from '@angular/common/http';
4
+ import {
5
+ StateService,
6
+ CacheInterceptor,
7
+ ErrorInterceptor,
8
+ MockHttpInterceptor,
9
+ MockOrchestratorService,
10
+ } from '@asor-studio/asor-core';
11
+ import { routes } from './app.routes';
12
+ import { initializeAsorCoreApp } from './config/app-asor.config';
13
+ import { APP_BASE_HREF } from '@angular/common';
14
+
15
+ export const appConfig: ApplicationConfig = {
16
+ providers: [
17
+ {
18
+ provide: APP_INITIALIZER,
19
+ useFactory: initializeAsorCoreApp,
20
+ deps: [StateService, MockOrchestratorService],
21
+ multi: true,
22
+ },
23
+ provideZonelessChangeDetection(),
24
+ provideRouter(routes, withComponentInputBinding()),
25
+ provideHttpClient(withInterceptorsFromDi()),
26
+ {
27
+ provide: HTTP_INTERCEPTORS,
28
+ useClass: MockHttpInterceptor,
29
+ multi: true,
30
+ },
31
+ {
32
+ provide: HTTP_INTERCEPTORS,
33
+ useClass: CacheInterceptor,
34
+ multi: true,
35
+ },
36
+ {
37
+ provide: HTTP_INTERCEPTORS,
38
+ useClass: ErrorInterceptor,
39
+ multi: true,
40
+ },
41
+ {
42
+ provide: APP_BASE_HREF,
43
+ useValue: '/', // Customize for your application
44
+ },
45
+ ],
46
+ };
@@ -0,0 +1,40 @@
1
+ import { Routes } from '@angular/router';
2
+ import { AuthGuard, IAsorRoute } from '@asor-studio/asor-core';
3
+ import { AppConfig } from './config/app.config';
4
+ import { PageHomeComponent } from './pages/page-home/home.component';
5
+ import { AppStateConnectionSystem } from './config/app-state.config';
6
+ import { SampleWidgetMolecule } from './molecules/molecule-sample-widget/sample-widget.molecule';
7
+ // [ASOR-INJECT:ROUTE-IMPORTS]
8
+
9
+ export const routes: Routes = [
10
+ {
11
+ path: '',
12
+ redirectTo: AppConfig.Route.HOME,
13
+ pathMatch: 'full',
14
+ },
15
+ {
16
+ path: AppConfig.Route.HOME,
17
+ component: PageHomeComponent,
18
+ data: {
19
+ I18nPath: [AppConfig.TranslationUrl.HOME],
20
+ ConnectDataSet: AppStateConnectionSystem,
21
+ Components: [
22
+ // [ASOR-INJECT:COMPONENT-DEFS(home)]
23
+ ],
24
+ Molecules: [
25
+ {
26
+ Molecule: SampleWidgetMolecule,
27
+ I18nPath: [AppConfig.TranslationUrl.SAMPLE_WIDGET],
28
+ },
29
+ // [ASOR-INJECT:MOLECULE-DEFS(home)]
30
+ ],
31
+ Atoms: [
32
+ // [ASOR-INJECT:ATOM-DEFS(home)]
33
+ ],
34
+ Organisms: [
35
+ // [ASOR-INJECT:ORGANISM-DEFS(home)]
36
+ ],
37
+ } as IAsorRoute,
38
+ },
39
+ // [ASOR-INJECT:ROUTE-DEFS]
40
+ ];
@@ -0,0 +1,64 @@
1
+ import { inject } from '@angular/core';
2
+ import {
3
+ StateService,
4
+ CacheInterceptor,
5
+ ErrorInterceptor,
6
+ ConsoleLogsConfig,
7
+ AuthGuard,
8
+ NotifyErrorService,
9
+ HttpRequestHandler,
10
+ LogLevel,
11
+ MockOrchestratorService,
12
+ AsorStorage,
13
+ } from '@asor-studio/asor-core';
14
+ import { AppConfig } from './app.config';
15
+ import { AppCacheConfig } from './app-cache.config';
16
+ import { AppStateCreateSystemDataSet } from './app-state.config';
17
+ import { PageHomeComponent } from '../pages/page-home/home.component';
18
+ // [ASOR-INJECT:LOG-IMPORTS]
19
+
20
+ export function initializeAsorCoreApp(
21
+ stateService: StateService,
22
+ mockOrchestratorService: MockOrchestratorService
23
+ ) {
24
+ return () => {
25
+ const levels: LogLevel[] = ['INFO', 'WARNING', 'ERROR'];
26
+
27
+ ConsoleLogsConfig.silent = false;
28
+ ConsoleLogsConfig.defaultLevels = [];
29
+
30
+ ConsoleLogsConfig.setClassLevels(StateService.className, levels);
31
+ ConsoleLogsConfig.setClassLevels(AuthGuard.className, levels);
32
+ ConsoleLogsConfig.setClassLevels(CacheInterceptor.className, levels);
33
+ ConsoleLogsConfig.setClassLevels(ErrorInterceptor.className, levels);
34
+ ConsoleLogsConfig.setClassLevels(NotifyErrorService.className, levels);
35
+ ConsoleLogsConfig.setClassLevels(HttpRequestHandler.className, levels);
36
+
37
+ // Init Configs
38
+ ConsoleLogsConfig.setClassLevels(AppConfig.className, levels);
39
+ ConsoleLogsConfig.setClassLevels(AppCacheConfig.className, levels);
40
+ AppConfig.init();
41
+ AppCacheConfig.init();
42
+
43
+ // Register Pages (To enable BaseStorageComponent logs)
44
+ ConsoleLogsConfig.setClassLevels(PageHomeComponent.className, levels);
45
+ // [ASOR-INJECT:LOG-REGISTRATIONS]
46
+
47
+ stateService.initialize({
48
+ globalStateName: '<placeholder-globalStateName>',
49
+ encryptionType: AsorStorage.StateConst.EncryptType.AES,
50
+ nameType: AsorStorage.StateConst.Generate.AUTO,
51
+ keyType: AsorStorage.StateConst.Generate.AUTO,
52
+ asyncEnabled: true,
53
+ });
54
+
55
+ // Register the Global State "AppSystemStatus" in memory
56
+ stateService.createDataSet(
57
+ AppStateCreateSystemDataSet.name,
58
+ AppStateCreateSystemDataSet.data,
59
+ AppStateCreateSystemDataSet.option!
60
+ );
61
+
62
+ return true;
63
+ };
64
+ }
@@ -0,0 +1,26 @@
1
+ import { ConfigCache, AsorGlobalEnum, ConsoleLogsUtility } from '@asor-studio/asor-core';
2
+ import { AppConfig } from './app.config';
3
+
4
+ export class AppCacheConfig extends ConfigCache {
5
+ public static readonly className: string = 'AppCacheConfig';
6
+ public static override globalStateName = '<placeholder-globalStateName>';
7
+
8
+ protected static override _pathsExtensions = [
9
+ {
10
+ pathValue: AppConfig.Cache.APP_BASE_I18N,
11
+ persistenceType: AsorGlobalEnum.CacheType.VOLATILE,
12
+ encriptType: AsorGlobalEnum.CacheEncriptType.NONE,
13
+ reqPayloadCache: false,
14
+ resctrictRoute: AppConfig.Route.NONE,
15
+ clearOn: [],
16
+ },
17
+ ];
18
+
19
+ static init() {
20
+ ConsoleLogsUtility.info(AppCacheConfig, 'AppCacheConfig initialized');
21
+ }
22
+
23
+ static {
24
+ ConfigCache.SetConfiguration(AppCacheConfig);
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ import { AsorStorage, ICreateDataSet, IConnectDataSet } from '@asor-studio/asor-core';
2
+ import { IAppSystemStatus } from './interfaces/app-state.interfaces';
3
+
4
+ // Dataset Definition (Global data sitting next to the StateService)
5
+ export const AppStateCreateSystemDataSet: ICreateDataSet = {
6
+ name: 'app-system-status',
7
+ data: {
8
+ isMenuOpen: false,
9
+ appVersion: '1.0.0',
10
+ currentUser: null,
11
+ } as IAppSystemStatus,
12
+ option: {
13
+ storeType: AsorStorage.StateConst.StoreType.VOLATILE,
14
+ encrypt: false,
15
+ },
16
+ };
17
+
18
+ // Connection Definition that components will use to subscribe to specific Proxy keys
19
+ export const AppStateConnectionSystem: IConnectDataSet = {
20
+ name: 'AppSystemConnection',
21
+ selectors: {
22
+ isMenuOpen: 'app-system-status.isMenuOpen',
23
+ appVersion: 'app-system-status.appVersion',
24
+ currentUser: 'app-system-status.currentUser',
25
+ },
26
+ };
@@ -0,0 +1,62 @@
1
+ import { ConfigConst, ConsoleLogsUtility } from '@asor-studio/asor-core';
2
+
3
+ export class AppConfig extends ConfigConst {
4
+ public static readonly className: string = 'AppConfig';
5
+
6
+ static override BaseApi = '/api/v1';
7
+ static override SiteBaseUrl = '/';
8
+ static override SiteAssetsUrl = '/assets';
9
+
10
+ protected static override _routeExtensions = {
11
+ HOME: 'home',
12
+ // [ASOR-INJECT:ROUTE-EXTENSIONS]
13
+ };
14
+
15
+ protected static override _translationUrlExtensions = {
16
+ DefaultLanguage: 'en',
17
+ HOME: '/page-home',
18
+ SAMPLE_WIDGET: '/molecule-sample-widget',
19
+ // [ASOR-INJECT:TRANSLATION-EXTENSIONS]
20
+ };
21
+
22
+ protected static override _authCheckExtensions = {
23
+ HOME: 'home',
24
+ };
25
+
26
+ protected static override _urlExtensions = {
27
+ HOME: '/home',
28
+ };
29
+
30
+ protected static override _cacheExtensions = {
31
+ APP_BASE_I18N: 'i18n/app',
32
+ };
33
+
34
+ protected static _assetsExtensions = {};
35
+
36
+ static override get Route() {
37
+ return { ...super.Route, ...this._routeExtensions };
38
+ }
39
+ static override get TranslationUrl() {
40
+ return { ...super.TranslationUrl, ...this._translationUrlExtensions };
41
+ }
42
+ static override get AuthCheck() {
43
+ return { ...super.AuthCheck, ...this._authCheckExtensions };
44
+ }
45
+ static override get Url() {
46
+ return { ...super.Url, ...this._urlExtensions };
47
+ }
48
+ static override get Cache() {
49
+ return { ...super.Cache, ...this._cacheExtensions };
50
+ }
51
+ static get Assets() {
52
+ return { ...this._assetsExtensions };
53
+ }
54
+
55
+ static init() {
56
+ ConsoleLogsUtility.info(AppConfig, 'AppConfig initialized');
57
+ }
58
+
59
+ static {
60
+ ConfigConst.SetConfiguration(AppConfig);
61
+ }
62
+ }
@@ -0,0 +1,5 @@
1
+ export interface IAppSystemStatus {
2
+ isMenuOpen: boolean;
3
+ appVersion: string;
4
+ currentUser: string | null;
5
+ }
@@ -0,0 +1,84 @@
1
+ <style>
2
+ /*
3
+ * IMPORTANT:
4
+ * This CSS block is only a minimal base generated by the template.
5
+ * In a real project, styles should be moved to a dedicated file
6
+ * based on the standard chosen by the team: CSS, SCSS, Sass, Less,
7
+ * or handled through global styles, utility classes, a design system, or inline styles.
8
+ */
9
+ .sample-widget {
10
+ position: relative;
11
+ display: grid;
12
+ gap: 0.85rem;
13
+ padding: 0.95rem 1rem;
14
+ border: 1px solid rgba(226, 232, 240, 0.95);
15
+ border-radius: 1.25rem;
16
+ background:
17
+ radial-gradient(circle at top right, rgba(219, 39, 119, 0.08), transparent 40%),
18
+ linear-gradient(180deg, #ffffff, #fbfbff);
19
+ color: #0f172a;
20
+ box-shadow: 0 0.75rem 1.5rem rgba(15, 23, 42, 0.05);
21
+ }
22
+
23
+ .sample-widget--highlighted {
24
+ animation: sample-widget-pulse 780ms ease;
25
+ }
26
+
27
+ .sample-widget__title,
28
+ .sample-widget__text,
29
+ .sample-widget__status {
30
+ margin: 0;
31
+ }
32
+
33
+ .sample-widget__title {
34
+ font-size: 0.98rem;
35
+ letter-spacing: 0.04em;
36
+ color: #7c3aed;
37
+ }
38
+
39
+ .sample-widget__text,
40
+ .sample-widget__status {
41
+ color: #475569;
42
+ font-size: 0.9rem;
43
+ }
44
+
45
+ .sample-widget__status-value {
46
+ font-weight: 600;
47
+ color: #64748b;
48
+ }
49
+
50
+ .sample-widget__status-value.active {
51
+ color: #db2777;
52
+ }
53
+
54
+ @keyframes sample-widget-pulse {
55
+ 0%,
56
+ 100% {
57
+ box-shadow: 0 0.75rem 1.5rem rgba(15, 23, 42, 0.05);
58
+ border-color: rgba(226, 232, 240, 0.95);
59
+ }
60
+
61
+ 35% {
62
+ box-shadow: 0 0 0 0.28rem rgba(168, 85, 247, 0.12), 0 1rem 2rem rgba(168, 85, 247, 0.18);
63
+ border-color: rgba(168, 85, 247, 0.45);
64
+ }
65
+
66
+ 60% {
67
+ box-shadow: 0 0 0 0.28rem rgba(236, 72, 153, 0.1), 0 1rem 2rem rgba(236, 72, 153, 0.16);
68
+ border-color: rgba(236, 72, 153, 0.4);
69
+ }
70
+ }
71
+ </style>
72
+
73
+ <section
74
+ class="sample-widget"
75
+ [class.sample-widget--highlighted]="isHighlighted">
76
+ <h3 class="sample-widget__title">{{ widgetTitle }}</h3>
77
+ <p class="sample-widget__text">{{ 'SAMPLE_WIDGET.RESPONSIVE' | translate }}</p>
78
+ <p class="sample-widget__status">
79
+ {{ 'SAMPLE_WIDGET.MENU_STATUS' | translate }}:
80
+ <span class="sample-widget__status-value" [class.active]="isMenuOpen">
81
+ {{ (isMenuOpen ? 'SAMPLE_WIDGET.OPEN' : 'SAMPLE_WIDGET.CLOSED') | translate }}
82
+ </span>
83
+ </p>
84
+ </section>
@@ -0,0 +1,56 @@
1
+ import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { BaseStorageMolecule, TranslatePipe } from '@asor-studio/asor-core';
4
+ import { IAppSystemStatus } from '../../config/interfaces/app-state.interfaces';
5
+
6
+ @Component({
7
+ selector: 'app-sample-widget',
8
+ standalone: true,
9
+ imports: [CommonModule, TranslatePipe],
10
+ templateUrl: './sample-widget.molecule.html',
11
+ })
12
+ export class SampleWidgetMolecule extends BaseStorageMolecule<IAppSystemStatus> implements OnInit, OnChanges {
13
+ public static override readonly className: string = 'SampleWidgetMolecule';
14
+
15
+ @Input() widgetTitle: string = 'Sample Widget';
16
+ @Input() highlightToken: number = 0;
17
+ public isHighlighted: boolean = false;
18
+ private highlightTimeoutId: ReturnType<typeof setTimeout> | null = null;
19
+
20
+ constructor() {
21
+ super();
22
+ }
23
+
24
+ override ngOnInit(): void {
25
+ super.ngOnInit?.();
26
+ // Widget initialization logic
27
+ }
28
+
29
+ ngOnChanges(changes: SimpleChanges): void {
30
+ if (changes['highlightToken'] && !changes['highlightToken'].firstChange) {
31
+ this.isHighlighted = false;
32
+
33
+ if (this.highlightTimeoutId) {
34
+ clearTimeout(this.highlightTimeoutId);
35
+ }
36
+
37
+ setTimeout(() => {
38
+ this.isHighlighted = true;
39
+
40
+ this.highlightTimeoutId = setTimeout(() => {
41
+ this.isHighlighted = false;
42
+ this.highlightTimeoutId = null;
43
+ }, 820);
44
+ }, 0);
45
+ }
46
+ }
47
+
48
+ override baseMoleculeViewWillEnter(): void {
49
+ super.baseMoleculeViewWillEnter?.();
50
+ // Widget post-view initialization, highly suggested for complex widgets
51
+ }
52
+
53
+ public get isMenuOpen(): boolean {
54
+ return this.props?.isMenuOpen || false;
55
+ }
56
+ }