@trismegisto/spine 3.3.0 → 3.3.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.
Files changed (92) hide show
  1. package/README.md +19 -11
  2. package/fesm2022/trismegisto-spine-audit.mjs +16 -12
  3. package/fesm2022/trismegisto-spine-audit.mjs.map +1 -1
  4. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-tfGcVR3i.mjs → trismegisto-spine-auto-service-auto-service-audit.component-D8HUpZtA.mjs} +4 -4
  5. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-tfGcVR3i.mjs.map → trismegisto-spine-auto-service-auto-service-audit.component-D8HUpZtA.mjs.map} +1 -1
  6. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-NawbfdLW.mjs +65 -0
  7. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-NawbfdLW.mjs.map +1 -0
  8. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-IATeedbr.mjs → trismegisto-spine-auto-service-auto-service-client.component-BAfeLyTB.mjs} +27 -19
  9. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-BAfeLyTB.mjs.map +1 -0
  10. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-BYjIQ9ID.mjs +635 -0
  11. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-BYjIQ9ID.mjs.map +1 -0
  12. package/fesm2022/{trismegisto-spine-auto-service-auto-service-login-attempts.component-CRvxfSWD.mjs → trismegisto-spine-auto-service-auto-service-login-attempts.component-DoyhkSYc.mjs} +4 -4
  13. package/fesm2022/{trismegisto-spine-auto-service-auto-service-login-attempts.component-CRvxfSWD.mjs.map → trismegisto-spine-auto-service-auto-service-login-attempts.component-DoyhkSYc.mjs.map} +1 -1
  14. package/fesm2022/trismegisto-spine-auto-service-auto-service-news-page.component-CBemH4hr.mjs +432 -0
  15. package/fesm2022/trismegisto-spine-auto-service-auto-service-news-page.component-CBemH4hr.mjs.map +1 -0
  16. package/fesm2022/trismegisto-spine-auto-service-auto-service-ratings-page.component-C_THGTkw.mjs +769 -0
  17. package/fesm2022/trismegisto-spine-auto-service-auto-service-ratings-page.component-C_THGTkw.mjs.map +1 -0
  18. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-DIeunpQ9.mjs +2123 -0
  19. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-DIeunpQ9.mjs.map +1 -0
  20. package/fesm2022/{trismegisto-spine-auto-service-auto-service.component-DGrs_Jvw.mjs → trismegisto-spine-auto-service-auto-service.component-QsIQ61w2.mjs} +31 -23
  21. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-QsIQ61w2.mjs.map +1 -0
  22. package/fesm2022/{trismegisto-spine-auto-service-client.usage-C_2zBCRN.mjs → trismegisto-spine-auto-service-client.usage-CJxn9Ysz.mjs} +5 -3
  23. package/fesm2022/trismegisto-spine-auto-service-client.usage-CJxn9Ysz.mjs.map +1 -0
  24. package/fesm2022/{trismegisto-spine-auto-service-groups.technical-9aPPulhy.mjs → trismegisto-spine-auto-service-groups.technical-_jQrg1Qh.mjs} +7 -5
  25. package/fesm2022/trismegisto-spine-auto-service-groups.technical-_jQrg1Qh.mjs.map +1 -0
  26. package/fesm2022/{trismegisto-spine-auto-service-groups.usage-CYh61LFL.mjs → trismegisto-spine-auto-service-groups.usage-OvWjNeeT.mjs} +7 -6
  27. package/fesm2022/{trismegisto-spine-auto-service-groups.usage-CYh61LFL.mjs.map → trismegisto-spine-auto-service-groups.usage-OvWjNeeT.mjs.map} +1 -1
  28. package/fesm2022/trismegisto-spine-auto-service-home.component-rQ3dIGIE.mjs +315 -0
  29. package/fesm2022/trismegisto-spine-auto-service-home.component-rQ3dIGIE.mjs.map +1 -0
  30. package/fesm2022/trismegisto-spine-auto-service-home.technical-Ce7oYRdg.mjs +46 -0
  31. package/fesm2022/trismegisto-spine-auto-service-home.technical-Ce7oYRdg.mjs.map +1 -0
  32. package/fesm2022/trismegisto-spine-auto-service-home.usage-D9R_ZMuU.mjs +48 -0
  33. package/fesm2022/trismegisto-spine-auto-service-home.usage-D9R_ZMuU.mjs.map +1 -0
  34. package/fesm2022/trismegisto-spine-auto-service-news.technical-DV9UpobG.mjs +38 -0
  35. package/fesm2022/trismegisto-spine-auto-service-news.technical-DV9UpobG.mjs.map +1 -0
  36. package/fesm2022/trismegisto-spine-auto-service-news.usage-CeuZnG0V.mjs +48 -0
  37. package/fesm2022/trismegisto-spine-auto-service-news.usage-CeuZnG0V.mjs.map +1 -0
  38. package/fesm2022/trismegisto-spine-auto-service-ratings.technical-QIVj6HBU.mjs +52 -0
  39. package/fesm2022/trismegisto-spine-auto-service-ratings.technical-QIVj6HBU.mjs.map +1 -0
  40. package/fesm2022/trismegisto-spine-auto-service-ratings.usage-BkteY8lz.mjs +45 -0
  41. package/fesm2022/trismegisto-spine-auto-service-ratings.usage-BkteY8lz.mjs.map +1 -0
  42. package/fesm2022/trismegisto-spine-auto-service-suggestions.technical-BtUK6sKD.mjs +69 -0
  43. package/fesm2022/trismegisto-spine-auto-service-suggestions.technical-BtUK6sKD.mjs.map +1 -0
  44. package/fesm2022/trismegisto-spine-auto-service-suggestions.usage-BwO9Hjsx.mjs +72 -0
  45. package/fesm2022/trismegisto-spine-auto-service-suggestions.usage-BwO9Hjsx.mjs.map +1 -0
  46. package/fesm2022/trismegisto-spine-auto-service.mjs +49 -19
  47. package/fesm2022/trismegisto-spine-auto-service.mjs.map +1 -1
  48. package/fesm2022/trismegisto-spine-graphql.mjs +11 -2
  49. package/fesm2022/trismegisto-spine-graphql.mjs.map +1 -1
  50. package/fesm2022/trismegisto-spine-layout.mjs +3113 -1221
  51. package/fesm2022/trismegisto-spine-layout.mjs.map +1 -1
  52. package/fesm2022/trismegisto-spine-modal.mjs +7 -5
  53. package/fesm2022/trismegisto-spine-modal.mjs.map +1 -1
  54. package/fesm2022/trismegisto-spine-routing.mjs +12 -20
  55. package/fesm2022/trismegisto-spine-routing.mjs.map +1 -1
  56. package/fesm2022/trismegisto-spine-shell.mjs +15 -39
  57. package/fesm2022/trismegisto-spine-shell.mjs.map +1 -1
  58. package/fesm2022/trismegisto-spine-side-panel.mjs +7 -5
  59. package/fesm2022/trismegisto-spine-side-panel.mjs.map +1 -1
  60. package/fesm2022/trismegisto-spine.mjs +1969 -279
  61. package/fesm2022/trismegisto-spine.mjs.map +1 -1
  62. package/package.json +6 -6
  63. package/types/trismegisto-spine-audit.d.ts +2 -12
  64. package/types/trismegisto-spine-audit.d.ts.map +1 -1
  65. package/types/trismegisto-spine-auto-service.d.ts +22 -5
  66. package/types/trismegisto-spine-auto-service.d.ts.map +1 -1
  67. package/types/trismegisto-spine-layout.d.ts +1083 -283
  68. package/types/trismegisto-spine-layout.d.ts.map +1 -1
  69. package/types/trismegisto-spine-modal.d.ts +1 -0
  70. package/types/trismegisto-spine-modal.d.ts.map +1 -1
  71. package/types/trismegisto-spine-routing.d.ts +3 -12
  72. package/types/trismegisto-spine-routing.d.ts.map +1 -1
  73. package/types/trismegisto-spine-shell.d.ts +2 -15
  74. package/types/trismegisto-spine-shell.d.ts.map +1 -1
  75. package/types/trismegisto-spine-side-panel.d.ts +1 -0
  76. package/types/trismegisto-spine-side-panel.d.ts.map +1 -1
  77. package/types/trismegisto-spine.d.ts +739 -165
  78. package/types/trismegisto-spine.d.ts.map +1 -1
  79. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-Cl7ahisu.mjs +0 -65
  80. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-Cl7ahisu.mjs.map +0 -1
  81. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-IATeedbr.mjs.map +0 -1
  82. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-D6L0gTmd.mjs +0 -605
  83. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-D6L0gTmd.mjs.map +0 -1
  84. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-DGrs_Jvw.mjs.map +0 -1
  85. package/fesm2022/trismegisto-spine-auto-service-client.usage-C_2zBCRN.mjs.map +0 -1
  86. package/fesm2022/trismegisto-spine-auto-service-groups.technical-9aPPulhy.mjs.map +0 -1
  87. package/fesm2022/trismegisto-spine-auto-service-home.component-5_aji1hf.mjs +0 -239
  88. package/fesm2022/trismegisto-spine-auto-service-home.component-5_aji1hf.mjs.map +0 -1
  89. package/fesm2022/trismegisto-spine-auto-service-home.technical-BrVhcgDO.mjs +0 -35
  90. package/fesm2022/trismegisto-spine-auto-service-home.technical-BrVhcgDO.mjs.map +0 -1
  91. package/fesm2022/trismegisto-spine-auto-service-home.usage-DWYGtngX.mjs +0 -43
  92. package/fesm2022/trismegisto-spine-auto-service-home.usage-DWYGtngX.mjs.map +0 -1
@@ -0,0 +1,315 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, inject, Injector, computed, signal, effect, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgTemplateOutlet } from '@angular/common';
4
+ import { toSignal } from '@angular/core/rxjs-interop';
5
+ import { Router } from '@angular/router';
6
+ import { ViewService, AuthorizationService } from '@orfeu/angular/core';
7
+ import { IconButtonComponent, IconComponent, MenuButtonComponent, StatePageComponent } from '@trismegisto/angular';
8
+ import { pluralMessage, formatMessage } from '@trismegisto/primitives';
9
+ import { spineRoutesOf, SpineQuickAccessService, SPINE_MESSAGES } from '@trismegisto/spine';
10
+ import { SpineDocsService, SpineSettingsService, SpineNewsService, ShortcutService, SpineInitialSetupService } from '@trismegisto/spine/layout';
11
+ import { map } from 'rxjs';
12
+ import { AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_NEWS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_SUGGESTIONS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_RATINGS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID } from './trismegisto-spine-auto-service.mjs';
13
+
14
+ // Above this many tiles a grid stops being scannable at a glance.
15
+ const MAX_TILES = 12;
16
+ const ADMIN_TEMPLATES = new Set([
17
+ AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID,
18
+ AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID,
19
+ AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID,
20
+ AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID,
21
+ AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID,
22
+ // Novidades, Sugestões e Avaliações também são de quem administra o client (migration 0036 do argos).
23
+ AUTO_SERVICE_NEWS_TEMPLATE_ROUTER_ID,
24
+ AUTO_SERVICE_SUGGESTIONS_TEMPLATE_ROUTER_ID,
25
+ AUTO_SERVICE_RATINGS_TEMPLATE_ROUTER_ID,
26
+ ].map((id) => id.toLowerCase()));
27
+ const HOME_TEMPLATE = AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID.toLowerCase();
28
+ function childrenOf(node) {
29
+ return (node.routes ?? []).filter((child) => child != null);
30
+ }
31
+ function labelOf$1(node) {
32
+ return (node.route_display_name ?? node.route_name ?? '').trim();
33
+ }
34
+ function identityOf(node) {
35
+ return node.route_redirect || `${node.route_type ?? 'node'}:${node.routed_id}`;
36
+ }
37
+ function templateOf(node) {
38
+ return (node.source_template_router_id ?? '').toLowerCase();
39
+ }
40
+ function isPage(node) {
41
+ return !!node.route_redirect && (node.route_type === 'page' || childrenOf(node).length === 0);
42
+ }
43
+ function isHome(node) {
44
+ return templateOf(node) === HOME_TEMPLATE || (isPage(node) && node.route_path === 'home');
45
+ }
46
+ function modules(tree) {
47
+ return tree.filter((node) => node != null && !isHome(node));
48
+ }
49
+ function systemAreas(tree) {
50
+ return tree.filter((node) => node != null && isHome(node));
51
+ }
52
+ function destinations(tree, trail = []) {
53
+ const found = [];
54
+ const walk = (nodes, path) => {
55
+ for (const node of nodes) {
56
+ if (node == null)
57
+ continue;
58
+ if (isPage(node)) {
59
+ found.push({ id: identityOf(node), label: labelOf$1(node), icon: node.route_icon ?? null, redirect: node.route_redirect, trail: path });
60
+ }
61
+ const kids = childrenOf(node);
62
+ if (kids.length)
63
+ walk(kids, [...path, labelOf$1(node)]);
64
+ }
65
+ };
66
+ walk(tree, trail);
67
+ return found;
68
+ }
69
+ function trailFor(tree, redirect) {
70
+ return destinations(tree).find((d) => d.redirect === redirect)?.trail ?? null;
71
+ }
72
+ function isPrivileged(node) {
73
+ return ADMIN_TEMPLATES.has(templateOf(node)) || childrenOf(node).some(isPrivileged);
74
+ }
75
+ function navigableChildren(node) {
76
+ return childrenOf(node).filter((child) => isPage(child) || destinations([child]).length > 0);
77
+ }
78
+ /** Replaces each candidate by its navigable children; `null` when nobody has any. */
79
+ function descend(level) {
80
+ let changed = false;
81
+ const next = level.flatMap((item) => {
82
+ const kids = navigableChildren(item.node);
83
+ if (!kids.length)
84
+ return [item];
85
+ changed = true;
86
+ const trail = [...item.trail, labelOf$1(item.node)];
87
+ return kids.map((node) => ({ node, trail, privileged: item.privileged }));
88
+ });
89
+ return changed ? next : null;
90
+ }
91
+ function toShortcut({ node, trail, privileged }) {
92
+ const below = destinations([node]);
93
+ return {
94
+ id: identityOf(node),
95
+ label: labelOf$1(node),
96
+ icon: node.route_icon ?? null,
97
+ redirect: node.route_redirect || below[0]?.redirect || '',
98
+ trail,
99
+ screens: below.length - (isPage(node) ? 1 : 0),
100
+ privileged,
101
+ };
102
+ }
103
+ /**
104
+ * Picks the shallowest level of the tree where there is a real choice to make: a single item is
105
+ * skipped over, a pair is opened up while the result still fits the grid, three or more stay put.
106
+ */
107
+ function entryShortcuts(tree) {
108
+ let level = modules(tree).map((node) => ({ node, trail: [], privileged: isPrivileged(node) }));
109
+ while (level.length > 0 && level.length < 3) {
110
+ const next = descend(level);
111
+ if (!next)
112
+ break;
113
+ if (level.length === 2 && next.length > MAX_TILES)
114
+ break;
115
+ level = next;
116
+ }
117
+ return level.map(toShortcut);
118
+ }
119
+
120
+ /** Remembers, for the whole app load, that the home screen already jumped to the only screen. */
121
+ class HomeAutoRedirect {
122
+ constructor() {
123
+ this.done = false;
124
+ }
125
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HomeAutoRedirect, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
126
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HomeAutoRedirect, providedIn: 'root' }); }
127
+ }
128
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HomeAutoRedirect, decorators: [{
129
+ type: Injectable,
130
+ args: [{ providedIn: 'root' }]
131
+ }] });
132
+ const TRAIL_SEPARATOR = ' › ';
133
+ // Recentes e favoritos lado a lado mostram o mesmo número de linhas; o resto fica no "Ver tudo".
134
+ const RECENT_ROWS = 5;
135
+ const FAVORITE_ROWS = 5;
136
+ const NEWS_ROWS = 2;
137
+ const SYSTEM_CHIPS = 3;
138
+ const MINUTE = 60_000;
139
+ const HOUR = 60 * MINUTE;
140
+ const DAY = 24 * HOUR;
141
+ let instances = 0;
142
+ function cleanTree(view) {
143
+ return spineRoutesOf(view).filter((node) => node != null);
144
+ }
145
+ function pathOf(redirect) {
146
+ return (redirect ?? '').split(/[?#]/)[0];
147
+ }
148
+ function labelOf(node) {
149
+ return (node.route_display_name ?? node.route_name ?? '').trim();
150
+ }
151
+ class HomeComponent {
152
+ constructor() {
153
+ this.router = inject(Router);
154
+ this.injector = inject(Injector);
155
+ this.quickAccess = inject(SpineQuickAccessService);
156
+ this.docs = inject(SpineDocsService);
157
+ this.settings = inject(SpineSettingsService);
158
+ this.news = inject(SpineNewsService);
159
+ this.autoRedirect = inject(HomeAutoRedirect);
160
+ this.messages = inject(SPINE_MESSAGES);
161
+ this.t = this.messages.home;
162
+ this.uid = `home-${++instances}`;
163
+ this.view = inject(ViewService);
164
+ this.tree = toSignal(this.view.currentView$.pipe(map(cleanTree)), {
165
+ initialValue: cleanTree(this.view.currentView),
166
+ });
167
+ /** "Bom dia, Lucas": the first name from the session, the salutation from the hour the page was opened. */
168
+ this.greeting = this.greet(inject(AuthorizationService, { optional: true })?.profile?.name);
169
+ this.workDestinations = computed(() => destinations(modules(this.tree())), ...(ngDevMode ? [{ debugName: "workDestinations" }] : /* istanbul ignore next */ []));
170
+ this.noAccess = computed(() => this.tree().length > 0 && modules(this.tree()).length === 0, ...(ngDevMode ? [{ debugName: "noAccess" }] : /* istanbul ignore next */ []));
171
+ this.areas = computed(() => systemAreas(this.tree()), ...(ngDevMode ? [{ debugName: "areas" }] : /* istanbul ignore next */ []));
172
+ this.shortcuts = computed(() => entryShortcuts(this.tree()), ...(ngDevMode ? [{ debugName: "shortcuts" }] : /* istanbul ignore next */ []));
173
+ this.workTiles = computed(() => this.shortcuts().filter((s) => !s.privileged), ...(ngDevMode ? [{ debugName: "workTiles" }] : /* istanbul ignore next */ []));
174
+ this.adminTiles = computed(() => this.shortcuts().filter((s) => s.privileged), ...(ngDevMode ? [{ debugName: "adminTiles" }] : /* istanbul ignore next */ []));
175
+ this.descended = computed(() => this.shortcuts().some((s) => s.trail.length > 0), ...(ngDevMode ? [{ debugName: "descended" }] : /* istanbul ignore next */ []));
176
+ /**
177
+ * Screens the current badge can open (every page in the tree, the start pages included). Recents and favorites
178
+ * are filtered by it: switching badges hides what the new one cannot reach, without forgetting it.
179
+ */
180
+ this.reachable = computed(() => {
181
+ const tree = this.tree();
182
+ return new Set([...destinations(tree).map((d) => pathOf(d.redirect)), ...systemAreas(tree).map((a) => pathOf(a.route_redirect))]);
183
+ }, ...(ngDevMode ? [{ debugName: "reachable" }] : /* istanbul ignore next */ []));
184
+ this.canOpen = (item) => this.reachable().has(pathOf(item.redirect));
185
+ /** Top-level modules with something inside: their first sub-items as chips and the first screen to open. */
186
+ this.systems = computed(() => modules(this.tree()).flatMap((node) => {
187
+ const children = (node.routes ?? []).filter((child) => child != null);
188
+ const screens = destinations([node]);
189
+ if (children.length < 2 || screens.length === 0)
190
+ return [];
191
+ return [
192
+ {
193
+ id: node.routed_id,
194
+ label: labelOf(node),
195
+ icon: node.route_icon || 'grid-layout',
196
+ description: (node.description ?? '').trim(),
197
+ count: pluralMessage(this.t.screenCount, screens.length, this.messages.meta.locale),
198
+ chips: children.slice(0, SYSTEM_CHIPS).map(labelOf).filter(Boolean),
199
+ redirect: node.route_redirect || screens[0].redirect,
200
+ },
201
+ ];
202
+ }), ...(ngDevMode ? [{ debugName: "systems" }] : /* istanbul ignore next */ []));
203
+ this.showAllRecents = signal(false, ...(ngDevMode ? [{ debugName: "showAllRecents" }] : /* istanbul ignore next */ []));
204
+ this.showAllFavorites = signal(false, ...(ngDevMode ? [{ debugName: "showAllFavorites" }] : /* istanbul ignore next */ []));
205
+ this.favorites = computed(() => this.quickAccess.favorites().filter(this.canOpen), ...(ngDevMode ? [{ debugName: "favorites" }] : /* istanbul ignore next */ []));
206
+ this.visibleFavorites = computed(() => this.showAllFavorites() ? this.favorites() : this.favorites().slice(0, FAVORITE_ROWS), ...(ngDevMode ? [{ debugName: "visibleFavorites" }] : /* istanbul ignore next */ []));
207
+ this.moreFavorites = computed(() => this.favorites().length > FAVORITE_ROWS, ...(ngDevMode ? [{ debugName: "moreFavorites" }] : /* istanbul ignore next */ []));
208
+ this.allRecents = computed(() => {
209
+ const now = Date.now();
210
+ return this.quickAccess
211
+ .recents()
212
+ .filter(this.canOpen)
213
+ .map((item) => ({ ...item, where: this.whereOf(item), when: this.whenOf(item, now) }));
214
+ }, ...(ngDevMode ? [{ debugName: "allRecents" }] : /* istanbul ignore next */ []));
215
+ this.recentRows = computed(() => this.showAllRecents() ? this.allRecents() : this.allRecents().slice(0, RECENT_ROWS), ...(ngDevMode ? [{ debugName: "recentRows" }] : /* istanbul ignore next */ []));
216
+ this.moreRecents = computed(() => this.allRecents().length > RECENT_ROWS, ...(ngDevMode ? [{ debugName: "moreRecents" }] : /* istanbul ignore next */ []));
217
+ this.newsRows = computed(() => this.news.highlights().slice(0, NEWS_ROWS), ...(ngDevMode ? [{ debugName: "newsRows" }] : /* istanbul ignore next */ []));
218
+ this.recentMenu = [{ id: 'forget', label: this.t.forgetRecent, icon: 'times' }];
219
+ effect(() => {
220
+ const only = this.workDestinations();
221
+ if (only.length !== 1 || this.autoRedirect.done)
222
+ return;
223
+ this.autoRedirect.done = true;
224
+ void this.router.navigateByUrl(only[0].redirect, { replaceUrl: true });
225
+ });
226
+ }
227
+ caption(tile) {
228
+ if (tile.screens > 0)
229
+ return pluralMessage(this.t.screenCount, tile.screens, this.messages.meta.locale);
230
+ return tile.trail.join(TRAIL_SEPARATOR);
231
+ }
232
+ /** Where the screen sits in the menu; empty for a top-level screen (nothing above it to show). */
233
+ whereOf(item) {
234
+ const trail = item.trail ?? trailFor(this.tree(), item.redirect);
235
+ return trail?.join(TRAIL_SEPARATOR) ?? '';
236
+ }
237
+ isFavorite(id) {
238
+ return this.quickAccess.isFavorite(id);
239
+ }
240
+ favoriteLabel(row) {
241
+ const template = this.isFavorite(row.id) ? this.t.unfavorite : this.t.favorite;
242
+ return formatMessage(template, { name: row.displayName });
243
+ }
244
+ toggleFavorite(row) {
245
+ this.quickAccess.toggleFavorite({
246
+ id: row.id,
247
+ icon: row.icon,
248
+ displayName: row.displayName,
249
+ redirect: row.redirect,
250
+ trail: row.trail ?? trailFor(this.tree(), row.redirect) ?? [],
251
+ });
252
+ }
253
+ recentMenuLabel(row) {
254
+ return formatMessage(this.t.recentActions, { name: row.displayName });
255
+ }
256
+ onRecentMenu(row, action) {
257
+ if (action === 'forget')
258
+ this.quickAccess.forgetRecent(row.id);
259
+ }
260
+ go(redirect) {
261
+ if (redirect)
262
+ void this.router.navigateByUrl(redirect);
263
+ }
264
+ openSettings() {
265
+ this.settings.open();
266
+ }
267
+ openDocs() {
268
+ this.docs.openIndex();
269
+ }
270
+ // Lidos na hora do clique: a home não precisa montar o painel de atalhos nem a configuração inicial para aparecer.
271
+ openShortcuts() {
272
+ this.injector.get(ShortcutService).help$.next();
273
+ }
274
+ openEssentials() {
275
+ this.injector.get(SpineInitialSetupService).openEssentials();
276
+ }
277
+ /** Com `id`: abre o painel já com a novidade aberta; sem, a lista. */
278
+ openNews(id) {
279
+ if (id)
280
+ this.news.showItem(id);
281
+ else
282
+ this.news.showAll();
283
+ }
284
+ greet(fullName) {
285
+ const hour = new Date().getHours();
286
+ const greeting = hour < 12 ? this.t.morning : hour < 18 ? this.t.afternoon : this.t.evening;
287
+ const name = (fullName ?? '').trim().split(/\s+/)[0];
288
+ return name ? formatMessage(this.t.greetingName, { greeting, name }) : greeting;
289
+ }
290
+ /** "Há 12 min", "Ontem", "Há 2 dias" (Intl, no idioma do catálogo); vazio para item gravado sem horário. */
291
+ whenOf(item, now) {
292
+ if (!item.visitedAt)
293
+ return '';
294
+ const locale = this.messages.meta.locale;
295
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'short' });
296
+ const ago = Math.max(0, now - item.visitedAt);
297
+ const text = ago < MINUTE
298
+ ? rtf.format(0, 'second')
299
+ : ago < HOUR
300
+ ? rtf.format(-Math.floor(ago / MINUTE), 'minute')
301
+ : ago < DAY
302
+ ? rtf.format(-Math.floor(ago / HOUR), 'hour')
303
+ : rtf.format(-Math.floor(ago / DAY), 'day');
304
+ return text.charAt(0).toLocaleUpperCase(locale) + text.slice(1);
305
+ }
306
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HomeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
307
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: HomeComponent, isStandalone: true, selector: "home", ngImport: i0, template: "@if (noAccess()) {\n <hm-state-page kind=\"no-rights\" [heading]=\"t.noAccessTitle\" [description]=\"t.noAccessText\" action=\"\" />\n} @else {\n <div class=\"home\">\n <!-- Sauda\u00E7\u00E3o \u00E0 esquerda; as novidades, quando houver, ocupam o espa\u00E7o \u00E0 direita dela. -->\n <div class=\"hero\">\n <div class=\"hero__text\">\n <p class=\"hero__greeting\">{{ greeting }}</p>\n <p class=\"hero__intro\">{{ t.intro }}</p>\n </div>\n @if (newsRows().length) {\n <!-- Compacto: cabe\u00E7alho numa linha e cada novidade numa linha s\u00F3 (t\u00EDtulo e o come\u00E7o do texto). -->\n <section class=\"card card--compact hero__news\" [attr.aria-labelledby]=\"uid + '-news'\">\n <div class=\"card__head\">\n <span class=\"card__heading\">\n <h2 class=\"card__title\" [id]=\"uid + '-news'\">{{ t.news }}</h2>\n <span class=\"badge\">{{ t.newsBadge }}</span>\n </span>\n <button type=\"button\" class=\"more\" (click)=\"openNews()\">{{ t.newsAll }}</button>\n </div>\n <ul class=\"list\">\n @for (entry of newsRows(); track entry.id) {\n <li>\n <button type=\"button\" class=\"news\" [title]=\"entry.title + ' \u2014 ' + entry.text\" (click)=\"openNews(entry.id)\">\n <span class=\"news__dot\" [class.news__dot--seen]=\"!entry.unseen\" aria-hidden=\"true\"></span>\n <span class=\"news__title\">{{ entry.title }}</span>\n <span class=\"news__body\">{{ entry.text }}</span>\n </button>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n\n <!-- Atalhos de trabalho e privilegiados dividem a mesma linha (cada grupo na propor\u00E7\u00E3o dos seus atalhos). -->\n @if (workTiles().length || adminTiles().length) {\n <div class=\"shortcuts\">\n @if (workTiles().length) {\n <section class=\"block shortcuts__group\" [style.--tiles]=\"workTiles().length\" [attr.aria-labelledby]=\"uid + '-work'\">\n <h2 class=\"block__title\" [id]=\"uid + '-work'\">{{ descended() ? t.yourScreens : t.modules }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: workTiles(), admin: false }\" />\n </section>\n }\n @if (adminTiles().length) {\n <section class=\"block shortcuts__group\" [style.--tiles]=\"adminTiles().length\" [attr.aria-labelledby]=\"uid + '-admin'\">\n <h2 class=\"block__title\" [id]=\"uid + '-admin'\">{{ t.privileged }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: adminTiles(), admin: true }\" />\n </section>\n }\n </div>\n }\n\n <!-- Linha 1: o que a pessoa usa (recentes | favoritos). Linha 2: o resto do sistema (m\u00F3dulos | ajuda).\n Sem um dos lados, o outro ocupa a linha inteira. -->\n <div class=\"pair\">\n @if (recentRows().length) {\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-recent'\">\n <div class=\"card__head\">\n <h2 class=\"card__title\" [id]=\"uid + '-recent'\">{{ t.recent }}</h2>\n @if (moreRecents()) {\n <button type=\"button\" class=\"more\" [attr.aria-expanded]=\"showAllRecents()\" (click)=\"showAllRecents.set(!showAllRecents())\">\n {{ showAllRecents() ? t.seeLess : t.seeAll }}\n </button>\n }\n </div>\n <ul class=\"list\" [attr.aria-label]=\"t.recentCaption\">\n @for (item of recentRows(); track item.id) {\n <li class=\"recent\">\n <button type=\"button\" class=\"recent__open\" (click)=\"go(item.redirect)\">\n <hm-icon class=\"recent__icon\" [shape]=\"item.icon || 'grid-layout'\" />\n <span class=\"recent__name\">{{ item.displayName }}</span>\n <span class=\"recent__where\">{{ item.where }}</span>\n @if (item.when) {\n <span class=\"recent__when\">{{ item.when }}</span>\n }\n </button>\n <hm-icon-button\n emphasis=\"subtle\"\n size=\"small\"\n icon=\"heart\"\n [solid]=\"isFavorite(item.id)\"\n [class.heart--on]=\"isFavorite(item.id)\"\n [label]=\"favoriteLabel(item)\"\n (clicked)=\"toggleFavorite(item)\"\n />\n <hm-menu-button\n class=\"recent__menu\"\n icon=\"ellipsis-vertical\"\n shape=\"circle\"\n size=\"small\"\n align=\"end\"\n [showArrow]=\"false\"\n [label]=\"recentMenuLabel(item)\"\n [items]=\"recentMenu\"\n (chosen)=\"onRecentMenu(item, $event)\"\n />\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-fav'\">\n <div class=\"card__head\">\n <h2 class=\"card__title\" [id]=\"uid + '-fav'\">{{ t.favorites }}</h2>\n @if (moreFavorites()) {\n <button type=\"button\" class=\"more\" [attr.aria-expanded]=\"showAllFavorites()\" (click)=\"showAllFavorites.set(!showAllFavorites())\">\n {{ showAllFavorites() ? t.seeLess : t.seeAll }}\n </button>\n }\n </div>\n @if (visibleFavorites().length) {\n <ul class=\"list\">\n @for (fav of visibleFavorites(); track fav.id) {\n <li>\n <button type=\"button\" class=\"row\" (click)=\"go(fav.redirect)\">\n <hm-icon class=\"row__icon\" [shape]=\"fav.icon || 'grid-layout'\" />\n <span class=\"row__name\">{{ fav.displayName }}</span>\n <span class=\"row__meta\">{{ whereOf(fav) }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n } @else {\n <p class=\"card__empty\">{{ t.favoritesEmpty }}</p>\n }\n </section>\n </div>\n\n <div class=\"pair\">\n @if (systems().length) {\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-systems'\">\n <div class=\"card__head card__head--stacked\">\n <h2 class=\"card__title\" [id]=\"uid + '-systems'\">{{ t.explore }}</h2>\n <p class=\"card__intro\">{{ t.exploreIntro }}</p>\n </div>\n <ul class=\"systems\">\n @for (system of systems(); track system.id) {\n <!-- O card inteiro abre o m\u00F3dulo (como os atalhos): uma seta no canto, sem link pr\u00F3prio. -->\n <li class=\"systems__cell\">\n <button type=\"button\" class=\"system\" (click)=\"go(system.redirect)\">\n <span class=\"system__head\">\n <hm-icon class=\"system__icon\" [shape]=\"system.icon\" size=\"lg\" />\n <span class=\"system__name\" [title]=\"system.label\">{{ system.label }}</span>\n <span class=\"badge badge--neutral\">{{ system.count }}</span>\n <hm-icon class=\"system__chevron\" shape=\"angle-right\" size=\"sm\" />\n </span>\n @if (system.description) {\n <span class=\"system__description\">{{ system.description }}</span>\n }\n @if (system.chips.length) {\n <span class=\"system__chips\">\n @for (chip of system.chips; track chip) {\n <span class=\"system__chip\">{{ chip }}</span>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-help'\">\n <div class=\"card__head\">\n <h2 class=\"card__title\" [id]=\"uid + '-help'\">{{ t.help }}</h2>\n </div>\n <ul class=\"list\">\n @for (area of areas(); track area.routed_id) {\n <li>\n <button type=\"button\" class=\"row\" (click)=\"go(area.route_redirect)\">\n <hm-icon class=\"row__icon\" [shape]=\"area.route_icon || 'home'\" />\n <span class=\"row__name\">{{ area.route_display_name }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n }\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openDocs()\">\n <hm-icon class=\"row__icon\" shape=\"book\" />\n <span class=\"row__name\">{{ t.systemDocs }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openShortcuts()\">\n <hm-icon class=\"row__icon\" shape=\"keyboard\" />\n <span class=\"row__name\">{{ t.shortcuts }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openEssentials()\">\n <hm-icon class=\"row__icon\" shape=\"help\" />\n <span class=\"row__name\">{{ t.essentials }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openSettings()\">\n <hm-icon class=\"row__icon\" shape=\"cog\" />\n <span class=\"row__name\">{{ t.settings }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n </ul>\n </section>\n </div>\n </div>\n}\n\n<ng-template #tileGrid let-tiles let-admin=\"admin\">\n <ul class=\"tiles\" [class.tiles--admin]=\"admin\">\n @for (tile of tiles; track tile.id; let i = $index) {\n <li class=\"tiles__cell\" [style.--tile-order]=\"i\">\n <button type=\"button\" class=\"tile\" (click)=\"go(tile.redirect)\">\n <hm-icon class=\"tile__icon\" [shape]=\"tile.icon ?? 'grid-layout'\" size=\"lg\" />\n <span class=\"tile__text\">\n <span class=\"tile__label\">{{ tile.label }}</span>\n @if (caption(tile); as meta) {\n <span class=\"tile__meta\">{{ meta }}</span>\n }\n </span>\n <hm-icon class=\"tile__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:flex;flex:1 1 auto;flex-direction:column;min-block-size:100%;min-inline-size:0}hm-state-page{flex:1 1 auto}ul{margin:0;padding:0;list-style:none}p{margin:0}.home{container-type:inline-size;display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.hero{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-md)}.hero__text{display:flex;flex:1 1 20rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.hero__news{flex:0 1 32rem;min-inline-size:min(100%,20rem)}.hero__greeting{color:var(--hm-text);font-size:var(--hm-font-size-xxl);font-weight:var(--hm-font-weight-bold);line-height:var(--hm-line-height-tight)}.hero__intro{color:var(--hm-muted);font-size:var(--hm-font-size-md)}.block{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-inline-size:0}.block__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.news,.row,.system,.recent__open,.more,.tile{display:flex;align-items:center;gap:var(--hm-space-sm);border:var(--hm-border-hairline) solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;text-align:start;cursor:pointer;transition:background-color var(--hm-duration-quick) var(--hm-ease-standard),border-color var(--hm-duration-quick) var(--hm-ease-standard)}.news:hover,.row:hover,.system:hover,.recent__open:hover,.more:hover,.tile:hover{background:var(--hm-hover)}.news:focus-visible,.row:focus-visible,.system:focus-visible,.recent__open:focus-visible,.more:focus-visible,.tile:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:2px}@media(forced-colors:active){.news,.row,.system,.recent__open,.more,.tile{border-color:ButtonText}.news:focus-visible,.row:focus-visible,.system:focus-visible,.recent__open:focus-visible,.more:focus-visible,.tile:focus-visible{outline-color:Highlight}}.shortcuts{display:flex;flex-wrap:wrap;gap:var(--hm-space-md)}.shortcuts__group{flex:var(--tiles, 1) 1 calc(var(--tiles, 1) * 13rem + (var(--tiles, 1) - 1) * var(--hm-space-sm))}.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,13rem),1fr));gap:var(--hm-space-sm)}.tiles__cell{display:flex}.tile{flex:1 1 auto;gap:var(--hm-space-md);padding:var(--hm-space-md);border-color:var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.tile:hover{border-color:var(--hm-accent);background:var(--hm-surface-raised)}.tile__icon{flex:none;color:var(--hm-accent)}.tiles--admin .tile__icon{color:var(--hm-warning)}.tile__text{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.tile__label{font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold);overflow-wrap:anywhere}.tile__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.tile__chevron,.row__chevron{flex:none;color:var(--hm-muted)}@media(prefers-reduced-motion:no-preference){.tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}}:host-context([data-motion=full]) .tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}:host-context([data-motion=reduce]) .tiles__cell{animation:none}@keyframes tile-in{0%{opacity:0;transform:translateY(var(--hm-space-xs))}}.pair{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr);gap:var(--hm-space-md);align-items:stretch}.pair>:only-child{grid-column:1/-1}.stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.stack>.card:last-child{flex:1 1 auto}@container (max-width: 56rem){.pair{grid-template-columns:minmax(0,1fr)}}.card{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-inline-size:0;padding:var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.card__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm)}.card__head--stacked{flex-direction:column;align-items:flex-start;gap:var(--hm-space-micro)}.card__heading{display:flex;align-items:center;gap:var(--hm-space-xs)}.card__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.card__intro,.card__empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.more{flex:none;padding:var(--hm-space-nano) var(--hm-space-xs);color:var(--hm-accent);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-medium)}.badge{flex:none;padding:var(--hm-space-nano) var(--hm-space-xs);border-radius:var(--hm-radius-micro);background:var(--hm-accent-subtle);color:var(--hm-accent);font-size:var(--hm-font-size-3xs);font-weight:var(--hm-font-weight-semibold);letter-spacing:.04em;text-transform:uppercase}.badge--neutral{background:var(--hm-surface-muted);color:var(--hm-muted);letter-spacing:normal;text-transform:none}.card--compact{gap:var(--hm-space-micro);padding:var(--hm-space-sm) var(--hm-space-md)}.recent{display:flex;align-items:center;gap:var(--hm-space-micro)}.recent__open{flex:1 1 auto;min-inline-size:0;padding:var(--hm-space-nano) var(--hm-space-xs)}.recent__icon{flex:none;color:var(--hm-accent)}.recent__name{flex:none;max-inline-size:50%;overflow:hidden;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);text-overflow:ellipsis;white-space:nowrap}.recent__where{flex:1 1 auto;min-inline-size:0;overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);text-overflow:ellipsis;white-space:nowrap}.recent__when{flex:none;color:var(--hm-muted);font-size:var(--hm-font-size-xs);white-space:nowrap}.heart--on{color:var(--hm-danger)}.systems{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,14rem),1fr));gap:var(--hm-space-sm)}.systems__cell{display:flex}.system{flex:1 1 auto;flex-direction:column;align-items:stretch;gap:var(--hm-space-xs);padding:var(--hm-space-sm) var(--hm-space-md);border-color:var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.system:hover{border-color:var(--hm-accent);background:var(--hm-surface-raised)}.system__head{display:flex;align-items:center;gap:var(--hm-space-sm);min-inline-size:0}.system__icon{flex:none;color:var(--hm-accent)}.system__name{min-inline-size:0;overflow:hidden;font-weight:var(--hm-font-weight-semibold);text-overflow:ellipsis;white-space:nowrap}.system__chevron{flex:none;margin-inline-start:auto;color:var(--hm-muted)}.system__description{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.system__chips{display:flex;flex-wrap:wrap;gap:var(--hm-space-micro)}.system__chip{padding:var(--hm-space-nano) var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius-pill);background:var(--hm-surface-muted);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.list{display:flex;flex-direction:column}.list>li+li{border-block-start:var(--hm-border-hairline) solid var(--hm-divider)}.row{inline-size:100%;padding:var(--hm-space-nano) var(--hm-space-xs)}.row__icon{flex:none;color:var(--hm-muted)}.row__name{flex:1 1 auto;min-inline-size:0;overflow:hidden;font-size:var(--hm-font-size-sm);text-overflow:ellipsis;white-space:nowrap}.row__meta{flex:0 1 auto;min-inline-size:0;max-inline-size:45%;overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);text-overflow:ellipsis;white-space:nowrap}.news{inline-size:100%;gap:var(--hm-space-xs);padding:var(--hm-space-nano) var(--hm-space-xs)}.news__dot{flex:none;inline-size:var(--hm-space-xs);block-size:var(--hm-space-xs);border-radius:var(--hm-radius-pill);background:var(--hm-accent)}.news__dot--seen{background:var(--hm-divider)}.news__title{flex:none;max-inline-size:50%;overflow:hidden;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);text-overflow:ellipsis;white-space:nowrap}.news__body{flex:1 1 auto;min-inline-size:0;overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconButtonComponent, selector: "hm-icon-button", inputs: ["label", "icon", "solid", "iconDirection", "emphasis", "intent", "size", "disabled", "toggle", "pressed", "keyShortcuts"], outputs: ["pressedChange", "clicked"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: MenuButtonComponent, selector: "hm-menu-button", inputs: ["label", "icon", "items", "showArrow", "align", "side", "size", "shape", "layer", "closeOnCheck", "disabled"], outputs: ["chosen", "checkedChange"] }, { kind: "component", type: StatePageComponent, selector: "hm-state-page", inputs: ["kind", "heading", "description", "action", "actionIcon"], outputs: ["actionTriggered"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
308
+ }
309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HomeComponent, decorators: [{
310
+ type: Component,
311
+ args: [{ selector: 'home', imports: [NgTemplateOutlet, IconButtonComponent, IconComponent, MenuButtonComponent, StatePageComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (noAccess()) {\n <hm-state-page kind=\"no-rights\" [heading]=\"t.noAccessTitle\" [description]=\"t.noAccessText\" action=\"\" />\n} @else {\n <div class=\"home\">\n <!-- Sauda\u00E7\u00E3o \u00E0 esquerda; as novidades, quando houver, ocupam o espa\u00E7o \u00E0 direita dela. -->\n <div class=\"hero\">\n <div class=\"hero__text\">\n <p class=\"hero__greeting\">{{ greeting }}</p>\n <p class=\"hero__intro\">{{ t.intro }}</p>\n </div>\n @if (newsRows().length) {\n <!-- Compacto: cabe\u00E7alho numa linha e cada novidade numa linha s\u00F3 (t\u00EDtulo e o come\u00E7o do texto). -->\n <section class=\"card card--compact hero__news\" [attr.aria-labelledby]=\"uid + '-news'\">\n <div class=\"card__head\">\n <span class=\"card__heading\">\n <h2 class=\"card__title\" [id]=\"uid + '-news'\">{{ t.news }}</h2>\n <span class=\"badge\">{{ t.newsBadge }}</span>\n </span>\n <button type=\"button\" class=\"more\" (click)=\"openNews()\">{{ t.newsAll }}</button>\n </div>\n <ul class=\"list\">\n @for (entry of newsRows(); track entry.id) {\n <li>\n <button type=\"button\" class=\"news\" [title]=\"entry.title + ' \u2014 ' + entry.text\" (click)=\"openNews(entry.id)\">\n <span class=\"news__dot\" [class.news__dot--seen]=\"!entry.unseen\" aria-hidden=\"true\"></span>\n <span class=\"news__title\">{{ entry.title }}</span>\n <span class=\"news__body\">{{ entry.text }}</span>\n </button>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n\n <!-- Atalhos de trabalho e privilegiados dividem a mesma linha (cada grupo na propor\u00E7\u00E3o dos seus atalhos). -->\n @if (workTiles().length || adminTiles().length) {\n <div class=\"shortcuts\">\n @if (workTiles().length) {\n <section class=\"block shortcuts__group\" [style.--tiles]=\"workTiles().length\" [attr.aria-labelledby]=\"uid + '-work'\">\n <h2 class=\"block__title\" [id]=\"uid + '-work'\">{{ descended() ? t.yourScreens : t.modules }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: workTiles(), admin: false }\" />\n </section>\n }\n @if (adminTiles().length) {\n <section class=\"block shortcuts__group\" [style.--tiles]=\"adminTiles().length\" [attr.aria-labelledby]=\"uid + '-admin'\">\n <h2 class=\"block__title\" [id]=\"uid + '-admin'\">{{ t.privileged }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: adminTiles(), admin: true }\" />\n </section>\n }\n </div>\n }\n\n <!-- Linha 1: o que a pessoa usa (recentes | favoritos). Linha 2: o resto do sistema (m\u00F3dulos | ajuda).\n Sem um dos lados, o outro ocupa a linha inteira. -->\n <div class=\"pair\">\n @if (recentRows().length) {\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-recent'\">\n <div class=\"card__head\">\n <h2 class=\"card__title\" [id]=\"uid + '-recent'\">{{ t.recent }}</h2>\n @if (moreRecents()) {\n <button type=\"button\" class=\"more\" [attr.aria-expanded]=\"showAllRecents()\" (click)=\"showAllRecents.set(!showAllRecents())\">\n {{ showAllRecents() ? t.seeLess : t.seeAll }}\n </button>\n }\n </div>\n <ul class=\"list\" [attr.aria-label]=\"t.recentCaption\">\n @for (item of recentRows(); track item.id) {\n <li class=\"recent\">\n <button type=\"button\" class=\"recent__open\" (click)=\"go(item.redirect)\">\n <hm-icon class=\"recent__icon\" [shape]=\"item.icon || 'grid-layout'\" />\n <span class=\"recent__name\">{{ item.displayName }}</span>\n <span class=\"recent__where\">{{ item.where }}</span>\n @if (item.when) {\n <span class=\"recent__when\">{{ item.when }}</span>\n }\n </button>\n <hm-icon-button\n emphasis=\"subtle\"\n size=\"small\"\n icon=\"heart\"\n [solid]=\"isFavorite(item.id)\"\n [class.heart--on]=\"isFavorite(item.id)\"\n [label]=\"favoriteLabel(item)\"\n (clicked)=\"toggleFavorite(item)\"\n />\n <hm-menu-button\n class=\"recent__menu\"\n icon=\"ellipsis-vertical\"\n shape=\"circle\"\n size=\"small\"\n align=\"end\"\n [showArrow]=\"false\"\n [label]=\"recentMenuLabel(item)\"\n [items]=\"recentMenu\"\n (chosen)=\"onRecentMenu(item, $event)\"\n />\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-fav'\">\n <div class=\"card__head\">\n <h2 class=\"card__title\" [id]=\"uid + '-fav'\">{{ t.favorites }}</h2>\n @if (moreFavorites()) {\n <button type=\"button\" class=\"more\" [attr.aria-expanded]=\"showAllFavorites()\" (click)=\"showAllFavorites.set(!showAllFavorites())\">\n {{ showAllFavorites() ? t.seeLess : t.seeAll }}\n </button>\n }\n </div>\n @if (visibleFavorites().length) {\n <ul class=\"list\">\n @for (fav of visibleFavorites(); track fav.id) {\n <li>\n <button type=\"button\" class=\"row\" (click)=\"go(fav.redirect)\">\n <hm-icon class=\"row__icon\" [shape]=\"fav.icon || 'grid-layout'\" />\n <span class=\"row__name\">{{ fav.displayName }}</span>\n <span class=\"row__meta\">{{ whereOf(fav) }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n } @else {\n <p class=\"card__empty\">{{ t.favoritesEmpty }}</p>\n }\n </section>\n </div>\n\n <div class=\"pair\">\n @if (systems().length) {\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-systems'\">\n <div class=\"card__head card__head--stacked\">\n <h2 class=\"card__title\" [id]=\"uid + '-systems'\">{{ t.explore }}</h2>\n <p class=\"card__intro\">{{ t.exploreIntro }}</p>\n </div>\n <ul class=\"systems\">\n @for (system of systems(); track system.id) {\n <!-- O card inteiro abre o m\u00F3dulo (como os atalhos): uma seta no canto, sem link pr\u00F3prio. -->\n <li class=\"systems__cell\">\n <button type=\"button\" class=\"system\" (click)=\"go(system.redirect)\">\n <span class=\"system__head\">\n <hm-icon class=\"system__icon\" [shape]=\"system.icon\" size=\"lg\" />\n <span class=\"system__name\" [title]=\"system.label\">{{ system.label }}</span>\n <span class=\"badge badge--neutral\">{{ system.count }}</span>\n <hm-icon class=\"system__chevron\" shape=\"angle-right\" size=\"sm\" />\n </span>\n @if (system.description) {\n <span class=\"system__description\">{{ system.description }}</span>\n }\n @if (system.chips.length) {\n <span class=\"system__chips\">\n @for (chip of system.chips; track chip) {\n <span class=\"system__chip\">{{ chip }}</span>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-help'\">\n <div class=\"card__head\">\n <h2 class=\"card__title\" [id]=\"uid + '-help'\">{{ t.help }}</h2>\n </div>\n <ul class=\"list\">\n @for (area of areas(); track area.routed_id) {\n <li>\n <button type=\"button\" class=\"row\" (click)=\"go(area.route_redirect)\">\n <hm-icon class=\"row__icon\" [shape]=\"area.route_icon || 'home'\" />\n <span class=\"row__name\">{{ area.route_display_name }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n }\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openDocs()\">\n <hm-icon class=\"row__icon\" shape=\"book\" />\n <span class=\"row__name\">{{ t.systemDocs }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openShortcuts()\">\n <hm-icon class=\"row__icon\" shape=\"keyboard\" />\n <span class=\"row__name\">{{ t.shortcuts }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openEssentials()\">\n <hm-icon class=\"row__icon\" shape=\"help\" />\n <span class=\"row__name\">{{ t.essentials }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openSettings()\">\n <hm-icon class=\"row__icon\" shape=\"cog\" />\n <span class=\"row__name\">{{ t.settings }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n </ul>\n </section>\n </div>\n </div>\n}\n\n<ng-template #tileGrid let-tiles let-admin=\"admin\">\n <ul class=\"tiles\" [class.tiles--admin]=\"admin\">\n @for (tile of tiles; track tile.id; let i = $index) {\n <li class=\"tiles__cell\" [style.--tile-order]=\"i\">\n <button type=\"button\" class=\"tile\" (click)=\"go(tile.redirect)\">\n <hm-icon class=\"tile__icon\" [shape]=\"tile.icon ?? 'grid-layout'\" size=\"lg\" />\n <span class=\"tile__text\">\n <span class=\"tile__label\">{{ tile.label }}</span>\n @if (caption(tile); as meta) {\n <span class=\"tile__meta\">{{ meta }}</span>\n }\n </span>\n <hm-icon class=\"tile__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:flex;flex:1 1 auto;flex-direction:column;min-block-size:100%;min-inline-size:0}hm-state-page{flex:1 1 auto}ul{margin:0;padding:0;list-style:none}p{margin:0}.home{container-type:inline-size;display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.hero{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-md)}.hero__text{display:flex;flex:1 1 20rem;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.hero__news{flex:0 1 32rem;min-inline-size:min(100%,20rem)}.hero__greeting{color:var(--hm-text);font-size:var(--hm-font-size-xxl);font-weight:var(--hm-font-weight-bold);line-height:var(--hm-line-height-tight)}.hero__intro{color:var(--hm-muted);font-size:var(--hm-font-size-md)}.block{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-inline-size:0}.block__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.news,.row,.system,.recent__open,.more,.tile{display:flex;align-items:center;gap:var(--hm-space-sm);border:var(--hm-border-hairline) solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;text-align:start;cursor:pointer;transition:background-color var(--hm-duration-quick) var(--hm-ease-standard),border-color var(--hm-duration-quick) var(--hm-ease-standard)}.news:hover,.row:hover,.system:hover,.recent__open:hover,.more:hover,.tile:hover{background:var(--hm-hover)}.news:focus-visible,.row:focus-visible,.system:focus-visible,.recent__open:focus-visible,.more:focus-visible,.tile:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:2px}@media(forced-colors:active){.news,.row,.system,.recent__open,.more,.tile{border-color:ButtonText}.news:focus-visible,.row:focus-visible,.system:focus-visible,.recent__open:focus-visible,.more:focus-visible,.tile:focus-visible{outline-color:Highlight}}.shortcuts{display:flex;flex-wrap:wrap;gap:var(--hm-space-md)}.shortcuts__group{flex:var(--tiles, 1) 1 calc(var(--tiles, 1) * 13rem + (var(--tiles, 1) - 1) * var(--hm-space-sm))}.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,13rem),1fr));gap:var(--hm-space-sm)}.tiles__cell{display:flex}.tile{flex:1 1 auto;gap:var(--hm-space-md);padding:var(--hm-space-md);border-color:var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.tile:hover{border-color:var(--hm-accent);background:var(--hm-surface-raised)}.tile__icon{flex:none;color:var(--hm-accent)}.tiles--admin .tile__icon{color:var(--hm-warning)}.tile__text{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.tile__label{font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold);overflow-wrap:anywhere}.tile__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.tile__chevron,.row__chevron{flex:none;color:var(--hm-muted)}@media(prefers-reduced-motion:no-preference){.tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}}:host-context([data-motion=full]) .tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}:host-context([data-motion=reduce]) .tiles__cell{animation:none}@keyframes tile-in{0%{opacity:0;transform:translateY(var(--hm-space-xs))}}.pair{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr);gap:var(--hm-space-md);align-items:stretch}.pair>:only-child{grid-column:1/-1}.stack{display:flex;flex-direction:column;gap:var(--hm-space-md);min-inline-size:0}.stack>.card:last-child{flex:1 1 auto}@container (max-width: 56rem){.pair{grid-template-columns:minmax(0,1fr)}}.card{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-inline-size:0;padding:var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.card__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm)}.card__head--stacked{flex-direction:column;align-items:flex-start;gap:var(--hm-space-micro)}.card__heading{display:flex;align-items:center;gap:var(--hm-space-xs)}.card__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-semibold)}.card__intro,.card__empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.more{flex:none;padding:var(--hm-space-nano) var(--hm-space-xs);color:var(--hm-accent);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-medium)}.badge{flex:none;padding:var(--hm-space-nano) var(--hm-space-xs);border-radius:var(--hm-radius-micro);background:var(--hm-accent-subtle);color:var(--hm-accent);font-size:var(--hm-font-size-3xs);font-weight:var(--hm-font-weight-semibold);letter-spacing:.04em;text-transform:uppercase}.badge--neutral{background:var(--hm-surface-muted);color:var(--hm-muted);letter-spacing:normal;text-transform:none}.card--compact{gap:var(--hm-space-micro);padding:var(--hm-space-sm) var(--hm-space-md)}.recent{display:flex;align-items:center;gap:var(--hm-space-micro)}.recent__open{flex:1 1 auto;min-inline-size:0;padding:var(--hm-space-nano) var(--hm-space-xs)}.recent__icon{flex:none;color:var(--hm-accent)}.recent__name{flex:none;max-inline-size:50%;overflow:hidden;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);text-overflow:ellipsis;white-space:nowrap}.recent__where{flex:1 1 auto;min-inline-size:0;overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);text-overflow:ellipsis;white-space:nowrap}.recent__when{flex:none;color:var(--hm-muted);font-size:var(--hm-font-size-xs);white-space:nowrap}.heart--on{color:var(--hm-danger)}.systems{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,14rem),1fr));gap:var(--hm-space-sm)}.systems__cell{display:flex}.system{flex:1 1 auto;flex-direction:column;align-items:stretch;gap:var(--hm-space-xs);padding:var(--hm-space-sm) var(--hm-space-md);border-color:var(--hm-divider);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.system:hover{border-color:var(--hm-accent);background:var(--hm-surface-raised)}.system__head{display:flex;align-items:center;gap:var(--hm-space-sm);min-inline-size:0}.system__icon{flex:none;color:var(--hm-accent)}.system__name{min-inline-size:0;overflow:hidden;font-weight:var(--hm-font-weight-semibold);text-overflow:ellipsis;white-space:nowrap}.system__chevron{flex:none;margin-inline-start:auto;color:var(--hm-muted)}.system__description{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.system__chips{display:flex;flex-wrap:wrap;gap:var(--hm-space-micro)}.system__chip{padding:var(--hm-space-nano) var(--hm-space-xs);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:var(--hm-radius-pill);background:var(--hm-surface-muted);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.list{display:flex;flex-direction:column}.list>li+li{border-block-start:var(--hm-border-hairline) solid var(--hm-divider)}.row{inline-size:100%;padding:var(--hm-space-nano) var(--hm-space-xs)}.row__icon{flex:none;color:var(--hm-muted)}.row__name{flex:1 1 auto;min-inline-size:0;overflow:hidden;font-size:var(--hm-font-size-sm);text-overflow:ellipsis;white-space:nowrap}.row__meta{flex:0 1 auto;min-inline-size:0;max-inline-size:45%;overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);text-overflow:ellipsis;white-space:nowrap}.news{inline-size:100%;gap:var(--hm-space-xs);padding:var(--hm-space-nano) var(--hm-space-xs)}.news__dot{flex:none;inline-size:var(--hm-space-xs);block-size:var(--hm-space-xs);border-radius:var(--hm-radius-pill);background:var(--hm-accent)}.news__dot--seen{background:var(--hm-divider)}.news__title{flex:none;max-inline-size:50%;overflow:hidden;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);text-overflow:ellipsis;white-space:nowrap}.news__body{flex:1 1 auto;min-inline-size:0;overflow:hidden;color:var(--hm-muted);font-size:var(--hm-font-size-xs);text-overflow:ellipsis;white-space:nowrap}\n"] }]
312
+ }], ctorParameters: () => [] });
313
+
314
+ export { HomeAutoRedirect, HomeComponent };
315
+ //# sourceMappingURL=trismegisto-spine-auto-service-home.component-rQ3dIGIE.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trismegisto-spine-auto-service-home.component-rQ3dIGIE.mjs","sources":["../../../projects/ui/auto-service/src/home/home-shortcuts.ts","../../../projects/ui/auto-service/src/home/home.component.ts","../../../projects/ui/auto-service/src/home/home.component.html"],"sourcesContent":["import { Route } from '@trismegisto/spine';\nimport {\n AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_NEWS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_RATINGS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_SUGGESTIONS_TEMPLATE_ROUTER_ID,\n} from '../auto-service.tokens';\n\n/** A page the person can open, with the labels of the menus above it. */\nexport interface HomeDestination {\n readonly id: string;\n readonly label: string;\n readonly icon: string | null;\n readonly redirect: string;\n readonly trail: readonly string[];\n}\n\n/** A large entry tile on the home screen. */\nexport interface HomeShortcut extends HomeDestination {\n readonly screens: number;\n readonly privileged: boolean;\n}\n\n// Above this many tiles a grid stops being scannable at a glance.\nconst MAX_TILES = 12;\n\nconst ADMIN_TEMPLATES = new Set(\n [\n AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID,\n // Novidades, Sugestões e Avaliações também são de quem administra o client (migration 0036 do argos).\n AUTO_SERVICE_NEWS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_SUGGESTIONS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_RATINGS_TEMPLATE_ROUTER_ID,\n ].map((id) => id.toLowerCase()),\n);\nconst HOME_TEMPLATE = AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID.toLowerCase();\n\nfunction childrenOf(node: Route): Route[] {\n return (node.routes ?? []).filter((child): child is Route => child != null);\n}\n\nfunction labelOf(node: Route): string {\n return (node.route_display_name ?? node.route_name ?? '').trim();\n}\n\nfunction identityOf(node: Route): string {\n return node.route_redirect || `${node.route_type ?? 'node'}:${node.routed_id}`;\n}\n\nfunction templateOf(node: Route): string {\n return (node.source_template_router_id ?? '').toLowerCase();\n}\n\nexport function isPage(node: Route): boolean {\n return !!node.route_redirect && (node.route_type === 'page' || childrenOf(node).length === 0);\n}\n\nexport function isHome(node: Route): boolean {\n return templateOf(node) === HOME_TEMPLATE || (isPage(node) && node.route_path === 'home');\n}\n\nexport function modules(tree: readonly Route[]): readonly Route[] {\n return tree.filter((node) => node != null && !isHome(node));\n}\n\nexport function systemAreas(tree: readonly Route[]): readonly Route[] {\n return tree.filter((node) => node != null && isHome(node));\n}\n\nexport function destinations(tree: readonly Route[], trail: readonly string[] = []): readonly HomeDestination[] {\n const found: HomeDestination[] = [];\n const walk = (nodes: readonly Route[], path: readonly string[]) => {\n for (const node of nodes) {\n if (node == null) continue;\n if (isPage(node)) {\n found.push({ id: identityOf(node), label: labelOf(node), icon: node.route_icon ?? null, redirect: node.route_redirect!, trail: path });\n }\n const kids = childrenOf(node);\n if (kids.length) walk(kids, [...path, labelOf(node)]);\n }\n };\n walk(tree, trail);\n return found;\n}\n\nexport function trailFor(tree: readonly Route[], redirect: string): readonly string[] | null {\n return destinations(tree).find((d) => d.redirect === redirect)?.trail ?? null;\n}\n\nfunction isPrivileged(node: Route): boolean {\n return ADMIN_TEMPLATES.has(templateOf(node)) || childrenOf(node).some(isPrivileged);\n}\n\ninterface Candidate {\n readonly node: Route;\n readonly trail: readonly string[];\n readonly privileged: boolean;\n}\n\nfunction navigableChildren(node: Route): Route[] {\n return childrenOf(node).filter((child) => isPage(child) || destinations([child]).length > 0);\n}\n\n/** Replaces each candidate by its navigable children; `null` when nobody has any. */\nfunction descend(level: readonly Candidate[]): Candidate[] | null {\n let changed = false;\n const next = level.flatMap((item) => {\n const kids = navigableChildren(item.node);\n if (!kids.length) return [item];\n changed = true;\n const trail = [...item.trail, labelOf(item.node)];\n return kids.map((node) => ({ node, trail, privileged: item.privileged }));\n });\n return changed ? next : null;\n}\n\nfunction toShortcut({ node, trail, privileged }: Candidate): HomeShortcut {\n const below = destinations([node]);\n return {\n id: identityOf(node),\n label: labelOf(node),\n icon: node.route_icon ?? null,\n redirect: node.route_redirect || below[0]?.redirect || '',\n trail,\n screens: below.length - (isPage(node) ? 1 : 0),\n privileged,\n };\n}\n\n/**\n * Picks the shallowest level of the tree where there is a real choice to make: a single item is\n * skipped over, a pair is opened up while the result still fits the grid, three or more stay put.\n */\nexport function entryShortcuts(tree: readonly Route[]): readonly HomeShortcut[] {\n let level: readonly Candidate[] = modules(tree).map((node) => ({ node, trail: [], privileged: isPrivileged(node) }));\n while (level.length > 0 && level.length < 3) {\n const next = descend(level);\n if (!next) break;\n if (level.length === 2 && next.length > MAX_TILES) break;\n level = next;\n }\n return level.map(toShortcut);\n}\n","import { ChangeDetectionStrategy, Component, Injectable, Injector, computed, effect, inject, signal } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { Router } from '@angular/router';\nimport { AuthorizationService, ViewService } from '@orfeu/angular/core';\nimport { IconButtonComponent, IconComponent, MenuButtonComponent, MenuItem, StatePageComponent } from '@trismegisto/angular';\nimport { formatMessage, pluralMessage } from '@trismegisto/primitives';\nimport { QuickAccessItem, Route, SPINE_MESSAGES, SpineQuickAccessService, spineRoutesOf } from '@trismegisto/spine';\nimport { ShortcutService, SpineDocsService, SpineInitialSetupService, SpineNewsService, SpineSettingsService } from '@trismegisto/spine/layout';\nimport { map } from 'rxjs';\nimport { HomeShortcut, destinations, entryShortcuts, modules, systemAreas, trailFor } from './home-shortcuts';\n\n/** Remembers, for the whole app load, that the home screen already jumped to the only screen. */\n@Injectable({ providedIn: 'root' })\nexport class HomeAutoRedirect {\n done = false;\n}\n\ninterface RecentRow extends QuickAccessItem {\n readonly where: string;\n readonly when: string;\n}\n\n/** A top-level module in \"Explore systems\": what is inside it and where it starts. */\ninterface SystemCard {\n readonly id: string;\n readonly label: string;\n readonly icon: string;\n /** Descrição do nó no menu (vazia = sem linha). */\n readonly description: string;\n /** \"13 telas\", na etiqueta ao lado do nome. */\n readonly count: string;\n readonly chips: readonly string[];\n readonly redirect: string;\n}\n\nconst TRAIL_SEPARATOR = ' › ';\n// Recentes e favoritos lado a lado mostram o mesmo número de linhas; o resto fica no \"Ver tudo\".\nconst RECENT_ROWS = 5;\nconst FAVORITE_ROWS = 5;\nconst NEWS_ROWS = 2;\nconst SYSTEM_CHIPS = 3;\nconst MINUTE = 60_000;\nconst HOUR = 60 * MINUTE;\nconst DAY = 24 * HOUR;\n\nlet instances = 0;\n\nfunction cleanTree(view: Parameters<typeof spineRoutesOf>[0]): Route[] {\n return spineRoutesOf(view).filter((node): node is Route => node != null);\n}\n\nfunction pathOf(redirect: string | null | undefined): string {\n return (redirect ?? '').split(/[?#]/)[0];\n}\n\nfunction labelOf(node: Route): string {\n return (node.route_display_name ?? node.route_name ?? '').trim();\n}\n\n@Component({\n selector: 'home',\n imports: [NgTemplateOutlet, IconButtonComponent, IconComponent, MenuButtonComponent, StatePageComponent],\n templateUrl: './home.component.html',\n styleUrl: './home.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class HomeComponent {\n private readonly router = inject(Router);\n private readonly injector = inject(Injector);\n private readonly quickAccess = inject(SpineQuickAccessService);\n private readonly docs = inject(SpineDocsService);\n private readonly settings = inject(SpineSettingsService);\n private readonly news = inject(SpineNewsService);\n private readonly autoRedirect = inject(HomeAutoRedirect);\n private readonly messages = inject(SPINE_MESSAGES);\n protected readonly t = this.messages.home;\n protected readonly uid = `home-${++instances}`;\n\n private readonly view = inject(ViewService);\n private readonly tree = toSignal(this.view.currentView$.pipe(map(cleanTree)), {\n initialValue: cleanTree(this.view.currentView),\n });\n\n /** \"Bom dia, Lucas\": the first name from the session, the salutation from the hour the page was opened. */\n protected readonly greeting = this.greet(inject(AuthorizationService, { optional: true })?.profile?.name);\n\n private readonly workDestinations = computed(() => destinations(modules(this.tree())));\n protected readonly noAccess = computed(() => this.tree().length > 0 && modules(this.tree()).length === 0);\n protected readonly areas = computed(() => systemAreas(this.tree()));\n\n private readonly shortcuts = computed(() => entryShortcuts(this.tree()));\n protected readonly workTiles = computed(() => this.shortcuts().filter((s) => !s.privileged));\n protected readonly adminTiles = computed(() => this.shortcuts().filter((s) => s.privileged));\n protected readonly descended = computed(() => this.shortcuts().some((s) => s.trail.length > 0));\n\n /**\n * Screens the current badge can open (every page in the tree, the start pages included). Recents and favorites\n * are filtered by it: switching badges hides what the new one cannot reach, without forgetting it.\n */\n private readonly reachable = computed(() => {\n const tree = this.tree();\n return new Set([...destinations(tree).map((d) => pathOf(d.redirect)), ...systemAreas(tree).map((a) => pathOf(a.route_redirect))]);\n });\n private readonly canOpen = (item: QuickAccessItem) => this.reachable().has(pathOf(item.redirect));\n\n /** Top-level modules with something inside: their first sub-items as chips and the first screen to open. */\n protected readonly systems = computed<readonly SystemCard[]>(() =>\n modules(this.tree()).flatMap((node) => {\n const children = (node.routes ?? []).filter((child): child is Route => child != null);\n const screens = destinations([node]);\n if (children.length < 2 || screens.length === 0) return [];\n return [\n {\n id: node.routed_id,\n label: labelOf(node),\n icon: node.route_icon || 'grid-layout',\n description: (node.description ?? '').trim(),\n count: pluralMessage(this.t.screenCount, screens.length, this.messages.meta.locale),\n chips: children.slice(0, SYSTEM_CHIPS).map(labelOf).filter(Boolean),\n redirect: node.route_redirect || screens[0].redirect,\n },\n ];\n }),\n );\n\n protected readonly showAllRecents = signal(false);\n protected readonly showAllFavorites = signal(false);\n\n protected readonly favorites = computed(() => this.quickAccess.favorites().filter(this.canOpen));\n protected readonly visibleFavorites = computed(() =>\n this.showAllFavorites() ? this.favorites() : this.favorites().slice(0, FAVORITE_ROWS),\n );\n protected readonly moreFavorites = computed(() => this.favorites().length > FAVORITE_ROWS);\n\n private readonly allRecents = computed<readonly RecentRow[]>(() => {\n const now = Date.now();\n return this.quickAccess\n .recents()\n .filter(this.canOpen)\n .map((item) => ({ ...item, where: this.whereOf(item), when: this.whenOf(item, now) }));\n });\n protected readonly recentRows = computed(() =>\n this.showAllRecents() ? this.allRecents() : this.allRecents().slice(0, RECENT_ROWS),\n );\n protected readonly moreRecents = computed(() => this.allRecents().length > RECENT_ROWS);\n\n protected readonly newsRows = computed(() => this.news.highlights().slice(0, NEWS_ROWS));\n protected readonly recentMenu: readonly MenuItem[] = [{ id: 'forget', label: this.t.forgetRecent, icon: 'times' }];\n\n constructor() {\n effect(() => {\n const only = this.workDestinations();\n if (only.length !== 1 || this.autoRedirect.done) return;\n this.autoRedirect.done = true;\n void this.router.navigateByUrl(only[0].redirect, { replaceUrl: true });\n });\n }\n\n protected caption(tile: HomeShortcut): string {\n if (tile.screens > 0) return pluralMessage(this.t.screenCount, tile.screens, this.messages.meta.locale);\n return tile.trail.join(TRAIL_SEPARATOR);\n }\n\n /** Where the screen sits in the menu; empty for a top-level screen (nothing above it to show). */\n protected whereOf(item: QuickAccessItem): string {\n const trail = item.trail ?? trailFor(this.tree(), item.redirect);\n return trail?.join(TRAIL_SEPARATOR) ?? '';\n }\n\n protected isFavorite(id: string): boolean {\n return this.quickAccess.isFavorite(id);\n }\n\n protected favoriteLabel(row: RecentRow): string {\n const template = this.isFavorite(row.id) ? this.t.unfavorite : this.t.favorite;\n return formatMessage(template, { name: row.displayName });\n }\n\n protected toggleFavorite(row: RecentRow): void {\n this.quickAccess.toggleFavorite({\n id: row.id,\n icon: row.icon,\n displayName: row.displayName,\n redirect: row.redirect,\n trail: row.trail ?? trailFor(this.tree(), row.redirect) ?? [],\n });\n }\n\n protected recentMenuLabel(row: RecentRow): string {\n return formatMessage(this.t.recentActions, { name: row.displayName });\n }\n\n protected onRecentMenu(row: RecentRow, action: string): void {\n if (action === 'forget') this.quickAccess.forgetRecent(row.id);\n }\n\n protected go(redirect: string | null | undefined): void {\n if (redirect) void this.router.navigateByUrl(redirect);\n }\n\n protected openSettings(): void {\n this.settings.open();\n }\n\n protected openDocs(): void {\n this.docs.openIndex();\n }\n\n // Lidos na hora do clique: a home não precisa montar o painel de atalhos nem a configuração inicial para aparecer.\n protected openShortcuts(): void {\n this.injector.get(ShortcutService).help$.next();\n }\n\n protected openEssentials(): void {\n this.injector.get(SpineInitialSetupService).openEssentials();\n }\n\n /** Com `id`: abre o painel já com a novidade aberta; sem, a lista. */\n protected openNews(id?: string): void {\n if (id) this.news.showItem(id);\n else this.news.showAll();\n }\n\n private greet(fullName: string | null | undefined): string {\n const hour = new Date().getHours();\n const greeting = hour < 12 ? this.t.morning : hour < 18 ? this.t.afternoon : this.t.evening;\n const name = (fullName ?? '').trim().split(/\\s+/)[0];\n return name ? formatMessage(this.t.greetingName, { greeting, name }) : greeting;\n }\n\n /** \"Há 12 min\", \"Ontem\", \"Há 2 dias\" (Intl, no idioma do catálogo); vazio para item gravado sem horário. */\n private whenOf(item: QuickAccessItem, now: number): string {\n if (!item.visitedAt) return '';\n const locale = this.messages.meta.locale;\n const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'short' });\n const ago = Math.max(0, now - item.visitedAt);\n const text =\n ago < MINUTE\n ? rtf.format(0, 'second')\n : ago < HOUR\n ? rtf.format(-Math.floor(ago / MINUTE), 'minute')\n : ago < DAY\n ? rtf.format(-Math.floor(ago / HOUR), 'hour')\n : rtf.format(-Math.floor(ago / DAY), 'day');\n return text.charAt(0).toLocaleUpperCase(locale) + text.slice(1);\n }\n}\n","@if (noAccess()) {\n <hm-state-page kind=\"no-rights\" [heading]=\"t.noAccessTitle\" [description]=\"t.noAccessText\" action=\"\" />\n} @else {\n <div class=\"home\">\n <!-- Saudação à esquerda; as novidades, quando houver, ocupam o espaço à direita dela. -->\n <div class=\"hero\">\n <div class=\"hero__text\">\n <p class=\"hero__greeting\">{{ greeting }}</p>\n <p class=\"hero__intro\">{{ t.intro }}</p>\n </div>\n @if (newsRows().length) {\n <!-- Compacto: cabeçalho numa linha e cada novidade numa linha só (título e o começo do texto). -->\n <section class=\"card card--compact hero__news\" [attr.aria-labelledby]=\"uid + '-news'\">\n <div class=\"card__head\">\n <span class=\"card__heading\">\n <h2 class=\"card__title\" [id]=\"uid + '-news'\">{{ t.news }}</h2>\n <span class=\"badge\">{{ t.newsBadge }}</span>\n </span>\n <button type=\"button\" class=\"more\" (click)=\"openNews()\">{{ t.newsAll }}</button>\n </div>\n <ul class=\"list\">\n @for (entry of newsRows(); track entry.id) {\n <li>\n <button type=\"button\" class=\"news\" [title]=\"entry.title + ' — ' + entry.text\" (click)=\"openNews(entry.id)\">\n <span class=\"news__dot\" [class.news__dot--seen]=\"!entry.unseen\" aria-hidden=\"true\"></span>\n <span class=\"news__title\">{{ entry.title }}</span>\n <span class=\"news__body\">{{ entry.text }}</span>\n </button>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n\n <!-- Atalhos de trabalho e privilegiados dividem a mesma linha (cada grupo na proporção dos seus atalhos). -->\n @if (workTiles().length || adminTiles().length) {\n <div class=\"shortcuts\">\n @if (workTiles().length) {\n <section class=\"block shortcuts__group\" [style.--tiles]=\"workTiles().length\" [attr.aria-labelledby]=\"uid + '-work'\">\n <h2 class=\"block__title\" [id]=\"uid + '-work'\">{{ descended() ? t.yourScreens : t.modules }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: workTiles(), admin: false }\" />\n </section>\n }\n @if (adminTiles().length) {\n <section class=\"block shortcuts__group\" [style.--tiles]=\"adminTiles().length\" [attr.aria-labelledby]=\"uid + '-admin'\">\n <h2 class=\"block__title\" [id]=\"uid + '-admin'\">{{ t.privileged }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: adminTiles(), admin: true }\" />\n </section>\n }\n </div>\n }\n\n <!-- Linha 1: o que a pessoa usa (recentes | favoritos). Linha 2: o resto do sistema (módulos | ajuda).\n Sem um dos lados, o outro ocupa a linha inteira. -->\n <div class=\"pair\">\n @if (recentRows().length) {\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-recent'\">\n <div class=\"card__head\">\n <h2 class=\"card__title\" [id]=\"uid + '-recent'\">{{ t.recent }}</h2>\n @if (moreRecents()) {\n <button type=\"button\" class=\"more\" [attr.aria-expanded]=\"showAllRecents()\" (click)=\"showAllRecents.set(!showAllRecents())\">\n {{ showAllRecents() ? t.seeLess : t.seeAll }}\n </button>\n }\n </div>\n <ul class=\"list\" [attr.aria-label]=\"t.recentCaption\">\n @for (item of recentRows(); track item.id) {\n <li class=\"recent\">\n <button type=\"button\" class=\"recent__open\" (click)=\"go(item.redirect)\">\n <hm-icon class=\"recent__icon\" [shape]=\"item.icon || 'grid-layout'\" />\n <span class=\"recent__name\">{{ item.displayName }}</span>\n <span class=\"recent__where\">{{ item.where }}</span>\n @if (item.when) {\n <span class=\"recent__when\">{{ item.when }}</span>\n }\n </button>\n <hm-icon-button\n emphasis=\"subtle\"\n size=\"small\"\n icon=\"heart\"\n [solid]=\"isFavorite(item.id)\"\n [class.heart--on]=\"isFavorite(item.id)\"\n [label]=\"favoriteLabel(item)\"\n (clicked)=\"toggleFavorite(item)\"\n />\n <hm-menu-button\n class=\"recent__menu\"\n icon=\"ellipsis-vertical\"\n shape=\"circle\"\n size=\"small\"\n align=\"end\"\n [showArrow]=\"false\"\n [label]=\"recentMenuLabel(item)\"\n [items]=\"recentMenu\"\n (chosen)=\"onRecentMenu(item, $event)\"\n />\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-fav'\">\n <div class=\"card__head\">\n <h2 class=\"card__title\" [id]=\"uid + '-fav'\">{{ t.favorites }}</h2>\n @if (moreFavorites()) {\n <button type=\"button\" class=\"more\" [attr.aria-expanded]=\"showAllFavorites()\" (click)=\"showAllFavorites.set(!showAllFavorites())\">\n {{ showAllFavorites() ? t.seeLess : t.seeAll }}\n </button>\n }\n </div>\n @if (visibleFavorites().length) {\n <ul class=\"list\">\n @for (fav of visibleFavorites(); track fav.id) {\n <li>\n <button type=\"button\" class=\"row\" (click)=\"go(fav.redirect)\">\n <hm-icon class=\"row__icon\" [shape]=\"fav.icon || 'grid-layout'\" />\n <span class=\"row__name\">{{ fav.displayName }}</span>\n <span class=\"row__meta\">{{ whereOf(fav) }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n } @else {\n <p class=\"card__empty\">{{ t.favoritesEmpty }}</p>\n }\n </section>\n </div>\n\n <div class=\"pair\">\n @if (systems().length) {\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-systems'\">\n <div class=\"card__head card__head--stacked\">\n <h2 class=\"card__title\" [id]=\"uid + '-systems'\">{{ t.explore }}</h2>\n <p class=\"card__intro\">{{ t.exploreIntro }}</p>\n </div>\n <ul class=\"systems\">\n @for (system of systems(); track system.id) {\n <!-- O card inteiro abre o módulo (como os atalhos): uma seta no canto, sem link próprio. -->\n <li class=\"systems__cell\">\n <button type=\"button\" class=\"system\" (click)=\"go(system.redirect)\">\n <span class=\"system__head\">\n <hm-icon class=\"system__icon\" [shape]=\"system.icon\" size=\"lg\" />\n <span class=\"system__name\" [title]=\"system.label\">{{ system.label }}</span>\n <span class=\"badge badge--neutral\">{{ system.count }}</span>\n <hm-icon class=\"system__chevron\" shape=\"angle-right\" size=\"sm\" />\n </span>\n @if (system.description) {\n <span class=\"system__description\">{{ system.description }}</span>\n }\n @if (system.chips.length) {\n <span class=\"system__chips\">\n @for (chip of system.chips; track chip) {\n <span class=\"system__chip\">{{ chip }}</span>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"card\" [attr.aria-labelledby]=\"uid + '-help'\">\n <div class=\"card__head\">\n <h2 class=\"card__title\" [id]=\"uid + '-help'\">{{ t.help }}</h2>\n </div>\n <ul class=\"list\">\n @for (area of areas(); track area.routed_id) {\n <li>\n <button type=\"button\" class=\"row\" (click)=\"go(area.route_redirect)\">\n <hm-icon class=\"row__icon\" [shape]=\"area.route_icon || 'home'\" />\n <span class=\"row__name\">{{ area.route_display_name }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n }\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openDocs()\">\n <hm-icon class=\"row__icon\" shape=\"book\" />\n <span class=\"row__name\">{{ t.systemDocs }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openShortcuts()\">\n <hm-icon class=\"row__icon\" shape=\"keyboard\" />\n <span class=\"row__name\">{{ t.shortcuts }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openEssentials()\">\n <hm-icon class=\"row__icon\" shape=\"help\" />\n <span class=\"row__name\">{{ t.essentials }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n <li>\n <button type=\"button\" class=\"row\" (click)=\"openSettings()\">\n <hm-icon class=\"row__icon\" shape=\"cog\" />\n <span class=\"row__name\">{{ t.settings }}</span>\n <hm-icon class=\"row__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n </ul>\n </section>\n </div>\n </div>\n}\n\n<ng-template #tileGrid let-tiles let-admin=\"admin\">\n <ul class=\"tiles\" [class.tiles--admin]=\"admin\">\n @for (tile of tiles; track tile.id; let i = $index) {\n <li class=\"tiles__cell\" [style.--tile-order]=\"i\">\n <button type=\"button\" class=\"tile\" (click)=\"go(tile.redirect)\">\n <hm-icon class=\"tile__icon\" [shape]=\"tile.icon ?? 'grid-layout'\" size=\"lg\" />\n <span class=\"tile__text\">\n <span class=\"tile__label\">{{ tile.label }}</span>\n @if (caption(tile); as meta) {\n <span class=\"tile__meta\">{{ meta }}</span>\n }\n </span>\n <hm-icon class=\"tile__chevron\" shape=\"angle-right\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n</ng-template>\n"],"names":["labelOf"],"mappings":";;;;;;;;;;;;;AA4BA;AACA,MAAM,SAAS,GAAG,EAAE;AAEpB,MAAM,eAAe,GAAG,IAAI,GAAG,CAC7B;IACE,sCAAsC;IACtC,oCAAoC;IACpC,sCAAsC;IACtC,qCAAqC;IACrC,8CAA8C;;IAE9C,oCAAoC;IACpC,2CAA2C;IAC3C,uCAAuC;AACxC,CAAA,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,CAChC;AACD,MAAM,aAAa,GAAG,oCAAoC,CAAC,WAAW,EAAE;AAExE,SAAS,UAAU,CAAC,IAAW,EAAA;AAC7B,IAAA,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,KAAK,KAAqB,KAAK,IAAI,IAAI,CAAC;AAC7E;AAEA,SAASA,SAAO,CAAC,IAAW,EAAA;AAC1B,IAAA,OAAO,CAAC,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,UAAU,IAAI,EAAE,EAAE,IAAI,EAAE;AAClE;AAEA,SAAS,UAAU,CAAC,IAAW,EAAA;AAC7B,IAAA,OAAO,IAAI,CAAC,cAAc,IAAI,GAAG,IAAI,CAAC,UAAU,IAAI,MAAM,CAAA,CAAA,EAAI,IAAI,CAAC,SAAS,EAAE;AAChF;AAEA,SAAS,UAAU,CAAC,IAAW,EAAA;IAC7B,OAAO,CAAC,IAAI,CAAC,yBAAyB,IAAI,EAAE,EAAE,WAAW,EAAE;AAC7D;AAEM,SAAU,MAAM,CAAC,IAAW,EAAA;IAChC,OAAO,CAAC,CAAC,IAAI,CAAC,cAAc,KAAK,IAAI,CAAC,UAAU,KAAK,MAAM,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAC/F;AAEM,SAAU,MAAM,CAAC,IAAW,EAAA;AAChC,IAAA,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,aAAa,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,KAAK,MAAM,CAAC;AAC3F;AAEM,SAAU,OAAO,CAAC,IAAsB,EAAA;AAC5C,IAAA,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7D;AAEM,SAAU,WAAW,CAAC,IAAsB,EAAA;AAChD,IAAA,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;AAC5D;SAEgB,YAAY,CAAC,IAAsB,EAAE,QAA2B,EAAE,EAAA;IAChF,MAAM,KAAK,GAAsB,EAAE;AACnC,IAAA,MAAM,IAAI,GAAG,CAAC,KAAuB,EAAE,IAAuB,KAAI;AAChE,QAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACxB,IAAI,IAAI,IAAI,IAAI;gBAAE;AAClB,YAAA,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE;AAChB,gBAAA,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAEA,SAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,cAAe,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACxI;AACA,YAAA,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC;YAC7B,IAAI,IAAI,CAAC,MAAM;AAAE,gBAAA,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAEA,SAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD;AACF,IAAA,CAAC;AACD,IAAA,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC;AACjB,IAAA,OAAO,KAAK;AACd;AAEM,SAAU,QAAQ,CAAC,IAAsB,EAAE,QAAgB,EAAA;IAC/D,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,KAAK,IAAI,IAAI;AAC/E;AAEA,SAAS,YAAY,CAAC,IAAW,EAAA;AAC/B,IAAA,OAAO,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC;AACrF;AAQA,SAAS,iBAAiB,CAAC,IAAW,EAAA;AACpC,IAAA,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9F;AAEA;AACA,SAAS,OAAO,CAAC,KAA2B,EAAA;IAC1C,IAAI,OAAO,GAAG,KAAK;IACnB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,KAAI;QAClC,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,IAAI,CAAC;QAC/B,OAAO,GAAG,IAAI;AACd,QAAA,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,EAAEA,SAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;AAC3E,IAAA,CAAC,CAAC;IACF,OAAO,OAAO,GAAG,IAAI,GAAG,IAAI;AAC9B;AAEA,SAAS,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAa,EAAA;IACxD,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO;AACL,QAAA,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC;AACpB,QAAA,KAAK,EAAEA,SAAO,CAAC,IAAI,CAAC;AACpB,QAAA,IAAI,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;AAC7B,QAAA,QAAQ,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,EAAE;QACzD,KAAK;AACL,QAAA,OAAO,EAAE,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9C,UAAU;KACX;AACH;AAEA;;;AAGG;AACG,SAAU,cAAc,CAAC,IAAsB,EAAA;AACnD,IAAA,IAAI,KAAK,GAAyB,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACpH,IAAA,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;AAC3C,QAAA,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC;AAC3B,QAAA,IAAI,CAAC,IAAI;YAAE;QACX,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,SAAS;YAAE;QACnD,KAAK,GAAG,IAAI;IACd;AACA,IAAA,OAAO,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC;AAC9B;;AC3IA;MAEa,gBAAgB,CAAA;AAD7B,IAAA,WAAA,GAAA;QAEE,IAAA,CAAA,IAAI,GAAG,KAAK;AACb,IAAA;+GAFY,gBAAgB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAAhB,IAAA,SAAA,IAAA,CAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,gBAAgB,cADH,MAAM,EAAA,CAAA,CAAA;;4FACnB,gBAAgB,EAAA,UAAA,EAAA,CAAA;kBAD5B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;AAuBlC,MAAM,eAAe,GAAG,KAAK;AAC7B;AACA,MAAM,WAAW,GAAG,CAAC;AACrB,MAAM,aAAa,GAAG,CAAC;AACvB,MAAM,SAAS,GAAG,CAAC;AACnB,MAAM,YAAY,GAAG,CAAC;AACtB,MAAM,MAAM,GAAG,MAAM;AACrB,MAAM,IAAI,GAAG,EAAE,GAAG,MAAM;AACxB,MAAM,GAAG,GAAG,EAAE,GAAG,IAAI;AAErB,IAAI,SAAS,GAAG,CAAC;AAEjB,SAAS,SAAS,CAAC,IAAyC,EAAA;AAC1D,IAAA,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,KAAoB,IAAI,IAAI,IAAI,CAAC;AAC1E;AAEA,SAAS,MAAM,CAAC,QAAmC,EAAA;AACjD,IAAA,OAAO,CAAC,QAAQ,IAAI,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAC1C;AAEA,SAAS,OAAO,CAAC,IAAW,EAAA;AAC1B,IAAA,OAAO,CAAC,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,UAAU,IAAI,EAAE,EAAE,IAAI,EAAE;AAClE;MASa,aAAa,CAAA;AAmFxB,IAAA,WAAA,GAAA;AAlFiB,QAAA,IAAA,CAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AACvB,QAAA,IAAA,CAAA,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;AAC3B,QAAA,IAAA,CAAA,WAAW,GAAG,MAAM,CAAC,uBAAuB,CAAC;AAC7C,QAAA,IAAA,CAAA,IAAI,GAAG,MAAM,CAAC,gBAAgB,CAAC;AAC/B,QAAA,IAAA,CAAA,QAAQ,GAAG,MAAM,CAAC,oBAAoB,CAAC;AACvC,QAAA,IAAA,CAAA,IAAI,GAAG,MAAM,CAAC,gBAAgB,CAAC;AAC/B,QAAA,IAAA,CAAA,YAAY,GAAG,MAAM,CAAC,gBAAgB,CAAC;AACvC,QAAA,IAAA,CAAA,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAC;AAC/B,QAAA,IAAA,CAAA,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI;AACtB,QAAA,IAAA,CAAA,GAAG,GAAG,CAAA,KAAA,EAAQ,EAAE,SAAS,EAAE;AAE7B,QAAA,IAAA,CAAA,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC;AAC1B,QAAA,IAAA,CAAA,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE;YAC5E,YAAY,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;AAC/C,SAAA,CAAC;;QAGiB,IAAA,CAAA,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,oBAAoB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC;AAExF,QAAA,IAAA,CAAA,gBAAgB,GAAG,QAAQ,CAAC,MAAM,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,uFAAC;QACnE,IAAA,CAAA,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,UAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;AACtF,QAAA,IAAA,CAAA,KAAK,GAAG,QAAQ,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,4EAAC;AAElD,QAAA,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,MAAM,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,gFAAC;QACrD,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,WAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;QACzE,IAAA,CAAA,UAAU,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,YAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;QACzE,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,WAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;AAE/F;;;AAGG;AACc,QAAA,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,MAAK;AACzC,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;YACxB,OAAO,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;AACnI,QAAA,CAAC,gFAAC;QACe,IAAA,CAAA,OAAO,GAAG,CAAC,IAAqB,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;;AAG9E,QAAA,IAAA,CAAA,OAAO,GAAG,QAAQ,CAAwB,MAC3D,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,KAAI;YACpC,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,KAAK,KAAqB,KAAK,IAAI,IAAI,CAAC;YACrF,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;AAAE,gBAAA,OAAO,EAAE;YAC1D,OAAO;AACL,gBAAA;oBACE,EAAE,EAAE,IAAI,CAAC,SAAS;AAClB,oBAAA,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC;AACpB,oBAAA,IAAI,EAAE,IAAI,CAAC,UAAU,IAAI,aAAa;oBACtC,WAAW,EAAE,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,EAAE,IAAI,EAAE;oBAC5C,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;AACnF,oBAAA,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;oBACnE,QAAQ,EAAE,IAAI,CAAC,cAAc,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ;AACrD,iBAAA;aACF;QACH,CAAC,CAAC,8EACH;AAEkB,QAAA,IAAA,CAAA,cAAc,GAAG,MAAM,CAAC,KAAK,qFAAC;AAC9B,QAAA,IAAA,CAAA,gBAAgB,GAAG,MAAM,CAAC,KAAK,uFAAC;QAEhC,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,gFAAC;AAC7E,QAAA,IAAA,CAAA,gBAAgB,GAAG,QAAQ,CAAC,MAC7C,IAAI,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,uFACtF;AACkB,QAAA,IAAA,CAAA,aAAa,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,GAAG,aAAa,oFAAC;AAEzE,QAAA,IAAA,CAAA,UAAU,GAAG,QAAQ,CAAuB,MAAK;AAChE,YAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;YACtB,OAAO,IAAI,CAAC;AACT,iBAAA,OAAO;AACP,iBAAA,MAAM,CAAC,IAAI,CAAC,OAAO;AACnB,iBAAA,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAC1F,QAAA,CAAC,iFAAC;AACiB,QAAA,IAAA,CAAA,UAAU,GAAG,QAAQ,CAAC,MACvC,IAAI,CAAC,cAAc,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,iFACpF;AACkB,QAAA,IAAA,CAAA,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,GAAG,WAAW,kFAAC;QAEpE,IAAA,CAAA,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,+EAAC;QACrE,IAAA,CAAA,UAAU,GAAwB,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAGhH,MAAM,CAAC,MAAK;AACV,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,EAAE;YACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI;gBAAE;AACjD,YAAA,IAAI,CAAC,YAAY,CAAC,IAAI,GAAG,IAAI;AAC7B,YAAA,KAAK,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACxE,QAAA,CAAC,CAAC;IACJ;AAEU,IAAA,OAAO,CAAC,IAAkB,EAAA;AAClC,QAAA,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC;YAAE,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QACvG,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC;IACzC;;AAGU,IAAA,OAAO,CAAC,IAAqB,EAAA;AACrC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC;QAChE,OAAO,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE;IAC3C;AAEU,IAAA,UAAU,CAAC,EAAU,EAAA;QAC7B,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;IACxC;AAEU,IAAA,aAAa,CAAC,GAAc,EAAA;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ;AAC9E,QAAA,OAAO,aAAa,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC;IAC3D;AAEU,IAAA,cAAc,CAAC,GAAc,EAAA;AACrC,QAAA,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC;YAC9B,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,QAAQ,EAAE,GAAG,CAAC,QAAQ;AACtB,YAAA,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE;AAC9D,SAAA,CAAC;IACJ;AAEU,IAAA,eAAe,CAAC,GAAc,EAAA;AACtC,QAAA,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC;IACvE;IAEU,YAAY,CAAC,GAAc,EAAE,MAAc,EAAA;QACnD,IAAI,MAAM,KAAK,QAAQ;YAAE,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;IAChE;AAEU,IAAA,EAAE,CAAC,QAAmC,EAAA;AAC9C,QAAA,IAAI,QAAQ;YAAE,KAAK,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC;IACxD;IAEU,YAAY,GAAA;AACpB,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;IACtB;IAEU,QAAQ,GAAA;AAChB,QAAA,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;IACvB;;IAGU,aAAa,GAAA;AACrB,QAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE;IACjD;IAEU,cAAc,GAAA;QACtB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,cAAc,EAAE;IAC9D;;AAGU,IAAA,QAAQ,CAAC,EAAW,EAAA;AAC5B,QAAA,IAAI,EAAE;AAAE,YAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;;AACzB,YAAA,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;IAC1B;AAEQ,IAAA,KAAK,CAAC,QAAmC,EAAA;QAC/C,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,QAAQ,EAAE;AAClC,QAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO;AAC3F,QAAA,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACpD,OAAO,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,GAAG,QAAQ;IACjF;;IAGQ,MAAM,CAAC,IAAqB,EAAE,GAAW,EAAA;QAC/C,IAAI,CAAC,IAAI,CAAC,SAAS;AAAE,YAAA,OAAO,EAAE;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM;AACxC,QAAA,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACpF,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;AAC7C,QAAA,MAAM,IAAI,GACR,GAAG,GAAG;cACF,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ;cACtB,GAAG,GAAG;AACN,kBAAE,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,EAAE,QAAQ;kBAC9C,GAAG,GAAG;AACN,sBAAE,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,MAAM;AAC5C,sBAAE,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,KAAK,CAAC;AACnD,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACjE;+GAnLW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;mGAAb,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,MAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECnE1B,o7VAwOA,EAAA,MAAA,EAAA,CAAA,q+PAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,ED1KY,gBAAgB,EAAA,QAAA,EAAA,oBAAA,EAAA,MAAA,EAAA,CAAA,yBAAA,EAAA,kBAAA,EAAA,0BAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,mBAAmB,EAAA,QAAA,EAAA,gBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EAAA,eAAA,EAAA,UAAA,EAAA,QAAA,EAAA,MAAA,EAAA,UAAA,EAAA,QAAA,EAAA,SAAA,EAAA,cAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,aAAa,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,WAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,EAAA,QAAA,EAAA,MAAA,EAAA,SAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,mBAAmB,EAAA,QAAA,EAAA,gBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EAAA,WAAA,EAAA,OAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,EAAA,cAAA,EAAA,UAAA,CAAA,EAAA,OAAA,EAAA,CAAA,QAAA,EAAA,eAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,kBAAkB,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,SAAA,EAAA,aAAA,EAAA,QAAA,EAAA,YAAA,CAAA,EAAA,OAAA,EAAA,CAAA,iBAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAK5F,aAAa,EAAA,UAAA,EAAA,CAAA;kBAPzB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,MAAM,EAAA,OAAA,EACP,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,aAAa,EAAE,mBAAmB,EAAE,kBAAkB,CAAC,EAAA,eAAA,EAGvF,uBAAuB,CAAC,MAAM,EAAA,QAAA,EAAA,o7VAAA,EAAA,MAAA,EAAA,CAAA,q+PAAA,CAAA,EAAA;;;"}
@@ -0,0 +1,46 @@
1
+ // Especificação técnica da tela Início do auto-serviço (`RouteDocument.loadTechnical`), em Markdown. Módulo próprio,
2
+ // carregado só pelo `import()` de `AUTO_SERVICE_DOCUMENTS`: vira um chunk à parte no FESM e fica fora do
3
+ // bundle inicial. Não importe estaticamente nem reexporte no `public-api.ts`.
4
+ var home_technical = `# Início — especificação técnica
5
+
6
+ Tela \`HomeComponent\` do \`@trismegisto/spine/auto-service\`, ligada ao router de template
7
+ \`AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID\` (\`6a9a13db-…\`, página de topo e pública do template \`auto-service\`).
8
+
9
+ ## Origem dos dados
10
+
11
+ | Dado | Fonte |
12
+ | --- | --- |
13
+ | Atalhos, áreas e "Onde fica" | Árvore da view do Orfeu (\`ViewService.currentView$\`, via \`spineRoutesOf\`); nenhuma chamada HTTP própria |
14
+ | Saudação | Primeiro nome de \`AuthorizationService.profile.name\`; "Bom dia" até 12h, "Boa tarde" até 18h, "Boa noite" depois |
15
+ | Continuar de onde parou e favoritos | \`SpineQuickAccessService\` (\`localStorage\` por usuário: \`SpineUserStorage\`, chave \`<clientId>_nav_*_<sub>\`; recentes apagados no logout). O horário é o \`visitedAt\` gravado a cada visita, formatado por \`Intl.RelativeTimeFormat\` |
16
+ | Novidades | \`SpineNewsService.highlights\` (as 2 primeiras de \`argos-admin /me/news\`, lidas na entrada: título e resumo); clicar numa abre o painel já com ela aberta (\`showItem(id)\`), **Ver todas** chama \`showAll()\` |
17
+ | Configurações | \`SpineSettingsService.open()\` |
18
+ | Documentação do sistema | \`SpineDocsService.openIndex()\` |
19
+ | Atalhos do teclado e o essencial | \`ShortcutService.help$\` e \`SpineInitialSetupService.openEssentials()\` (lidos só no clique) |
20
+
21
+ ## Regras
22
+
23
+ - **Módulos × áreas:** nó de topo que é o próprio Início (template \`6a9a13db-…\` ou página com \`route_path\` \`home\`)
24
+ vai para **Ajuda e recursos**; os demais são módulos.
25
+ - **Explorar sistemas:** módulo de topo com 2 ou mais filhos; descrição do nó (\`description\`) ou a contagem de telas,
26
+ os 3 primeiros filhos como etiquetas e a primeira tela (\`route_redirect\` do módulo ou o 1º destino).
27
+ - **Atalhos** (\`entryShortcuts\`): parte dos módulos e desce um nível enquanto houver menos de 3 candidatos e algum
28
+ deles tiver filhos navegáveis; com 2 candidatos, não desce se o próximo nível passar de 12 atalhos.
29
+ - **Acesso privilegiado:** atalho cujo nó (ou algum descendente) vem dos templates de Grupos, Aplicações, Cliente
30
+ ou Auditoria (\`ad100001…\` a \`ad100004…\`).
31
+ - **Recentes e favoritos:** filtrados pelas telas que a view atual alcança (\`destinations\` da árvore + áreas de
32
+ topo, comparando o caminho sem query/hash); trocar de crachá esconde o que o novo não abre, sem apagar. Recentes:
33
+ os 4 primeiros (**Ver tudo** mostra os 10); favoritos: os 5 primeiros (**Ver tudo** mostra todos).
34
+ - **Abas:** \`SpineWorkspaceNavService.reconcileWithView\` fecha, a cada view nova, as abas de telas fora dela (o
35
+ painel que ficaria vazio mostra o Início).
36
+ - **Leiaute:** saudação com as novidades à direita; atalhos de trabalho e privilegiados na mesma linha (proporcionais
37
+ ao número de atalhos); depois duas linhas 2:1 — recentes | favoritos e módulos | ajuda — que viram uma coluna só
38
+ quando o painel tem menos de 56rem (container query: o workspace divide a área).
39
+ - **Redirecionamento:** com exatamente 1 destino de trabalho, navega para ele com \`replaceUrl\`, uma vez por
40
+ carregamento do app (\`HomeAutoRedirect\`, \`providedIn: 'root'\`).
41
+ - **Sem acesso:** árvore com nós, mas nenhum módulo → \`hm-state-page\` \`no-rights\`.
42
+ - Textos em \`SpineMessages.home\` (pt-BR/en/es).
43
+ `;
44
+
45
+ export { home_technical as default };
46
+ //# sourceMappingURL=trismegisto-spine-auto-service-home.technical-Ce7oYRdg.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trismegisto-spine-auto-service-home.technical-Ce7oYRdg.mjs","sources":["../../../projects/ui/auto-service/src/docs/home.technical.ts"],"sourcesContent":["// Especificação técnica da tela Início do auto-serviço (`RouteDocument.loadTechnical`), em Markdown. Módulo próprio,\n// carregado só pelo `import()` de `AUTO_SERVICE_DOCUMENTS`: vira um chunk à parte no FESM e fica fora do\n// bundle inicial. Não importe estaticamente nem reexporte no `public-api.ts`.\nexport default `# Início — especificação técnica\n\nTela \\`HomeComponent\\` do \\`@trismegisto/spine/auto-service\\`, ligada ao router de template\n\\`AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID\\` (\\`6a9a13db-…\\`, página de topo e pública do template \\`auto-service\\`).\n\n## Origem dos dados\n\n| Dado | Fonte |\n| --- | --- |\n| Atalhos, áreas e \"Onde fica\" | Árvore da view do Orfeu (\\`ViewService.currentView$\\`, via \\`spineRoutesOf\\`); nenhuma chamada HTTP própria |\n| Saudação | Primeiro nome de \\`AuthorizationService.profile.name\\`; \"Bom dia\" até 12h, \"Boa tarde\" até 18h, \"Boa noite\" depois |\n| Continuar de onde parou e favoritos | \\`SpineQuickAccessService\\` (\\`localStorage\\` por usuário: \\`SpineUserStorage\\`, chave \\`<clientId>_nav_*_<sub>\\`; recentes apagados no logout). O horário é o \\`visitedAt\\` gravado a cada visita, formatado por \\`Intl.RelativeTimeFormat\\` |\n| Novidades | \\`SpineNewsService.highlights\\` (as 2 primeiras de \\`argos-admin /me/news\\`, lidas na entrada: título e resumo); clicar numa abre o painel já com ela aberta (\\`showItem(id)\\`), **Ver todas** chama \\`showAll()\\` |\n| Configurações | \\`SpineSettingsService.open()\\` |\n| Documentação do sistema | \\`SpineDocsService.openIndex()\\` |\n| Atalhos do teclado e o essencial | \\`ShortcutService.help$\\` e \\`SpineInitialSetupService.openEssentials()\\` (lidos só no clique) |\n\n## Regras\n\n- **Módulos × áreas:** nó de topo que é o próprio Início (template \\`6a9a13db-…\\` ou página com \\`route_path\\` \\`home\\`)\n vai para **Ajuda e recursos**; os demais são módulos.\n- **Explorar sistemas:** módulo de topo com 2 ou mais filhos; descrição do nó (\\`description\\`) ou a contagem de telas,\n os 3 primeiros filhos como etiquetas e a primeira tela (\\`route_redirect\\` do módulo ou o 1º destino).\n- **Atalhos** (\\`entryShortcuts\\`): parte dos módulos e desce um nível enquanto houver menos de 3 candidatos e algum\n deles tiver filhos navegáveis; com 2 candidatos, não desce se o próximo nível passar de 12 atalhos.\n- **Acesso privilegiado:** atalho cujo nó (ou algum descendente) vem dos templates de Grupos, Aplicações, Cliente\n ou Auditoria (\\`ad100001…\\` a \\`ad100004…\\`).\n- **Recentes e favoritos:** filtrados pelas telas que a view atual alcança (\\`destinations\\` da árvore + áreas de\n topo, comparando o caminho sem query/hash); trocar de crachá esconde o que o novo não abre, sem apagar. Recentes:\n os 4 primeiros (**Ver tudo** mostra os 10); favoritos: os 5 primeiros (**Ver tudo** mostra todos).\n- **Abas:** \\`SpineWorkspaceNavService.reconcileWithView\\` fecha, a cada view nova, as abas de telas fora dela (o\n painel que ficaria vazio mostra o Início).\n- **Leiaute:** saudação com as novidades à direita; atalhos de trabalho e privilegiados na mesma linha (proporcionais\n ao número de atalhos); depois duas linhas 2:1 — recentes | favoritos e módulos | ajuda — que viram uma coluna só\n quando o painel tem menos de 56rem (container query: o workspace divide a área).\n- **Redirecionamento:** com exatamente 1 destino de trabalho, navega para ele com \\`replaceUrl\\`, uma vez por\n carregamento do app (\\`HomeAutoRedirect\\`, \\`providedIn: 'root'\\`).\n- **Sem acesso:** árvore com nós, mas nenhum módulo → \\`hm-state-page\\` \\`no-rights\\`.\n- Textos em \\`SpineMessages.home\\` (pt-BR/en/es).\n`;\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA,qBAAe,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCd;;"}