@praxisui/page-builder 3.0.0-beta.5 → 3.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/praxisui-page-builder.mjs +1565 -52
- package/fesm2022/praxisui-page-builder.mjs.map +1 -1
- package/index.d.ts +62 -1
- package/package.json +3 -3
|
@@ -9,7 +9,7 @@ import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
|
|
9
9
|
import * as i3 from '@angular/material/icon';
|
|
10
10
|
import { MatIconModule } from '@angular/material/icon';
|
|
11
11
|
import * as i1$1 from '@praxisui/core';
|
|
12
|
-
import { PraxisIconDirective, BUILTIN_SHELL_PRESETS, deepMerge, generateId, GlobalActionService, buildPageKey, ASYNC_CONFIG_STORAGE, TABLE_CONFIG_EDITOR, STEPPER_CONFIG_EDITOR, SETTINGS_PANEL_BRIDGE, DynamicWidgetLoaderDirective, WidgetShellComponent } from '@praxisui/core';
|
|
12
|
+
import { PraxisIconDirective, BUILTIN_SHELL_PRESETS, BUILTIN_PAGE_LAYOUT_PRESETS, BUILTIN_PAGE_THEME_PRESETS, deepMerge, generateId, GlobalActionService, buildPageKey, ASYNC_CONFIG_STORAGE, TABLE_CONFIG_EDITOR, STEPPER_CONFIG_EDITOR, SETTINGS_PANEL_BRIDGE, DynamicWidgetLoaderDirective, WidgetShellComponent } from '@praxisui/core';
|
|
13
13
|
export { WidgetShellComponent } from '@praxisui/core';
|
|
14
14
|
import * as i7 from '@angular/material/tooltip';
|
|
15
15
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
@@ -480,6 +480,7 @@ class ConnectionBuilderComponent {
|
|
|
480
480
|
// Signals
|
|
481
481
|
connections = signal([], ...(ngDevMode ? [{ debugName: "connections" }] : []));
|
|
482
482
|
selectedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "selectedIndex" }] : []));
|
|
483
|
+
stateOptions = signal([], ...(ngDevMode ? [{ debugName: "stateOptions" }] : []));
|
|
483
484
|
// Derived lists
|
|
484
485
|
filteredConnections = computed(() => this.applyFilters(this.connections()), ...(ngDevMode ? [{ debugName: "filteredConnections" }] : []));
|
|
485
486
|
groupedConnections = computed(() => this.applyGrouping(this.filteredConnections()), ...(ngDevMode ? [{ debugName: "groupedConnections" }] : []));
|
|
@@ -492,6 +493,7 @@ class ConnectionBuilderComponent {
|
|
|
492
493
|
const p = this.parsePage(this.page);
|
|
493
494
|
const ws = this.widgets || p?.widgets || [];
|
|
494
495
|
this.widgets = ws;
|
|
496
|
+
this.refreshStateOptions(p);
|
|
495
497
|
const conns = [...(p?.connections || [])];
|
|
496
498
|
this.connections.set(conns);
|
|
497
499
|
this.originalSnapshot = JSON.stringify(conns);
|
|
@@ -500,6 +502,7 @@ class ConnectionBuilderComponent {
|
|
|
500
502
|
if (changes['page'] || changes['widgets']) {
|
|
501
503
|
const p = this.parsePage(this.page);
|
|
502
504
|
this.widgets = this.widgets || p?.widgets || [];
|
|
505
|
+
this.refreshStateOptions(p);
|
|
503
506
|
const next = [...(p?.connections || [])];
|
|
504
507
|
this.connections.set(next);
|
|
505
508
|
this.originalSnapshot = JSON.stringify(next);
|
|
@@ -528,10 +531,14 @@ class ConnectionBuilderComponent {
|
|
|
528
531
|
return this.isWidgetTarget(c.to) ? `${c.to.widget}.${c.to.input}` : `state:${c.to.state}`;
|
|
529
532
|
}
|
|
530
533
|
fromFriendly(c) {
|
|
531
|
-
return this.isWidgetSource(c.from)
|
|
534
|
+
return this.isWidgetSource(c.from)
|
|
535
|
+
? `${this.widgetFriendlyNameForKey(c.from.widget)}.${c.from.output}`
|
|
536
|
+
: `state:${this.stateOptionLabel(c.from.state)}`;
|
|
532
537
|
}
|
|
533
538
|
toFriendly(c) {
|
|
534
|
-
return this.isWidgetTarget(c.to)
|
|
539
|
+
return this.isWidgetTarget(c.to)
|
|
540
|
+
? `${this.widgetFriendlyNameForKey(c.to.widget)}.${c.to.input}`
|
|
541
|
+
: `state:${this.stateOptionLabel(c.to.state)}`;
|
|
535
542
|
}
|
|
536
543
|
outputDescription(c) {
|
|
537
544
|
if (!this.isWidgetSource(c.from))
|
|
@@ -557,6 +564,44 @@ class ConnectionBuilderComponent {
|
|
|
557
564
|
const index = this.selectedIndex();
|
|
558
565
|
return index >= 0 ? this.connections()[index] : undefined;
|
|
559
566
|
}
|
|
567
|
+
selectedSourceKind() {
|
|
568
|
+
const selected = this.selectedConnection();
|
|
569
|
+
return selected && this.isWidgetSource(selected.from) ? 'widget' : 'state';
|
|
570
|
+
}
|
|
571
|
+
setSelectedSourceKind(kind) {
|
|
572
|
+
const selected = this.selectedConnection();
|
|
573
|
+
if (!selected)
|
|
574
|
+
return;
|
|
575
|
+
if (kind === 'widget' && !this.isWidgetSource(selected.from)) {
|
|
576
|
+
selected.from = {
|
|
577
|
+
widget: this.widgets?.[0]?.key || '',
|
|
578
|
+
output: this.availableOutputsForWidget(this.widgets?.[0]?.key || '')[0] || '',
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
if (kind === 'state' && this.isWidgetSource(selected.from)) {
|
|
582
|
+
selected.from = { state: this.stateOptions()[0]?.value || '' };
|
|
583
|
+
}
|
|
584
|
+
this.connections.set([...this.connections()]);
|
|
585
|
+
}
|
|
586
|
+
selectedTargetKind() {
|
|
587
|
+
const selected = this.selectedConnection();
|
|
588
|
+
return selected && this.isWidgetTarget(selected.to) ? 'widget' : 'state';
|
|
589
|
+
}
|
|
590
|
+
setSelectedTargetKind(kind) {
|
|
591
|
+
const selected = this.selectedConnection();
|
|
592
|
+
if (!selected)
|
|
593
|
+
return;
|
|
594
|
+
if (kind === 'widget' && !this.isWidgetTarget(selected.to)) {
|
|
595
|
+
selected.to = {
|
|
596
|
+
widget: this.widgets?.[0]?.key || '',
|
|
597
|
+
input: this.availableInputsForWidget(this.widgets?.[0]?.key || '')[0] || '',
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
if (kind === 'state' && this.isWidgetTarget(selected.to)) {
|
|
601
|
+
selected.to = { state: this.stateOptions()[0]?.value || '' };
|
|
602
|
+
}
|
|
603
|
+
this.connections.set([...this.connections()]);
|
|
604
|
+
}
|
|
560
605
|
selectedSourcePrimaryValue() {
|
|
561
606
|
const selected = this.selectedConnection();
|
|
562
607
|
if (!selected)
|
|
@@ -629,6 +674,29 @@ class ConnectionBuilderComponent {
|
|
|
629
674
|
const selected = this.selectedConnection();
|
|
630
675
|
return !!selected && this.isWidgetTarget(selected.to);
|
|
631
676
|
}
|
|
677
|
+
availableOutputsForSelectedWidget() {
|
|
678
|
+
return this.availableOutputsForWidget(this.selectedSourcePrimaryValue());
|
|
679
|
+
}
|
|
680
|
+
availableInputsForSelectedWidget() {
|
|
681
|
+
return this.availableInputsForWidget(this.selectedTargetPrimaryValue());
|
|
682
|
+
}
|
|
683
|
+
availableStateOptions() {
|
|
684
|
+
return this.stateOptions();
|
|
685
|
+
}
|
|
686
|
+
stateOptionLabel(path) {
|
|
687
|
+
return this.stateOptions().find((option) => option.value === path)?.label || path;
|
|
688
|
+
}
|
|
689
|
+
stateOptionIcon(path) {
|
|
690
|
+
return this.stateOptions().find((option) => option.value === path)?.kind === 'derived'
|
|
691
|
+
? 'function'
|
|
692
|
+
: 'account_tree';
|
|
693
|
+
}
|
|
694
|
+
widgetOptions() {
|
|
695
|
+
return (this.widgets || []).map((widget) => ({
|
|
696
|
+
key: widget.key,
|
|
697
|
+
label: `${this.widgetFriendlyNameForKey(widget.key)} (${widget.key})`,
|
|
698
|
+
}));
|
|
699
|
+
}
|
|
632
700
|
applyFilters(list) {
|
|
633
701
|
const q = (this.filterText || '').toLowerCase();
|
|
634
702
|
const arr = list.filter((c) => {
|
|
@@ -670,7 +738,10 @@ class ConnectionBuilderComponent {
|
|
|
670
738
|
createNew() {
|
|
671
739
|
const fromKey = this.widgets?.[0]?.key || '';
|
|
672
740
|
const toKey = this.widgets?.[1]?.key || '';
|
|
673
|
-
const conn = {
|
|
741
|
+
const conn = {
|
|
742
|
+
from: { widget: fromKey, output: this.availableOutputsForWidget(fromKey)[0] || 'submit' },
|
|
743
|
+
to: { widget: toKey, input: this.availableInputsForWidget(toKey)[0] || 'context' },
|
|
744
|
+
};
|
|
674
745
|
this.connections.set([conn, ...this.connections()]);
|
|
675
746
|
}
|
|
676
747
|
startEdit(index, _c) { this.selectedIndex.set(index); }
|
|
@@ -741,12 +812,81 @@ class ConnectionBuilderComponent {
|
|
|
741
812
|
}
|
|
742
813
|
return input;
|
|
743
814
|
}
|
|
815
|
+
availableOutputsForWidget(key) {
|
|
816
|
+
const widgetType = this.widgetTypeByKey(key);
|
|
817
|
+
return (this.registry.get(widgetType)?.outputs || []).map((output) => output.name);
|
|
818
|
+
}
|
|
819
|
+
availableInputsForWidget(key) {
|
|
820
|
+
const widgetType = this.widgetTypeByKey(key);
|
|
821
|
+
return (this.registry.get(widgetType)?.inputs || []).map((input) => input.name);
|
|
822
|
+
}
|
|
823
|
+
refreshStateOptions(page) {
|
|
824
|
+
this.stateOptions.set(this.collectStateOptions(page?.state));
|
|
825
|
+
}
|
|
826
|
+
collectStateOptions(state) {
|
|
827
|
+
const normalized = this.normalizeState(state);
|
|
828
|
+
const options = new Map();
|
|
829
|
+
for (const [path, config] of Object.entries(normalized.schema || {})) {
|
|
830
|
+
options.set(path, {
|
|
831
|
+
value: path,
|
|
832
|
+
label: config.description ? `${path} - ${config.description}` : path,
|
|
833
|
+
kind: 'primary',
|
|
834
|
+
description: config.description,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
for (const path of this.collectPaths(normalized.values || {})) {
|
|
838
|
+
if (!options.has(path)) {
|
|
839
|
+
options.set(path, { value: path, label: path, kind: 'primary' });
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
for (const [path, config] of Object.entries(normalized.derived || {})) {
|
|
843
|
+
options.set(path, {
|
|
844
|
+
value: path,
|
|
845
|
+
label: config.description ? `${path} - ${config.description}` : `${path} [derived]`,
|
|
846
|
+
kind: 'derived',
|
|
847
|
+
description: config.description,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return Array.from(options.values()).sort((left, right) => left.value.localeCompare(right.value));
|
|
851
|
+
}
|
|
852
|
+
normalizeState(state) {
|
|
853
|
+
if (!state)
|
|
854
|
+
return { values: {} };
|
|
855
|
+
const isStructured = typeof state === 'object'
|
|
856
|
+
&& !Array.isArray(state)
|
|
857
|
+
&& ('values' in state || 'schema' in state || 'derived' in state);
|
|
858
|
+
if (isStructured) {
|
|
859
|
+
const structured = state;
|
|
860
|
+
return {
|
|
861
|
+
values: this.clone(structured.values) || {},
|
|
862
|
+
schema: this.clone(structured.schema),
|
|
863
|
+
derived: this.clone(structured.derived),
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
return { values: this.clone(state) || {} };
|
|
867
|
+
}
|
|
868
|
+
collectPaths(source, prefix = '') {
|
|
869
|
+
const paths = [];
|
|
870
|
+
for (const [key, value] of Object.entries(source || {})) {
|
|
871
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
872
|
+
paths.push(path);
|
|
873
|
+
if (value != null && typeof value === 'object' && !Array.isArray(value)) {
|
|
874
|
+
paths.push(...this.collectPaths(value, path));
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return paths;
|
|
878
|
+
}
|
|
879
|
+
clone(value) {
|
|
880
|
+
if (value == null || typeof value !== 'object')
|
|
881
|
+
return value;
|
|
882
|
+
return JSON.parse(JSON.stringify(value));
|
|
883
|
+
}
|
|
744
884
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ConnectionBuilderComponent, deps: [{ token: i1.MatDialog }, { token: i1$1.ComponentMetadataRegistry }, { token: i3$1.MatSnackBar }], target: i0.ɵɵFactoryTarget.Component });
|
|
745
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: ConnectionBuilderComponent, isStandalone: true, selector: "praxis-connection-builder", inputs: { page: "page", widgets: "widgets" }, outputs: { pageChange: "pageChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"pdx-conn-root\" role=\"region\" aria-label=\"Construtor de Conex\u00C3\u00B5es\">\n <div class=\"pdx-conn-head\">\n <span class=\"pdx-conn-title\">Conex\u00C3\u00B5es</span>\n <span class=\"pdx-conn-count\" aria-label=\"Conex\u00C3\u00B5es filtradas e total\">{{ filteredConnections().length }} / {{ connections().length }}</span>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-search\">\n <mat-label>Buscar</mat-label>\n <input matInput [(ngModel)]=\"filterText\" placeholder=\"Origem, Destino, Input, Map...\" />\n <button mat-icon-button matSuffix *ngIf=\"filterText\" (click)=\"filterText='';\" aria-label=\"Limpar busca\"><mat-icon [praxisIcon]=\"'close'\"></mat-icon></button>\n </mat-form-field>\n <span class=\"pdx-spacer\"></span>\n <button mat-button (click)=\"toggleShowOnlyIssues()\" [color]=\"showOnlyIssues ? 'primary': undefined\" aria-label=\"Somente avisos/erros\"><mat-icon [praxisIcon]=\"'report_problem'\"></mat-icon> Alertas</button>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-select\">\n <mat-label>Ordenar por</mat-label>\n <mat-select [(ngModel)]=\"sortBy\" (ngModelChange)=\"setSortBy($event)\">\n <mat-option value=\"from\">Origem</mat-option>\n <mat-option value=\"to\">Destino</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-group\">\n <mat-label>Agrupar por</mat-label>\n <mat-select [(ngModel)]=\"groupBy\" (ngModelChange)=\"onGroupByChange()\">\n <mat-option value=\"none\">Nenhum</mat-option>\n <mat-option value=\"from\">Origem</mat-option>\n <mat-option value=\"to\">Destino</mat-option>\n <mat-option value=\"event\">Evento</mat-option>\n </mat-select>\n </mat-form-field>\n <button mat-icon-button [color]=\"showFriendly ? 'primary' : undefined\" (click)=\"toggleFriendly()\" aria-label=\"Alternar nome amig\u00C3\u00A1vel/t\u00C3\u00A9cnico\"><mat-icon [praxisIcon]=\"'translate'\"></mat-icon></button>\n <button mat-stroked-button (click)=\"createNew()\" aria-label=\"Nova conex\u00C3\u00A3o\"><mat-icon [praxisIcon]=\"'add'\"></mat-icon> Nova conex\u00C3\u00A3o</button>\n <button mat-flat-button color=\"accent\" class=\"pdx-diagram-cta\" (click)=\"openDiagramFullscreen()\" aria-label=\"Abrir diagrama em tela cheia\" matTooltip=\"Visualizar conex\u00C3\u00B5es em grafo\"><mat-icon [praxisIcon]=\"'schema'\"></mat-icon><span>Diagrama</span></button>\n </div>\n\n <div class=\"pdx-conn-grid\">\n <div class=\"pdx-conn-list\" role=\"list\" aria-label=\"Lista de conex\u00C3\u00B5es\" cdkScrollable>\n <mat-list *ngIf=\"filteredConnections().length; else noConns\">\n <ng-container *ngIf=\"groupBy!=='none'; else flatList\">\n <div class=\"group-block\" *ngFor=\"let g of groupedConnections(); let gi = index\">\n <div class=\"group-header\">\n <span class=\"group-title\">{{ g.label }}</span>\n <span class=\"group-count\">{{ g.list.length }}</span>\n </div>\n <mat-divider></mat-divider>\n <div class=\"group-list\">\n <ng-container *ngFor=\"let c of g.list; let i = index\">\n <mat-list-item role=\"listitem\" (click)=\"startEdit(i, c)\" [class.selected]=\"selectedIndex()===i\">\n <div class=\"card-head\" matListItemTitle [matTooltip]=\"fromTooltip(c)\">\n <mat-icon class=\"comp-icon from\" [matTooltip]=\"fromIconTooltip(c)\">{{ fromIcon(c) }}</mat-icon>\n <span class=\"pdx-badge from\" aria-hidden=\"true\"></span>\n <span class=\"from\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</span>\n <span class=\"arrow\">\u00E2\u2020\u2019</span>\n <mat-icon class=\"comp-icon to\" [matTooltip]=\"toIconTooltip(c)\">{{ toIcon(c) }}</mat-icon>\n <span class=\"pdx-badge to\" aria-hidden=\"true\"></span>\n <span class=\"to\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"status-pill\" [class.ok]=\"connectionStatus(c)==='ok'\" [class.warn]=\"connectionStatus(c)==='warn'\" [class.err]=\"connectionStatus(c)==='err'\">\n <mat-icon *ngIf=\"connectionStatus(c)==='ok'\">check_circle</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='warn'\">warning</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='err'\">error</mat-icon>\n </span>\n <button mat-icon-button class=\"expand-btn\" [attr.aria-expanded]=\"isExpanded(i)\" (click)=\"toggleExpanded(i, $event)\" [matTooltip]=\"isExpanded(i) ? 'Recolher detalhes' : 'Expandir detalhes'\">\n <mat-icon>{{ isExpanded(i) ? 'expand_less' : 'expand_more' }}</mat-icon>\n </button>\n </div>\n <div class=\"meta\" *ngIf=\"showFriendly\" matListItemLine>\n <span class=\"hint\">{{ outputDescription(c) }}</span>\n <span class=\"sep\" *ngIf=\"outputDescription(c) && inputDescription(c)\">\u00E2\u20AC\u00A2</span>\n <span class=\"hint\">{{ inputDescription(c) }}</span>\n </div>\n <div class=\"map-chip\" *ngIf=\"c.map\" [matTooltip]=\"c.map || ''\" matListItemLine (click)=\"startEditByConn(c)\" title=\"Clique para editar Map\">\n <span class=\"pdx-badge map\" aria-hidden=\"true\"></span>\n <mat-icon class=\"map-icon\" inline>bolt</mat-icon>\n <span class=\"mono\">{{ c.map }}</span>\n </div>\n <div class=\"card-actions\" matListItemLine (click)=\"$event.stopPropagation()\">\n <span class=\"action-group\">\n <button mat-icon-button (click)=\"startEdit(i, c)\" matTooltip=\"Editar\"><mat-icon>settings</mat-icon></button>\n <button mat-icon-button (click)=\"duplicateConnection(i)\" matTooltip=\"Duplicar\"><mat-icon>content_copy</mat-icon></button>\n </span>\n <span class=\"action-group\">\n <button mat-stroked-button color=\"primary\" (click)=\"openDiagramFor(c)\" matTooltip=\"Ver rela\u00C3\u00A7\u00C3\u00A3o no diagrama\"><mat-icon>schema</mat-icon><span>Diagrama</span></button>\n </span>\n <span class=\"spacer\"></span>\n <span class=\"action-group\">\n <button mat-icon-button color=\"warn\" (click)=\"removeConnection(i)\" matTooltip=\"Remover\"><mat-icon>delete</mat-icon></button>\n </span>\n </div>\n <div class=\"card-details\" matListItemLine [class.expanded]=\"isExpanded(i)\" [style.maxHeight]=\"isExpanded(i) ? '240px' : '0px'\" [style.opacity]=\"isExpanded(i) ? 1 : 0\" [style.pointerEvents]=\"isExpanded(i) ? 'auto' : 'none'\">\n <div class=\"stepper\">\n <div class=\"step from-step\">\n <div class=\"dot from\"></div>\n <div class=\"content\">\n <div class=\"label\">De</div>\n <div class=\"value\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step to-step\">\n <div class=\"dot to\"></div>\n <div class=\"content\">\n <div class=\"label\">Para</div>\n <div class=\"value\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step map-step\" *ngIf=\"c.map\">\n <div class=\"dot map\"></div>\n <div class=\"content\">\n <div class=\"label\">Map</div>\n <div class=\"value mono\">{{ c.map }}</div>\n </div>\n </div>\n </div>\n </div>\n </mat-list-item>\n </ng-container>\n </div>\n </div>\n </ng-container>\n <ng-template #flatList>\n <ng-container *ngFor=\"let c of filteredConnections(); let i = index\">\n <mat-list-item role=\"listitem\" (click)=\"startEdit(i, c)\" [class.selected]=\"selectedIndex()===i\">\n <div class=\"card-head\" matListItemTitle [matTooltip]=\"fromTooltip(c)\">\n <mat-icon class=\"comp-icon from\" [matTooltip]=\"fromIconTooltip(c)\">{{ fromIcon(c) }}</mat-icon>\n <span class=\"pdx-badge from\" aria-hidden=\"true\"></span>\n <span class=\"from\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</span>\n <span class=\"arrow\">\u00E2\u2020\u2019</span>\n <mat-icon class=\"comp-icon to\" [matTooltip]=\"toIconTooltip(c)\">{{ toIcon(c) }}</mat-icon>\n <span class=\"pdx-badge to\" aria-hidden=\"true\"></span>\n <span class=\"to\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"status-pill\" [class.ok]=\"connectionStatus(c)==='ok'\" [class.warn]=\"connectionStatus(c)==='warn'\" [class.err]=\"connectionStatus(c)==='err'\">\n <mat-icon *ngIf=\"connectionStatus(c)==='ok'\">check_circle</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='warn'\">warning</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='err'\">error</mat-icon>\n </span>\n <button mat-icon-button class=\"expand-btn\" [attr.aria-expanded]=\"isExpanded(i)\" (click)=\"toggleExpanded(i, $event)\" [matTooltip]=\"isExpanded(i) ? 'Recolher detalhes' : 'Expandir detalhes'\">\n <mat-icon>{{ isExpanded(i) ? 'expand_less' : 'expand_more' }}</mat-icon>\n </button>\n </div>\n <div class=\"meta\" *ngIf=\"showFriendly\" matListItemLine>\n <span class=\"hint\">{{ outputDescription(c) }}</span>\n <span class=\"sep\" *ngIf=\"outputDescription(c) && inputDescription(c)\">\u00E2\u20AC\u00A2</span>\n <span class=\"hint\">{{ inputDescription(c) }}</span>\n </div>\n <div class=\"map-chip\" *ngIf=\"c.map\" [matTooltip]=\"c.map || ''\" matListItemLine (click)=\"startEditByConn(c)\" title=\"Clique para editar Map\">\n <span class=\"pdx-badge map\" aria-hidden=\"true\"></span>\n <mat-icon class=\"map-icon\" inline>bolt</mat-icon>\n <span class=\"mono\">{{ c.map }}</span>\n </div>\n <div class=\"card-actions\" matListItemLine (click)=\"$event.stopPropagation()\">\n <span class=\"action-group\">\n <button mat-icon-button (click)=\"startEdit(i, c)\" matTooltip=\"Editar\"><mat-icon>settings</mat-icon></button>\n <button mat-icon-button (click)=\"duplicateConnection(i)\" matTooltip=\"Duplicar\"><mat-icon>content_copy</mat-icon></button>\n </span>\n <span class=\"action-group\">\n <button mat-stroked-button color=\"primary\" (click)=\"openDiagramFor(c)\" matTooltip=\"Ver rela\u00C3\u00A7\u00C3\u00A3o no diagrama\"><mat-icon>schema</mat-icon><span>Diagrama</span></button>\n </span>\n <span class=\"spacer\"></span>\n <span class=\"action-group\">\n <button mat-icon-button color=\"warn\" (click)=\"removeConnection(i)\" matTooltip=\"Remover\"><mat-icon>delete</mat-icon></button>\n </span>\n </div>\n <div class=\"card-details\" matListItemLine [class.expanded]=\"isExpanded(i)\" [style.maxHeight]=\"isExpanded(i) ? '240px' : '0px'\" [style.opacity]=\"isExpanded(i) ? 1 : 0\" [style.pointerEvents]=\"isExpanded(i) ? 'auto' : 'none'\">\n <div class=\"stepper\">\n <div class=\"step from-step\">\n <div class=\"dot from\"></div>\n <div class=\"content\">\n <div class=\"label\">De</div>\n <div class=\"value\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step to-step\">\n <div class=\"dot to\"></div>\n <div class=\"content\">\n <div class=\"label\">Para</div>\n <div class=\"value\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step map-step\" *ngIf=\"c.map\">\n <div class=\"dot map\"></div>\n <div class=\"content\">\n <div class=\"label\">Map</div>\n <div class=\"value mono\">{{ c.map }}</div>\n </div>\n </div>\n </div>\n </div>\n </mat-list-item>\n </ng-container>\n </ng-template>\n </mat-list>\n <ng-template #noConns>\n <div class=\"pdx-empty-list\" role=\"status\" aria-live=\"polite\">\n <mat-icon>link_off</mat-icon>\n <div>Nenhuma conex\u00C3\u00A3o definida. Use \"Nova conex\u00C3\u00A3o\".</div>\n </div>\n </ng-template>\n </div>\n\n <div class=\"pdx-conn-editor\" role=\"form\" aria-label=\"Editor de conex\u00C3\u00A3o\">\n <ng-container *ngIf=\"selectedIndex() >= 0; else emptyEditor\">\n <div class=\"form-grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Origem (widget.key)</mat-label>\n <input matInput [ngModel]=\"selectedSourcePrimaryValue()\" (ngModelChange)=\"setSelectedSourcePrimaryValue($event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" *ngIf=\"showSelectedSourceSecondary()\">\n <mat-label>Evento (output)</mat-label>\n <input matInput [ngModel]=\"selectedSourceSecondaryValue()\" (ngModelChange)=\"setSelectedSourceSecondaryValue($event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Destino (widget.key)</mat-label>\n <input matInput [ngModel]=\"selectedTargetPrimaryValue()\" (ngModelChange)=\"setSelectedTargetPrimaryValue($event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" *ngIf=\"showSelectedTargetSecondary()\">\n <mat-label>Input</mat-label>\n <input matInput [ngModel]=\"selectedTargetSecondaryValue()\" (ngModelChange)=\"setSelectedTargetSecondaryValue($event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-span\">\n <mat-label>Map (opcional)</mat-label>\n <input matInput [ngModel]=\"selectedMapValue()\" (ngModelChange)=\"setSelectedMapValue($event)\" [placeholder]=\"mapPlaceholder\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Ex.: payload.id ou ${payload.id}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"full-span\">\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\"><mat-icon [praxisIcon]=\"'save'\"></mat-icon>Salvar</button>\n </div>\n </div>\n </ng-container>\n <ng-template #emptyEditor>\n <div class=\"pdx-empty-editor\">Selecione uma conex\u00C3\u00A3o para editar.</div>\n </ng-template>\n </div>\n </div>\n</div>\n", styles: [".pdx-conn-root{display:grid;gap:12px;color:var(--md-sys-color-on-surface)}.pdx-conn-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.pdx-conn-title{font-weight:600}.pdx-conn-count{color:var(--md-sys-color-on-surface-variant)}.pdx-conn-search{width:260px}.pdx-conn-select{width:160px}.pdx-conn-group{width:170px}.pdx-spacer{flex:1}.pdx-diagram-cta span{margin-left:6px}.pdx-conn-grid{display:grid;grid-template-columns:1.4fr 1fr;gap:12px}.pdx-conn-list{height:540px;overflow:auto;padding:8px 0;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface)}.pdx-conn-list .mat-mdc-list-item.mdc-list-item{height:auto;padding:10px 8px 8px}.pdx-conn-list .mat-mdc-list-item.selected{background:var(--md-sys-color-primary-container)}.card-head{display:flex;align-items:center;gap:6px}.card-head .spacer{flex:1}.card-head .comp-icon{opacity:.85}.card-head .expand-btn{margin-left:4px}.status-pill{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px}.status-pill.ok{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.status-pill.warn{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.status-pill.err{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.map-chip{display:inline-flex;align-items:center;gap:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.map-chip .map-icon{font-size:16px}.pdx-conn-editor{min-height:540px;padding:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface)}.pdx-empty-list{padding:16px;display:grid;place-items:center;gap:6px;opacity:.8}.pdx-empty-editor{padding:16px;opacity:.75}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;align-content:start}.full-span{grid-column:1/-1}.stepper{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;padding:8px 0}.step{display:grid;grid-template-columns:12px 1fr;gap:8px;align-items:start}.dot{width:12px;height:12px;border-radius:50%;margin-top:5px}.dot.from{background:var(--md-sys-color-primary-container)}.dot.to{background:var(--md-sys-color-secondary-container)}.dot.map{background:var(--md-sys-color-tertiary-container)}.group-block{padding:6px 6px 10px}.group-header{display:flex;gap:8px;align-items:center;padding:8px 0}.group-title{font-weight:600}.group-count{opacity:.75}.group-list{display:grid;gap:6px}.pdx-badge{width:10px;height:10px;border-radius:50%;display:inline-block}.pdx-badge.from{background:var(--md-sys-color-primary-container)}.pdx-badge.to{background:var(--md-sys-color-secondary-container)}.pdx-badge.map{background:var(--md-sys-color-tertiary-container)}.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:12px}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}@media(max-width:960px){.pdx-conn-grid{grid-template-columns:1fr}.pdx-conn-list,.pdx-conn-editor{min-height:360px}.pdx-conn-search,.pdx-conn-select,.pdx-conn-group{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "directive", type: i8.CdkScrollable, selector: "[cdk-scrollable], [cdkScrollable]" }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i12.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i12.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i12.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i12.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatDialogModule }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "ngmodule", type: MatTabsModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
885
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: ConnectionBuilderComponent, isStandalone: true, selector: "praxis-connection-builder", inputs: { page: "page", widgets: "widgets" }, outputs: { pageChange: "pageChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"pdx-conn-root\" role=\"region\" aria-label=\"Construtor de Conex\u00C3\u00B5es\">\n <div class=\"pdx-conn-head\">\n <span class=\"pdx-conn-title\">Conex\u00C3\u00B5es</span>\n <span class=\"pdx-conn-count\" aria-label=\"Conex\u00C3\u00B5es filtradas e total\">{{ filteredConnections().length }} / {{ connections().length }}</span>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-search\">\n <mat-label>Buscar</mat-label>\n <input matInput [(ngModel)]=\"filterText\" placeholder=\"Origem, Destino, Input, Map...\" />\n <button mat-icon-button matSuffix *ngIf=\"filterText\" (click)=\"filterText='';\" aria-label=\"Limpar busca\"><mat-icon [praxisIcon]=\"'close'\"></mat-icon></button>\n </mat-form-field>\n <span class=\"pdx-spacer\"></span>\n <button mat-button (click)=\"toggleShowOnlyIssues()\" [color]=\"showOnlyIssues ? 'primary': undefined\" aria-label=\"Somente avisos/erros\"><mat-icon [praxisIcon]=\"'report_problem'\"></mat-icon> Alertas</button>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-select\">\n <mat-label>Ordenar por</mat-label>\n <mat-select [(ngModel)]=\"sortBy\" (ngModelChange)=\"setSortBy($event)\">\n <mat-option value=\"from\">Origem</mat-option>\n <mat-option value=\"to\">Destino</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-group\">\n <mat-label>Agrupar por</mat-label>\n <mat-select [(ngModel)]=\"groupBy\" (ngModelChange)=\"onGroupByChange()\">\n <mat-option value=\"none\">Nenhum</mat-option>\n <mat-option value=\"from\">Origem</mat-option>\n <mat-option value=\"to\">Destino</mat-option>\n <mat-option value=\"event\">Evento</mat-option>\n </mat-select>\n </mat-form-field>\n <button mat-icon-button [color]=\"showFriendly ? 'primary' : undefined\" (click)=\"toggleFriendly()\" aria-label=\"Alternar nome amig\u00C3\u00A1vel/t\u00C3\u00A9cnico\"><mat-icon [praxisIcon]=\"'translate'\"></mat-icon></button>\n <button mat-stroked-button (click)=\"createNew()\" aria-label=\"Nova conex\u00C3\u00A3o\"><mat-icon [praxisIcon]=\"'add'\"></mat-icon> Nova conex\u00C3\u00A3o</button>\n <button mat-flat-button color=\"accent\" class=\"pdx-diagram-cta\" (click)=\"openDiagramFullscreen()\" aria-label=\"Abrir diagrama em tela cheia\" matTooltip=\"Visualizar conex\u00C3\u00B5es em grafo\"><mat-icon [praxisIcon]=\"'schema'\"></mat-icon><span>Diagrama</span></button>\n </div>\n\n <div class=\"pdx-conn-grid\">\n <div class=\"pdx-conn-list\" role=\"list\" aria-label=\"Lista de conex\u00C3\u00B5es\" cdkScrollable>\n <mat-list *ngIf=\"filteredConnections().length; else noConns\">\n <ng-container *ngIf=\"groupBy!=='none'; else flatList\">\n <div class=\"group-block\" *ngFor=\"let g of groupedConnections(); let gi = index\">\n <div class=\"group-header\">\n <span class=\"group-title\">{{ g.label }}</span>\n <span class=\"group-count\">{{ g.list.length }}</span>\n </div>\n <mat-divider></mat-divider>\n <div class=\"group-list\">\n <ng-container *ngFor=\"let c of g.list; let i = index\">\n <mat-list-item role=\"listitem\" (click)=\"startEdit(i, c)\" [class.selected]=\"selectedIndex()===i\">\n <div class=\"card-head\" matListItemTitle [matTooltip]=\"fromTooltip(c)\">\n <mat-icon class=\"comp-icon from\" [matTooltip]=\"fromIconTooltip(c)\">{{ fromIcon(c) }}</mat-icon>\n <span class=\"pdx-badge from\" aria-hidden=\"true\"></span>\n <span class=\"from\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</span>\n <span class=\"arrow\">\u00E2\u2020\u2019</span>\n <mat-icon class=\"comp-icon to\" [matTooltip]=\"toIconTooltip(c)\">{{ toIcon(c) }}</mat-icon>\n <span class=\"pdx-badge to\" aria-hidden=\"true\"></span>\n <span class=\"to\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"status-pill\" [class.ok]=\"connectionStatus(c)==='ok'\" [class.warn]=\"connectionStatus(c)==='warn'\" [class.err]=\"connectionStatus(c)==='err'\">\n <mat-icon *ngIf=\"connectionStatus(c)==='ok'\">check_circle</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='warn'\">warning</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='err'\">error</mat-icon>\n </span>\n <button mat-icon-button class=\"expand-btn\" [attr.aria-expanded]=\"isExpanded(i)\" (click)=\"toggleExpanded(i, $event)\" [matTooltip]=\"isExpanded(i) ? 'Recolher detalhes' : 'Expandir detalhes'\">\n <mat-icon>{{ isExpanded(i) ? 'expand_less' : 'expand_more' }}</mat-icon>\n </button>\n </div>\n <div class=\"meta\" *ngIf=\"showFriendly\" matListItemLine>\n <span class=\"hint\">{{ outputDescription(c) }}</span>\n <span class=\"sep\" *ngIf=\"outputDescription(c) && inputDescription(c)\">\u00E2\u20AC\u00A2</span>\n <span class=\"hint\">{{ inputDescription(c) }}</span>\n </div>\n <div class=\"map-chip\" *ngIf=\"c.map\" [matTooltip]=\"c.map || ''\" matListItemLine (click)=\"startEditByConn(c)\" title=\"Clique para editar Map\">\n <span class=\"pdx-badge map\" aria-hidden=\"true\"></span>\n <mat-icon class=\"map-icon\" inline>bolt</mat-icon>\n <span class=\"mono\">{{ c.map }}</span>\n </div>\n <div class=\"card-actions\" (click)=\"$event.stopPropagation()\">\n <span class=\"action-group\">\n <button mat-icon-button (click)=\"startEdit(i, c)\" matTooltip=\"Editar\"><mat-icon>settings</mat-icon></button>\n <button mat-icon-button (click)=\"duplicateConnection(i)\" matTooltip=\"Duplicar\"><mat-icon>content_copy</mat-icon></button>\n </span>\n <span class=\"action-group\">\n <button mat-stroked-button color=\"primary\" (click)=\"openDiagramFor(c)\" matTooltip=\"Ver rela\u00C3\u00A7\u00C3\u00A3o no diagrama\"><mat-icon>schema</mat-icon><span>Diagrama</span></button>\n </span>\n <span class=\"spacer\"></span>\n <span class=\"action-group\">\n <button mat-icon-button color=\"warn\" (click)=\"removeConnection(i)\" matTooltip=\"Remover\"><mat-icon>delete</mat-icon></button>\n </span>\n </div>\n <div class=\"card-details\" [class.expanded]=\"isExpanded(i)\" [style.maxHeight]=\"isExpanded(i) ? '240px' : '0px'\" [style.opacity]=\"isExpanded(i) ? 1 : 0\" [style.pointerEvents]=\"isExpanded(i) ? 'auto' : 'none'\">\n <div class=\"stepper\">\n <div class=\"step from-step\">\n <div class=\"dot from\"></div>\n <div class=\"content\">\n <div class=\"label\">De</div>\n <div class=\"value\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step to-step\">\n <div class=\"dot to\"></div>\n <div class=\"content\">\n <div class=\"label\">Para</div>\n <div class=\"value\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step map-step\" *ngIf=\"c.map\">\n <div class=\"dot map\"></div>\n <div class=\"content\">\n <div class=\"label\">Map</div>\n <div class=\"value mono\">{{ c.map }}</div>\n </div>\n </div>\n </div>\n </div>\n </mat-list-item>\n </ng-container>\n </div>\n </div>\n </ng-container>\n <ng-template #flatList>\n <ng-container *ngFor=\"let c of filteredConnections(); let i = index\">\n <mat-list-item role=\"listitem\" (click)=\"startEdit(i, c)\" [class.selected]=\"selectedIndex()===i\">\n <div class=\"card-head\" matListItemTitle [matTooltip]=\"fromTooltip(c)\">\n <mat-icon class=\"comp-icon from\" [matTooltip]=\"fromIconTooltip(c)\">{{ fromIcon(c) }}</mat-icon>\n <span class=\"pdx-badge from\" aria-hidden=\"true\"></span>\n <span class=\"from\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</span>\n <span class=\"arrow\">\u00E2\u2020\u2019</span>\n <mat-icon class=\"comp-icon to\" [matTooltip]=\"toIconTooltip(c)\">{{ toIcon(c) }}</mat-icon>\n <span class=\"pdx-badge to\" aria-hidden=\"true\"></span>\n <span class=\"to\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"status-pill\" [class.ok]=\"connectionStatus(c)==='ok'\" [class.warn]=\"connectionStatus(c)==='warn'\" [class.err]=\"connectionStatus(c)==='err'\">\n <mat-icon *ngIf=\"connectionStatus(c)==='ok'\">check_circle</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='warn'\">warning</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='err'\">error</mat-icon>\n </span>\n <button mat-icon-button class=\"expand-btn\" [attr.aria-expanded]=\"isExpanded(i)\" (click)=\"toggleExpanded(i, $event)\" [matTooltip]=\"isExpanded(i) ? 'Recolher detalhes' : 'Expandir detalhes'\">\n <mat-icon>{{ isExpanded(i) ? 'expand_less' : 'expand_more' }}</mat-icon>\n </button>\n </div>\n <div class=\"meta\" *ngIf=\"showFriendly\" matListItemLine>\n <span class=\"hint\">{{ outputDescription(c) }}</span>\n <span class=\"sep\" *ngIf=\"outputDescription(c) && inputDescription(c)\">\u00E2\u20AC\u00A2</span>\n <span class=\"hint\">{{ inputDescription(c) }}</span>\n </div>\n <div class=\"map-chip\" *ngIf=\"c.map\" [matTooltip]=\"c.map || ''\" matListItemLine (click)=\"startEditByConn(c)\" title=\"Clique para editar Map\">\n <span class=\"pdx-badge map\" aria-hidden=\"true\"></span>\n <mat-icon class=\"map-icon\" inline>bolt</mat-icon>\n <span class=\"mono\">{{ c.map }}</span>\n </div>\n <div class=\"card-actions\" (click)=\"$event.stopPropagation()\">\n <span class=\"action-group\">\n <button mat-icon-button (click)=\"startEdit(i, c)\" matTooltip=\"Editar\"><mat-icon>settings</mat-icon></button>\n <button mat-icon-button (click)=\"duplicateConnection(i)\" matTooltip=\"Duplicar\"><mat-icon>content_copy</mat-icon></button>\n </span>\n <span class=\"action-group\">\n <button mat-stroked-button color=\"primary\" (click)=\"openDiagramFor(c)\" matTooltip=\"Ver rela\u00C3\u00A7\u00C3\u00A3o no diagrama\"><mat-icon>schema</mat-icon><span>Diagrama</span></button>\n </span>\n <span class=\"spacer\"></span>\n <span class=\"action-group\">\n <button mat-icon-button color=\"warn\" (click)=\"removeConnection(i)\" matTooltip=\"Remover\"><mat-icon>delete</mat-icon></button>\n </span>\n </div>\n <div class=\"card-details\" [class.expanded]=\"isExpanded(i)\" [style.maxHeight]=\"isExpanded(i) ? '240px' : '0px'\" [style.opacity]=\"isExpanded(i) ? 1 : 0\" [style.pointerEvents]=\"isExpanded(i) ? 'auto' : 'none'\">\n <div class=\"stepper\">\n <div class=\"step from-step\">\n <div class=\"dot from\"></div>\n <div class=\"content\">\n <div class=\"label\">De</div>\n <div class=\"value\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step to-step\">\n <div class=\"dot to\"></div>\n <div class=\"content\">\n <div class=\"label\">Para</div>\n <div class=\"value\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step map-step\" *ngIf=\"c.map\">\n <div class=\"dot map\"></div>\n <div class=\"content\">\n <div class=\"label\">Map</div>\n <div class=\"value mono\">{{ c.map }}</div>\n </div>\n </div>\n </div>\n </div>\n </mat-list-item>\n </ng-container>\n </ng-template>\n </mat-list>\n <ng-template #noConns>\n <div class=\"pdx-empty-list\" role=\"status\" aria-live=\"polite\">\n <mat-icon>link_off</mat-icon>\n <div>Nenhuma conex\u00C3\u00A3o definida. Use \"Nova conex\u00C3\u00A3o\".</div>\n </div>\n </ng-template>\n </div>\n\n <div class=\"pdx-conn-editor\" role=\"form\" aria-label=\"Editor de conex\u00C3\u00A3o\">\n <ng-container *ngIf=\"selectedIndex() >= 0; else emptyEditor\">\n <div class=\"form-grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo da origem</mat-label>\n <mat-select [ngModel]=\"selectedSourceKind()\" (ngModelChange)=\"setSelectedSourceKind($event)\">\n <mat-option value=\"widget\">Widget</mat-option>\n <mat-option value=\"state\">State</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ selectedSourceKind() === 'widget' ? 'Origem (widget.key)' : 'Origem (state)' }}</mat-label>\n <mat-select [ngModel]=\"selectedSourcePrimaryValue()\" (ngModelChange)=\"setSelectedSourcePrimaryValue($event)\">\n <ng-container *ngIf=\"selectedSourceKind() === 'widget'; else sourceStateOptions\">\n <mat-option *ngFor=\"let option of widgetOptions()\" [value]=\"option.key\">\n {{ option.label }}\n </mat-option>\n </ng-container>\n <ng-template #sourceStateOptions>\n <mat-option *ngFor=\"let option of availableStateOptions()\" [value]=\"option.value\">\n {{ option.label }}\n </mat-option>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" *ngIf=\"showSelectedSourceSecondary()\">\n <mat-label>Evento (output)</mat-label>\n <mat-select [ngModel]=\"selectedSourceSecondaryValue()\" (ngModelChange)=\"setSelectedSourceSecondaryValue($event)\">\n <mat-option *ngFor=\"let output of availableOutputsForSelectedWidget()\" [value]=\"output\">{{ output }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"endpoint-hint full-span\" *ngIf=\"selectedSourceKind() === 'state' && selectedSourcePrimaryValue()\">\n <mat-icon>{{ stateOptionIcon(selectedSourcePrimaryValue()) }}</mat-icon>\n <span>{{ stateOptionLabel(selectedSourcePrimaryValue()) }}</span>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo do destino</mat-label>\n <mat-select [ngModel]=\"selectedTargetKind()\" (ngModelChange)=\"setSelectedTargetKind($event)\">\n <mat-option value=\"widget\">Widget</mat-option>\n <mat-option value=\"state\">State</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ selectedTargetKind() === 'widget' ? 'Destino (widget.key)' : 'Destino (state)' }}</mat-label>\n <mat-select [ngModel]=\"selectedTargetPrimaryValue()\" (ngModelChange)=\"setSelectedTargetPrimaryValue($event)\">\n <ng-container *ngIf=\"selectedTargetKind() === 'widget'; else targetStateOptions\">\n <mat-option *ngFor=\"let option of widgetOptions()\" [value]=\"option.key\">\n {{ option.label }}\n </mat-option>\n </ng-container>\n <ng-template #targetStateOptions>\n <mat-option *ngFor=\"let option of availableStateOptions()\" [value]=\"option.value\">\n {{ option.label }}\n </mat-option>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" *ngIf=\"showSelectedTargetSecondary()\">\n <mat-label>Input</mat-label>\n <mat-select [ngModel]=\"selectedTargetSecondaryValue()\" (ngModelChange)=\"setSelectedTargetSecondaryValue($event)\">\n <mat-option *ngFor=\"let input of availableInputsForSelectedWidget()\" [value]=\"input\">{{ input }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"endpoint-hint full-span\" *ngIf=\"selectedTargetKind() === 'state' && selectedTargetPrimaryValue()\">\n <mat-icon>{{ stateOptionIcon(selectedTargetPrimaryValue()) }}</mat-icon>\n <span>{{ stateOptionLabel(selectedTargetPrimaryValue()) }}</span>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-span\">\n <mat-label>Map (opcional)</mat-label>\n <input matInput [ngModel]=\"selectedMapValue()\" (ngModelChange)=\"setSelectedMapValue($event)\" [placeholder]=\"mapPlaceholder\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Ex.: payload.id ou ${payload.id}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"full-span\">\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\"><mat-icon [praxisIcon]=\"'save'\"></mat-icon>Salvar</button>\n </div>\n </div>\n </ng-container>\n <ng-template #emptyEditor>\n <div class=\"pdx-empty-editor\">Selecione uma conex\u00C3\u00A3o para editar.</div>\n </ng-template>\n </div>\n </div>\n</div>\n", styles: [".pdx-conn-root{display:grid;gap:12px;color:var(--md-sys-color-on-surface)}.pdx-conn-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.pdx-conn-title{font-weight:600}.pdx-conn-count{color:var(--md-sys-color-on-surface-variant)}.pdx-conn-search{width:260px}.pdx-conn-select{width:160px}.pdx-conn-group{width:170px}.pdx-spacer{flex:1}.pdx-diagram-cta span{margin-left:6px}.pdx-conn-grid{display:grid;grid-template-columns:1.4fr 1fr;gap:12px}.pdx-conn-list{height:540px;overflow:auto;padding:8px 0;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface)}.pdx-conn-list .mat-mdc-list-item.mdc-list-item{height:auto;padding:10px 8px 8px}.pdx-conn-list .mat-mdc-list-item.selected{background:var(--md-sys-color-primary-container)}.card-head{display:flex;align-items:center;gap:6px}.card-head .spacer{flex:1}.card-head .comp-icon{opacity:.85}.card-head .expand-btn{margin-left:4px}.status-pill{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px}.status-pill.ok{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.status-pill.warn{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.status-pill.err{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.map-chip{display:inline-flex;align-items:center;gap:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.map-chip .map-icon{font-size:16px}.pdx-conn-editor{min-height:540px;padding:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface)}.pdx-empty-list{padding:16px;display:grid;place-items:center;gap:6px;opacity:.8}.pdx-empty-editor{padding:16px;opacity:.75}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;align-content:start}.full-span{grid-column:1/-1}.endpoint-hint{display:flex;align-items:center;gap:8px;min-height:44px;padding:0 12px;border-radius:12px;color:var(--md-sys-color-on-surface-variant);background:color-mix(in srgb,var(--md-sys-color-secondary-container) 35%,transparent)}.endpoint-hint mat-icon{color:var(--md-sys-color-secondary)}.stepper{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;padding:8px 0}.step{display:grid;grid-template-columns:12px 1fr;gap:8px;align-items:start}.dot{width:12px;height:12px;border-radius:50%;margin-top:5px}.dot.from{background:var(--md-sys-color-primary-container)}.dot.to{background:var(--md-sys-color-secondary-container)}.dot.map{background:var(--md-sys-color-tertiary-container)}.group-block{padding:6px 6px 10px}.group-header{display:flex;gap:8px;align-items:center;padding:8px 0}.group-title{font-weight:600}.group-count{opacity:.75}.group-list{display:grid;gap:6px}.pdx-badge{width:10px;height:10px;border-radius:50%;display:inline-block}.pdx-badge.from{background:var(--md-sys-color-primary-container)}.pdx-badge.to{background:var(--md-sys-color-secondary-container)}.pdx-badge.map{background:var(--md-sys-color-tertiary-container)}.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:12px}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}@media(max-width:960px){.pdx-conn-grid{grid-template-columns:1fr}.pdx-conn-list,.pdx-conn-editor{min-height:360px}.pdx-conn-search,.pdx-conn-select,.pdx-conn-group{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "directive", type: i8.CdkScrollable, selector: "[cdk-scrollable], [cdkScrollable]" }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i12.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i12.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i12.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i12.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatDialogModule }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "ngmodule", type: MatTabsModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
746
886
|
}
|
|
747
887
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ConnectionBuilderComponent, decorators: [{
|
|
748
888
|
type: Component,
|
|
749
|
-
args: [{ selector: 'praxis-connection-builder', standalone: true, imports: [CommonModule, FormsModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatButtonModule, MatIconModule, MatListModule, MatDialogModule, MatAutocompleteModule, MatMenuModule, MatTabsModule, MatTooltipModule, MatProgressBarModule, MatCheckboxModule, MatSnackBarModule, ScrollingModule, PraxisIconDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pdx-conn-root\" role=\"region\" aria-label=\"Construtor de Conex\u00C3\u00B5es\">\n <div class=\"pdx-conn-head\">\n <span class=\"pdx-conn-title\">Conex\u00C3\u00B5es</span>\n <span class=\"pdx-conn-count\" aria-label=\"Conex\u00C3\u00B5es filtradas e total\">{{ filteredConnections().length }} / {{ connections().length }}</span>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-search\">\n <mat-label>Buscar</mat-label>\n <input matInput [(ngModel)]=\"filterText\" placeholder=\"Origem, Destino, Input, Map...\" />\n <button mat-icon-button matSuffix *ngIf=\"filterText\" (click)=\"filterText='';\" aria-label=\"Limpar busca\"><mat-icon [praxisIcon]=\"'close'\"></mat-icon></button>\n </mat-form-field>\n <span class=\"pdx-spacer\"></span>\n <button mat-button (click)=\"toggleShowOnlyIssues()\" [color]=\"showOnlyIssues ? 'primary': undefined\" aria-label=\"Somente avisos/erros\"><mat-icon [praxisIcon]=\"'report_problem'\"></mat-icon> Alertas</button>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-select\">\n <mat-label>Ordenar por</mat-label>\n <mat-select [(ngModel)]=\"sortBy\" (ngModelChange)=\"setSortBy($event)\">\n <mat-option value=\"from\">Origem</mat-option>\n <mat-option value=\"to\">Destino</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-group\">\n <mat-label>Agrupar por</mat-label>\n <mat-select [(ngModel)]=\"groupBy\" (ngModelChange)=\"onGroupByChange()\">\n <mat-option value=\"none\">Nenhum</mat-option>\n <mat-option value=\"from\">Origem</mat-option>\n <mat-option value=\"to\">Destino</mat-option>\n <mat-option value=\"event\">Evento</mat-option>\n </mat-select>\n </mat-form-field>\n <button mat-icon-button [color]=\"showFriendly ? 'primary' : undefined\" (click)=\"toggleFriendly()\" aria-label=\"Alternar nome amig\u00C3\u00A1vel/t\u00C3\u00A9cnico\"><mat-icon [praxisIcon]=\"'translate'\"></mat-icon></button>\n <button mat-stroked-button (click)=\"createNew()\" aria-label=\"Nova conex\u00C3\u00A3o\"><mat-icon [praxisIcon]=\"'add'\"></mat-icon> Nova conex\u00C3\u00A3o</button>\n <button mat-flat-button color=\"accent\" class=\"pdx-diagram-cta\" (click)=\"openDiagramFullscreen()\" aria-label=\"Abrir diagrama em tela cheia\" matTooltip=\"Visualizar conex\u00C3\u00B5es em grafo\"><mat-icon [praxisIcon]=\"'schema'\"></mat-icon><span>Diagrama</span></button>\n </div>\n\n <div class=\"pdx-conn-grid\">\n <div class=\"pdx-conn-list\" role=\"list\" aria-label=\"Lista de conex\u00C3\u00B5es\" cdkScrollable>\n <mat-list *ngIf=\"filteredConnections().length; else noConns\">\n <ng-container *ngIf=\"groupBy!=='none'; else flatList\">\n <div class=\"group-block\" *ngFor=\"let g of groupedConnections(); let gi = index\">\n <div class=\"group-header\">\n <span class=\"group-title\">{{ g.label }}</span>\n <span class=\"group-count\">{{ g.list.length }}</span>\n </div>\n <mat-divider></mat-divider>\n <div class=\"group-list\">\n <ng-container *ngFor=\"let c of g.list; let i = index\">\n <mat-list-item role=\"listitem\" (click)=\"startEdit(i, c)\" [class.selected]=\"selectedIndex()===i\">\n <div class=\"card-head\" matListItemTitle [matTooltip]=\"fromTooltip(c)\">\n <mat-icon class=\"comp-icon from\" [matTooltip]=\"fromIconTooltip(c)\">{{ fromIcon(c) }}</mat-icon>\n <span class=\"pdx-badge from\" aria-hidden=\"true\"></span>\n <span class=\"from\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</span>\n <span class=\"arrow\">\u00E2\u2020\u2019</span>\n <mat-icon class=\"comp-icon to\" [matTooltip]=\"toIconTooltip(c)\">{{ toIcon(c) }}</mat-icon>\n <span class=\"pdx-badge to\" aria-hidden=\"true\"></span>\n <span class=\"to\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"status-pill\" [class.ok]=\"connectionStatus(c)==='ok'\" [class.warn]=\"connectionStatus(c)==='warn'\" [class.err]=\"connectionStatus(c)==='err'\">\n <mat-icon *ngIf=\"connectionStatus(c)==='ok'\">check_circle</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='warn'\">warning</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='err'\">error</mat-icon>\n </span>\n <button mat-icon-button class=\"expand-btn\" [attr.aria-expanded]=\"isExpanded(i)\" (click)=\"toggleExpanded(i, $event)\" [matTooltip]=\"isExpanded(i) ? 'Recolher detalhes' : 'Expandir detalhes'\">\n <mat-icon>{{ isExpanded(i) ? 'expand_less' : 'expand_more' }}</mat-icon>\n </button>\n </div>\n <div class=\"meta\" *ngIf=\"showFriendly\" matListItemLine>\n <span class=\"hint\">{{ outputDescription(c) }}</span>\n <span class=\"sep\" *ngIf=\"outputDescription(c) && inputDescription(c)\">\u00E2\u20AC\u00A2</span>\n <span class=\"hint\">{{ inputDescription(c) }}</span>\n </div>\n <div class=\"map-chip\" *ngIf=\"c.map\" [matTooltip]=\"c.map || ''\" matListItemLine (click)=\"startEditByConn(c)\" title=\"Clique para editar Map\">\n <span class=\"pdx-badge map\" aria-hidden=\"true\"></span>\n <mat-icon class=\"map-icon\" inline>bolt</mat-icon>\n <span class=\"mono\">{{ c.map }}</span>\n </div>\n <div class=\"card-actions\" matListItemLine (click)=\"$event.stopPropagation()\">\n <span class=\"action-group\">\n <button mat-icon-button (click)=\"startEdit(i, c)\" matTooltip=\"Editar\"><mat-icon>settings</mat-icon></button>\n <button mat-icon-button (click)=\"duplicateConnection(i)\" matTooltip=\"Duplicar\"><mat-icon>content_copy</mat-icon></button>\n </span>\n <span class=\"action-group\">\n <button mat-stroked-button color=\"primary\" (click)=\"openDiagramFor(c)\" matTooltip=\"Ver rela\u00C3\u00A7\u00C3\u00A3o no diagrama\"><mat-icon>schema</mat-icon><span>Diagrama</span></button>\n </span>\n <span class=\"spacer\"></span>\n <span class=\"action-group\">\n <button mat-icon-button color=\"warn\" (click)=\"removeConnection(i)\" matTooltip=\"Remover\"><mat-icon>delete</mat-icon></button>\n </span>\n </div>\n <div class=\"card-details\" matListItemLine [class.expanded]=\"isExpanded(i)\" [style.maxHeight]=\"isExpanded(i) ? '240px' : '0px'\" [style.opacity]=\"isExpanded(i) ? 1 : 0\" [style.pointerEvents]=\"isExpanded(i) ? 'auto' : 'none'\">\n <div class=\"stepper\">\n <div class=\"step from-step\">\n <div class=\"dot from\"></div>\n <div class=\"content\">\n <div class=\"label\">De</div>\n <div class=\"value\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step to-step\">\n <div class=\"dot to\"></div>\n <div class=\"content\">\n <div class=\"label\">Para</div>\n <div class=\"value\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step map-step\" *ngIf=\"c.map\">\n <div class=\"dot map\"></div>\n <div class=\"content\">\n <div class=\"label\">Map</div>\n <div class=\"value mono\">{{ c.map }}</div>\n </div>\n </div>\n </div>\n </div>\n </mat-list-item>\n </ng-container>\n </div>\n </div>\n </ng-container>\n <ng-template #flatList>\n <ng-container *ngFor=\"let c of filteredConnections(); let i = index\">\n <mat-list-item role=\"listitem\" (click)=\"startEdit(i, c)\" [class.selected]=\"selectedIndex()===i\">\n <div class=\"card-head\" matListItemTitle [matTooltip]=\"fromTooltip(c)\">\n <mat-icon class=\"comp-icon from\" [matTooltip]=\"fromIconTooltip(c)\">{{ fromIcon(c) }}</mat-icon>\n <span class=\"pdx-badge from\" aria-hidden=\"true\"></span>\n <span class=\"from\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</span>\n <span class=\"arrow\">\u00E2\u2020\u2019</span>\n <mat-icon class=\"comp-icon to\" [matTooltip]=\"toIconTooltip(c)\">{{ toIcon(c) }}</mat-icon>\n <span class=\"pdx-badge to\" aria-hidden=\"true\"></span>\n <span class=\"to\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"status-pill\" [class.ok]=\"connectionStatus(c)==='ok'\" [class.warn]=\"connectionStatus(c)==='warn'\" [class.err]=\"connectionStatus(c)==='err'\">\n <mat-icon *ngIf=\"connectionStatus(c)==='ok'\">check_circle</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='warn'\">warning</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='err'\">error</mat-icon>\n </span>\n <button mat-icon-button class=\"expand-btn\" [attr.aria-expanded]=\"isExpanded(i)\" (click)=\"toggleExpanded(i, $event)\" [matTooltip]=\"isExpanded(i) ? 'Recolher detalhes' : 'Expandir detalhes'\">\n <mat-icon>{{ isExpanded(i) ? 'expand_less' : 'expand_more' }}</mat-icon>\n </button>\n </div>\n <div class=\"meta\" *ngIf=\"showFriendly\" matListItemLine>\n <span class=\"hint\">{{ outputDescription(c) }}</span>\n <span class=\"sep\" *ngIf=\"outputDescription(c) && inputDescription(c)\">\u00E2\u20AC\u00A2</span>\n <span class=\"hint\">{{ inputDescription(c) }}</span>\n </div>\n <div class=\"map-chip\" *ngIf=\"c.map\" [matTooltip]=\"c.map || ''\" matListItemLine (click)=\"startEditByConn(c)\" title=\"Clique para editar Map\">\n <span class=\"pdx-badge map\" aria-hidden=\"true\"></span>\n <mat-icon class=\"map-icon\" inline>bolt</mat-icon>\n <span class=\"mono\">{{ c.map }}</span>\n </div>\n <div class=\"card-actions\" matListItemLine (click)=\"$event.stopPropagation()\">\n <span class=\"action-group\">\n <button mat-icon-button (click)=\"startEdit(i, c)\" matTooltip=\"Editar\"><mat-icon>settings</mat-icon></button>\n <button mat-icon-button (click)=\"duplicateConnection(i)\" matTooltip=\"Duplicar\"><mat-icon>content_copy</mat-icon></button>\n </span>\n <span class=\"action-group\">\n <button mat-stroked-button color=\"primary\" (click)=\"openDiagramFor(c)\" matTooltip=\"Ver rela\u00C3\u00A7\u00C3\u00A3o no diagrama\"><mat-icon>schema</mat-icon><span>Diagrama</span></button>\n </span>\n <span class=\"spacer\"></span>\n <span class=\"action-group\">\n <button mat-icon-button color=\"warn\" (click)=\"removeConnection(i)\" matTooltip=\"Remover\"><mat-icon>delete</mat-icon></button>\n </span>\n </div>\n <div class=\"card-details\" matListItemLine [class.expanded]=\"isExpanded(i)\" [style.maxHeight]=\"isExpanded(i) ? '240px' : '0px'\" [style.opacity]=\"isExpanded(i) ? 1 : 0\" [style.pointerEvents]=\"isExpanded(i) ? 'auto' : 'none'\">\n <div class=\"stepper\">\n <div class=\"step from-step\">\n <div class=\"dot from\"></div>\n <div class=\"content\">\n <div class=\"label\">De</div>\n <div class=\"value\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step to-step\">\n <div class=\"dot to\"></div>\n <div class=\"content\">\n <div class=\"label\">Para</div>\n <div class=\"value\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step map-step\" *ngIf=\"c.map\">\n <div class=\"dot map\"></div>\n <div class=\"content\">\n <div class=\"label\">Map</div>\n <div class=\"value mono\">{{ c.map }}</div>\n </div>\n </div>\n </div>\n </div>\n </mat-list-item>\n </ng-container>\n </ng-template>\n </mat-list>\n <ng-template #noConns>\n <div class=\"pdx-empty-list\" role=\"status\" aria-live=\"polite\">\n <mat-icon>link_off</mat-icon>\n <div>Nenhuma conex\u00C3\u00A3o definida. Use \"Nova conex\u00C3\u00A3o\".</div>\n </div>\n </ng-template>\n </div>\n\n <div class=\"pdx-conn-editor\" role=\"form\" aria-label=\"Editor de conex\u00C3\u00A3o\">\n <ng-container *ngIf=\"selectedIndex() >= 0; else emptyEditor\">\n <div class=\"form-grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Origem (widget.key)</mat-label>\n <input matInput [ngModel]=\"selectedSourcePrimaryValue()\" (ngModelChange)=\"setSelectedSourcePrimaryValue($event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" *ngIf=\"showSelectedSourceSecondary()\">\n <mat-label>Evento (output)</mat-label>\n <input matInput [ngModel]=\"selectedSourceSecondaryValue()\" (ngModelChange)=\"setSelectedSourceSecondaryValue($event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Destino (widget.key)</mat-label>\n <input matInput [ngModel]=\"selectedTargetPrimaryValue()\" (ngModelChange)=\"setSelectedTargetPrimaryValue($event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" *ngIf=\"showSelectedTargetSecondary()\">\n <mat-label>Input</mat-label>\n <input matInput [ngModel]=\"selectedTargetSecondaryValue()\" (ngModelChange)=\"setSelectedTargetSecondaryValue($event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-span\">\n <mat-label>Map (opcional)</mat-label>\n <input matInput [ngModel]=\"selectedMapValue()\" (ngModelChange)=\"setSelectedMapValue($event)\" [placeholder]=\"mapPlaceholder\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Ex.: payload.id ou ${payload.id}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"full-span\">\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\"><mat-icon [praxisIcon]=\"'save'\"></mat-icon>Salvar</button>\n </div>\n </div>\n </ng-container>\n <ng-template #emptyEditor>\n <div class=\"pdx-empty-editor\">Selecione uma conex\u00C3\u00A3o para editar.</div>\n </ng-template>\n </div>\n </div>\n</div>\n", styles: [".pdx-conn-root{display:grid;gap:12px;color:var(--md-sys-color-on-surface)}.pdx-conn-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.pdx-conn-title{font-weight:600}.pdx-conn-count{color:var(--md-sys-color-on-surface-variant)}.pdx-conn-search{width:260px}.pdx-conn-select{width:160px}.pdx-conn-group{width:170px}.pdx-spacer{flex:1}.pdx-diagram-cta span{margin-left:6px}.pdx-conn-grid{display:grid;grid-template-columns:1.4fr 1fr;gap:12px}.pdx-conn-list{height:540px;overflow:auto;padding:8px 0;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface)}.pdx-conn-list .mat-mdc-list-item.mdc-list-item{height:auto;padding:10px 8px 8px}.pdx-conn-list .mat-mdc-list-item.selected{background:var(--md-sys-color-primary-container)}.card-head{display:flex;align-items:center;gap:6px}.card-head .spacer{flex:1}.card-head .comp-icon{opacity:.85}.card-head .expand-btn{margin-left:4px}.status-pill{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px}.status-pill.ok{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.status-pill.warn{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.status-pill.err{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.map-chip{display:inline-flex;align-items:center;gap:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.map-chip .map-icon{font-size:16px}.pdx-conn-editor{min-height:540px;padding:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface)}.pdx-empty-list{padding:16px;display:grid;place-items:center;gap:6px;opacity:.8}.pdx-empty-editor{padding:16px;opacity:.75}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;align-content:start}.full-span{grid-column:1/-1}.stepper{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;padding:8px 0}.step{display:grid;grid-template-columns:12px 1fr;gap:8px;align-items:start}.dot{width:12px;height:12px;border-radius:50%;margin-top:5px}.dot.from{background:var(--md-sys-color-primary-container)}.dot.to{background:var(--md-sys-color-secondary-container)}.dot.map{background:var(--md-sys-color-tertiary-container)}.group-block{padding:6px 6px 10px}.group-header{display:flex;gap:8px;align-items:center;padding:8px 0}.group-title{font-weight:600}.group-count{opacity:.75}.group-list{display:grid;gap:6px}.pdx-badge{width:10px;height:10px;border-radius:50%;display:inline-block}.pdx-badge.from{background:var(--md-sys-color-primary-container)}.pdx-badge.to{background:var(--md-sys-color-secondary-container)}.pdx-badge.map{background:var(--md-sys-color-tertiary-container)}.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:12px}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}@media(max-width:960px){.pdx-conn-grid{grid-template-columns:1fr}.pdx-conn-list,.pdx-conn-editor{min-height:360px}.pdx-conn-search,.pdx-conn-select,.pdx-conn-group{width:100%}}\n"] }]
|
|
889
|
+
args: [{ selector: 'praxis-connection-builder', standalone: true, imports: [CommonModule, FormsModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatButtonModule, MatIconModule, MatListModule, MatDialogModule, MatAutocompleteModule, MatMenuModule, MatTabsModule, MatTooltipModule, MatProgressBarModule, MatCheckboxModule, MatSnackBarModule, ScrollingModule, PraxisIconDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pdx-conn-root\" role=\"region\" aria-label=\"Construtor de Conex\u00C3\u00B5es\">\n <div class=\"pdx-conn-head\">\n <span class=\"pdx-conn-title\">Conex\u00C3\u00B5es</span>\n <span class=\"pdx-conn-count\" aria-label=\"Conex\u00C3\u00B5es filtradas e total\">{{ filteredConnections().length }} / {{ connections().length }}</span>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-search\">\n <mat-label>Buscar</mat-label>\n <input matInput [(ngModel)]=\"filterText\" placeholder=\"Origem, Destino, Input, Map...\" />\n <button mat-icon-button matSuffix *ngIf=\"filterText\" (click)=\"filterText='';\" aria-label=\"Limpar busca\"><mat-icon [praxisIcon]=\"'close'\"></mat-icon></button>\n </mat-form-field>\n <span class=\"pdx-spacer\"></span>\n <button mat-button (click)=\"toggleShowOnlyIssues()\" [color]=\"showOnlyIssues ? 'primary': undefined\" aria-label=\"Somente avisos/erros\"><mat-icon [praxisIcon]=\"'report_problem'\"></mat-icon> Alertas</button>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-select\">\n <mat-label>Ordenar por</mat-label>\n <mat-select [(ngModel)]=\"sortBy\" (ngModelChange)=\"setSortBy($event)\">\n <mat-option value=\"from\">Origem</mat-option>\n <mat-option value=\"to\">Destino</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"pdx-conn-group\">\n <mat-label>Agrupar por</mat-label>\n <mat-select [(ngModel)]=\"groupBy\" (ngModelChange)=\"onGroupByChange()\">\n <mat-option value=\"none\">Nenhum</mat-option>\n <mat-option value=\"from\">Origem</mat-option>\n <mat-option value=\"to\">Destino</mat-option>\n <mat-option value=\"event\">Evento</mat-option>\n </mat-select>\n </mat-form-field>\n <button mat-icon-button [color]=\"showFriendly ? 'primary' : undefined\" (click)=\"toggleFriendly()\" aria-label=\"Alternar nome amig\u00C3\u00A1vel/t\u00C3\u00A9cnico\"><mat-icon [praxisIcon]=\"'translate'\"></mat-icon></button>\n <button mat-stroked-button (click)=\"createNew()\" aria-label=\"Nova conex\u00C3\u00A3o\"><mat-icon [praxisIcon]=\"'add'\"></mat-icon> Nova conex\u00C3\u00A3o</button>\n <button mat-flat-button color=\"accent\" class=\"pdx-diagram-cta\" (click)=\"openDiagramFullscreen()\" aria-label=\"Abrir diagrama em tela cheia\" matTooltip=\"Visualizar conex\u00C3\u00B5es em grafo\"><mat-icon [praxisIcon]=\"'schema'\"></mat-icon><span>Diagrama</span></button>\n </div>\n\n <div class=\"pdx-conn-grid\">\n <div class=\"pdx-conn-list\" role=\"list\" aria-label=\"Lista de conex\u00C3\u00B5es\" cdkScrollable>\n <mat-list *ngIf=\"filteredConnections().length; else noConns\">\n <ng-container *ngIf=\"groupBy!=='none'; else flatList\">\n <div class=\"group-block\" *ngFor=\"let g of groupedConnections(); let gi = index\">\n <div class=\"group-header\">\n <span class=\"group-title\">{{ g.label }}</span>\n <span class=\"group-count\">{{ g.list.length }}</span>\n </div>\n <mat-divider></mat-divider>\n <div class=\"group-list\">\n <ng-container *ngFor=\"let c of g.list; let i = index\">\n <mat-list-item role=\"listitem\" (click)=\"startEdit(i, c)\" [class.selected]=\"selectedIndex()===i\">\n <div class=\"card-head\" matListItemTitle [matTooltip]=\"fromTooltip(c)\">\n <mat-icon class=\"comp-icon from\" [matTooltip]=\"fromIconTooltip(c)\">{{ fromIcon(c) }}</mat-icon>\n <span class=\"pdx-badge from\" aria-hidden=\"true\"></span>\n <span class=\"from\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</span>\n <span class=\"arrow\">\u00E2\u2020\u2019</span>\n <mat-icon class=\"comp-icon to\" [matTooltip]=\"toIconTooltip(c)\">{{ toIcon(c) }}</mat-icon>\n <span class=\"pdx-badge to\" aria-hidden=\"true\"></span>\n <span class=\"to\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"status-pill\" [class.ok]=\"connectionStatus(c)==='ok'\" [class.warn]=\"connectionStatus(c)==='warn'\" [class.err]=\"connectionStatus(c)==='err'\">\n <mat-icon *ngIf=\"connectionStatus(c)==='ok'\">check_circle</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='warn'\">warning</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='err'\">error</mat-icon>\n </span>\n <button mat-icon-button class=\"expand-btn\" [attr.aria-expanded]=\"isExpanded(i)\" (click)=\"toggleExpanded(i, $event)\" [matTooltip]=\"isExpanded(i) ? 'Recolher detalhes' : 'Expandir detalhes'\">\n <mat-icon>{{ isExpanded(i) ? 'expand_less' : 'expand_more' }}</mat-icon>\n </button>\n </div>\n <div class=\"meta\" *ngIf=\"showFriendly\" matListItemLine>\n <span class=\"hint\">{{ outputDescription(c) }}</span>\n <span class=\"sep\" *ngIf=\"outputDescription(c) && inputDescription(c)\">\u00E2\u20AC\u00A2</span>\n <span class=\"hint\">{{ inputDescription(c) }}</span>\n </div>\n <div class=\"map-chip\" *ngIf=\"c.map\" [matTooltip]=\"c.map || ''\" matListItemLine (click)=\"startEditByConn(c)\" title=\"Clique para editar Map\">\n <span class=\"pdx-badge map\" aria-hidden=\"true\"></span>\n <mat-icon class=\"map-icon\" inline>bolt</mat-icon>\n <span class=\"mono\">{{ c.map }}</span>\n </div>\n <div class=\"card-actions\" (click)=\"$event.stopPropagation()\">\n <span class=\"action-group\">\n <button mat-icon-button (click)=\"startEdit(i, c)\" matTooltip=\"Editar\"><mat-icon>settings</mat-icon></button>\n <button mat-icon-button (click)=\"duplicateConnection(i)\" matTooltip=\"Duplicar\"><mat-icon>content_copy</mat-icon></button>\n </span>\n <span class=\"action-group\">\n <button mat-stroked-button color=\"primary\" (click)=\"openDiagramFor(c)\" matTooltip=\"Ver rela\u00C3\u00A7\u00C3\u00A3o no diagrama\"><mat-icon>schema</mat-icon><span>Diagrama</span></button>\n </span>\n <span class=\"spacer\"></span>\n <span class=\"action-group\">\n <button mat-icon-button color=\"warn\" (click)=\"removeConnection(i)\" matTooltip=\"Remover\"><mat-icon>delete</mat-icon></button>\n </span>\n </div>\n <div class=\"card-details\" [class.expanded]=\"isExpanded(i)\" [style.maxHeight]=\"isExpanded(i) ? '240px' : '0px'\" [style.opacity]=\"isExpanded(i) ? 1 : 0\" [style.pointerEvents]=\"isExpanded(i) ? 'auto' : 'none'\">\n <div class=\"stepper\">\n <div class=\"step from-step\">\n <div class=\"dot from\"></div>\n <div class=\"content\">\n <div class=\"label\">De</div>\n <div class=\"value\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step to-step\">\n <div class=\"dot to\"></div>\n <div class=\"content\">\n <div class=\"label\">Para</div>\n <div class=\"value\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step map-step\" *ngIf=\"c.map\">\n <div class=\"dot map\"></div>\n <div class=\"content\">\n <div class=\"label\">Map</div>\n <div class=\"value mono\">{{ c.map }}</div>\n </div>\n </div>\n </div>\n </div>\n </mat-list-item>\n </ng-container>\n </div>\n </div>\n </ng-container>\n <ng-template #flatList>\n <ng-container *ngFor=\"let c of filteredConnections(); let i = index\">\n <mat-list-item role=\"listitem\" (click)=\"startEdit(i, c)\" [class.selected]=\"selectedIndex()===i\">\n <div class=\"card-head\" matListItemTitle [matTooltip]=\"fromTooltip(c)\">\n <mat-icon class=\"comp-icon from\" [matTooltip]=\"fromIconTooltip(c)\">{{ fromIcon(c) }}</mat-icon>\n <span class=\"pdx-badge from\" aria-hidden=\"true\"></span>\n <span class=\"from\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</span>\n <span class=\"arrow\">\u00E2\u2020\u2019</span>\n <mat-icon class=\"comp-icon to\" [matTooltip]=\"toIconTooltip(c)\">{{ toIcon(c) }}</mat-icon>\n <span class=\"pdx-badge to\" aria-hidden=\"true\"></span>\n <span class=\"to\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"status-pill\" [class.ok]=\"connectionStatus(c)==='ok'\" [class.warn]=\"connectionStatus(c)==='warn'\" [class.err]=\"connectionStatus(c)==='err'\">\n <mat-icon *ngIf=\"connectionStatus(c)==='ok'\">check_circle</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='warn'\">warning</mat-icon>\n <mat-icon *ngIf=\"connectionStatus(c)==='err'\">error</mat-icon>\n </span>\n <button mat-icon-button class=\"expand-btn\" [attr.aria-expanded]=\"isExpanded(i)\" (click)=\"toggleExpanded(i, $event)\" [matTooltip]=\"isExpanded(i) ? 'Recolher detalhes' : 'Expandir detalhes'\">\n <mat-icon>{{ isExpanded(i) ? 'expand_less' : 'expand_more' }}</mat-icon>\n </button>\n </div>\n <div class=\"meta\" *ngIf=\"showFriendly\" matListItemLine>\n <span class=\"hint\">{{ outputDescription(c) }}</span>\n <span class=\"sep\" *ngIf=\"outputDescription(c) && inputDescription(c)\">\u00E2\u20AC\u00A2</span>\n <span class=\"hint\">{{ inputDescription(c) }}</span>\n </div>\n <div class=\"map-chip\" *ngIf=\"c.map\" [matTooltip]=\"c.map || ''\" matListItemLine (click)=\"startEditByConn(c)\" title=\"Clique para editar Map\">\n <span class=\"pdx-badge map\" aria-hidden=\"true\"></span>\n <mat-icon class=\"map-icon\" inline>bolt</mat-icon>\n <span class=\"mono\">{{ c.map }}</span>\n </div>\n <div class=\"card-actions\" (click)=\"$event.stopPropagation()\">\n <span class=\"action-group\">\n <button mat-icon-button (click)=\"startEdit(i, c)\" matTooltip=\"Editar\"><mat-icon>settings</mat-icon></button>\n <button mat-icon-button (click)=\"duplicateConnection(i)\" matTooltip=\"Duplicar\"><mat-icon>content_copy</mat-icon></button>\n </span>\n <span class=\"action-group\">\n <button mat-stroked-button color=\"primary\" (click)=\"openDiagramFor(c)\" matTooltip=\"Ver rela\u00C3\u00A7\u00C3\u00A3o no diagrama\"><mat-icon>schema</mat-icon><span>Diagrama</span></button>\n </span>\n <span class=\"spacer\"></span>\n <span class=\"action-group\">\n <button mat-icon-button color=\"warn\" (click)=\"removeConnection(i)\" matTooltip=\"Remover\"><mat-icon>delete</mat-icon></button>\n </span>\n </div>\n <div class=\"card-details\" [class.expanded]=\"isExpanded(i)\" [style.maxHeight]=\"isExpanded(i) ? '240px' : '0px'\" [style.opacity]=\"isExpanded(i) ? 1 : 0\" [style.pointerEvents]=\"isExpanded(i) ? 'auto' : 'none'\">\n <div class=\"stepper\">\n <div class=\"step from-step\">\n <div class=\"dot from\"></div>\n <div class=\"content\">\n <div class=\"label\">De</div>\n <div class=\"value\">{{ showFriendly ? fromFriendly(c) : fromLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step to-step\">\n <div class=\"dot to\"></div>\n <div class=\"content\">\n <div class=\"label\">Para</div>\n <div class=\"value\">{{ showFriendly ? toFriendly(c) : toLabel(c) }}</div>\n </div>\n </div>\n <div class=\"step map-step\" *ngIf=\"c.map\">\n <div class=\"dot map\"></div>\n <div class=\"content\">\n <div class=\"label\">Map</div>\n <div class=\"value mono\">{{ c.map }}</div>\n </div>\n </div>\n </div>\n </div>\n </mat-list-item>\n </ng-container>\n </ng-template>\n </mat-list>\n <ng-template #noConns>\n <div class=\"pdx-empty-list\" role=\"status\" aria-live=\"polite\">\n <mat-icon>link_off</mat-icon>\n <div>Nenhuma conex\u00C3\u00A3o definida. Use \"Nova conex\u00C3\u00A3o\".</div>\n </div>\n </ng-template>\n </div>\n\n <div class=\"pdx-conn-editor\" role=\"form\" aria-label=\"Editor de conex\u00C3\u00A3o\">\n <ng-container *ngIf=\"selectedIndex() >= 0; else emptyEditor\">\n <div class=\"form-grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo da origem</mat-label>\n <mat-select [ngModel]=\"selectedSourceKind()\" (ngModelChange)=\"setSelectedSourceKind($event)\">\n <mat-option value=\"widget\">Widget</mat-option>\n <mat-option value=\"state\">State</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ selectedSourceKind() === 'widget' ? 'Origem (widget.key)' : 'Origem (state)' }}</mat-label>\n <mat-select [ngModel]=\"selectedSourcePrimaryValue()\" (ngModelChange)=\"setSelectedSourcePrimaryValue($event)\">\n <ng-container *ngIf=\"selectedSourceKind() === 'widget'; else sourceStateOptions\">\n <mat-option *ngFor=\"let option of widgetOptions()\" [value]=\"option.key\">\n {{ option.label }}\n </mat-option>\n </ng-container>\n <ng-template #sourceStateOptions>\n <mat-option *ngFor=\"let option of availableStateOptions()\" [value]=\"option.value\">\n {{ option.label }}\n </mat-option>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" *ngIf=\"showSelectedSourceSecondary()\">\n <mat-label>Evento (output)</mat-label>\n <mat-select [ngModel]=\"selectedSourceSecondaryValue()\" (ngModelChange)=\"setSelectedSourceSecondaryValue($event)\">\n <mat-option *ngFor=\"let output of availableOutputsForSelectedWidget()\" [value]=\"output\">{{ output }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"endpoint-hint full-span\" *ngIf=\"selectedSourceKind() === 'state' && selectedSourcePrimaryValue()\">\n <mat-icon>{{ stateOptionIcon(selectedSourcePrimaryValue()) }}</mat-icon>\n <span>{{ stateOptionLabel(selectedSourcePrimaryValue()) }}</span>\n </div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Tipo do destino</mat-label>\n <mat-select [ngModel]=\"selectedTargetKind()\" (ngModelChange)=\"setSelectedTargetKind($event)\">\n <mat-option value=\"widget\">Widget</mat-option>\n <mat-option value=\"state\">State</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ selectedTargetKind() === 'widget' ? 'Destino (widget.key)' : 'Destino (state)' }}</mat-label>\n <mat-select [ngModel]=\"selectedTargetPrimaryValue()\" (ngModelChange)=\"setSelectedTargetPrimaryValue($event)\">\n <ng-container *ngIf=\"selectedTargetKind() === 'widget'; else targetStateOptions\">\n <mat-option *ngFor=\"let option of widgetOptions()\" [value]=\"option.key\">\n {{ option.label }}\n </mat-option>\n </ng-container>\n <ng-template #targetStateOptions>\n <mat-option *ngFor=\"let option of availableStateOptions()\" [value]=\"option.value\">\n {{ option.label }}\n </mat-option>\n </ng-template>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" *ngIf=\"showSelectedTargetSecondary()\">\n <mat-label>Input</mat-label>\n <mat-select [ngModel]=\"selectedTargetSecondaryValue()\" (ngModelChange)=\"setSelectedTargetSecondaryValue($event)\">\n <mat-option *ngFor=\"let input of availableInputsForSelectedWidget()\" [value]=\"input\">{{ input }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"endpoint-hint full-span\" *ngIf=\"selectedTargetKind() === 'state' && selectedTargetPrimaryValue()\">\n <mat-icon>{{ stateOptionIcon(selectedTargetPrimaryValue()) }}</mat-icon>\n <span>{{ stateOptionLabel(selectedTargetPrimaryValue()) }}</span>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-span\">\n <mat-label>Map (opcional)</mat-label>\n <input matInput [ngModel]=\"selectedMapValue()\" (ngModelChange)=\"setSelectedMapValue($event)\" [placeholder]=\"mapPlaceholder\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Ex.: payload.id ou ${payload.id}.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"full-span\">\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\"><mat-icon [praxisIcon]=\"'save'\"></mat-icon>Salvar</button>\n </div>\n </div>\n </ng-container>\n <ng-template #emptyEditor>\n <div class=\"pdx-empty-editor\">Selecione uma conex\u00C3\u00A3o para editar.</div>\n </ng-template>\n </div>\n </div>\n</div>\n", styles: [".pdx-conn-root{display:grid;gap:12px;color:var(--md-sys-color-on-surface)}.pdx-conn-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.pdx-conn-title{font-weight:600}.pdx-conn-count{color:var(--md-sys-color-on-surface-variant)}.pdx-conn-search{width:260px}.pdx-conn-select{width:160px}.pdx-conn-group{width:170px}.pdx-spacer{flex:1}.pdx-diagram-cta span{margin-left:6px}.pdx-conn-grid{display:grid;grid-template-columns:1.4fr 1fr;gap:12px}.pdx-conn-list{height:540px;overflow:auto;padding:8px 0;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface)}.pdx-conn-list .mat-mdc-list-item.mdc-list-item{height:auto;padding:10px 8px 8px}.pdx-conn-list .mat-mdc-list-item.selected{background:var(--md-sys-color-primary-container)}.card-head{display:flex;align-items:center;gap:6px}.card-head .spacer{flex:1}.card-head .comp-icon{opacity:.85}.card-head .expand-btn{margin-left:4px}.status-pill{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px}.status-pill.ok{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.status-pill.warn{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.status-pill.err{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.map-chip{display:inline-flex;align-items:center;gap:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.map-chip .map-icon{font-size:16px}.pdx-conn-editor{min-height:540px;padding:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface)}.pdx-empty-list{padding:16px;display:grid;place-items:center;gap:6px;opacity:.8}.pdx-empty-editor{padding:16px;opacity:.75}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;align-content:start}.full-span{grid-column:1/-1}.endpoint-hint{display:flex;align-items:center;gap:8px;min-height:44px;padding:0 12px;border-radius:12px;color:var(--md-sys-color-on-surface-variant);background:color-mix(in srgb,var(--md-sys-color-secondary-container) 35%,transparent)}.endpoint-hint mat-icon{color:var(--md-sys-color-secondary)}.stepper{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;padding:8px 0}.step{display:grid;grid-template-columns:12px 1fr;gap:8px;align-items:start}.dot{width:12px;height:12px;border-radius:50%;margin-top:5px}.dot.from{background:var(--md-sys-color-primary-container)}.dot.to{background:var(--md-sys-color-secondary-container)}.dot.map{background:var(--md-sys-color-tertiary-container)}.group-block{padding:6px 6px 10px}.group-header{display:flex;gap:8px;align-items:center;padding:8px 0}.group-title{font-weight:600}.group-count{opacity:.75}.group-list{display:grid;gap:6px}.pdx-badge{width:10px;height:10px;border-radius:50%;display:inline-block}.pdx-badge.from{background:var(--md-sys-color-primary-container)}.pdx-badge.to{background:var(--md-sys-color-secondary-container)}.pdx-badge.map{background:var(--md-sys-color-tertiary-container)}.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:12px}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}@media(max-width:960px){.pdx-conn-grid{grid-template-columns:1fr}.pdx-conn-list,.pdx-conn-editor{min-height:360px}.pdx-conn-search,.pdx-conn-select,.pdx-conn-group{width:100%}}\n"] }]
|
|
750
890
|
}], ctorParameters: () => [{ type: i1.MatDialog }, { type: i1$1.ComponentMetadataRegistry }, { type: i3$1.MatSnackBar }], propDecorators: { page: [{
|
|
751
891
|
type: Input
|
|
752
892
|
}], widgets: [{
|
|
@@ -3050,6 +3190,7 @@ class GraphMapperService {
|
|
|
3050
3190
|
const colWidth = 240;
|
|
3051
3191
|
const rowHeight = 96;
|
|
3052
3192
|
const gap = 28;
|
|
3193
|
+
const structuredState = this.normalizeState(page?.state);
|
|
3053
3194
|
let leftY = 0;
|
|
3054
3195
|
let rightY = 0;
|
|
3055
3196
|
const stateNodeIds = new Map();
|
|
@@ -3186,17 +3327,71 @@ class GraphMapperService {
|
|
|
3186
3327
|
}
|
|
3187
3328
|
}
|
|
3188
3329
|
}
|
|
3330
|
+
let structureY = Math.max(leftY, rightY) + gap * 2;
|
|
3331
|
+
const structureX = gap;
|
|
3332
|
+
for (const group of page?.grouping || []) {
|
|
3333
|
+
const label = group.kind === 'tabs'
|
|
3334
|
+
? `${group.kind}: ${group.id} (${group.tabs.length} tabs)`
|
|
3335
|
+
: `${group.kind}: ${group.id}`;
|
|
3336
|
+
const widgetCount = group.kind === 'tabs'
|
|
3337
|
+
? group.tabs.reduce((total, tab) => total + tab.widgetKeys.length, 0)
|
|
3338
|
+
: group.widgetKeys.length;
|
|
3339
|
+
const node = {
|
|
3340
|
+
id: `group:${group.id}`,
|
|
3341
|
+
label,
|
|
3342
|
+
icon: group.kind === 'tabs' ? 'tabs' : group.kind === 'hero' ? 'web_asset' : group.kind === 'rail' ? 'view_sidebar' : 'dashboard_customize',
|
|
3343
|
+
type: `page-group:${group.kind}`,
|
|
3344
|
+
parentId: null,
|
|
3345
|
+
collapsed: true,
|
|
3346
|
+
bounds: { x: structureX, y: structureY, width: colWidth, height: 72 },
|
|
3347
|
+
ports: [],
|
|
3348
|
+
};
|
|
3349
|
+
nodes.push(node);
|
|
3350
|
+
nodeById.set(node.id, node);
|
|
3351
|
+
structureY += 72 + gap;
|
|
3352
|
+
if (widgetCount > 0) {
|
|
3353
|
+
node.label = `${node.label} • ${widgetCount} widgets`;
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
for (const device of ['desktop', 'tablet', 'mobile']) {
|
|
3357
|
+
const variant = page?.deviceLayouts?.[device];
|
|
3358
|
+
if (!variant)
|
|
3359
|
+
continue;
|
|
3360
|
+
const detail = [];
|
|
3361
|
+
if (variant.layout?.orientation)
|
|
3362
|
+
detail.push(variant.layout.orientation);
|
|
3363
|
+
if (variant.layout?.columns)
|
|
3364
|
+
detail.push(`${variant.layout.columns} cols`);
|
|
3365
|
+
if (variant.groupingOverrides?.length)
|
|
3366
|
+
detail.push(`${variant.groupingOverrides.length} grouping overrides`);
|
|
3367
|
+
if (variant.widgetOverrides && Object.keys(variant.widgetOverrides).length) {
|
|
3368
|
+
detail.push(`${Object.keys(variant.widgetOverrides).length} widget overrides`);
|
|
3369
|
+
}
|
|
3370
|
+
const node = {
|
|
3371
|
+
id: `device:${device}`,
|
|
3372
|
+
label: `device: ${device}${detail.length ? ` • ${detail.join(' • ')}` : ''}`,
|
|
3373
|
+
icon: device === 'desktop' ? 'desktop_windows' : device === 'tablet' ? 'tablet_mac' : 'smartphone',
|
|
3374
|
+
type: 'page-device-layout',
|
|
3375
|
+
parentId: null,
|
|
3376
|
+
collapsed: true,
|
|
3377
|
+
bounds: { x: structureX + colWidth + gap, y: structureY, width: colWidth + 40, height: 72 },
|
|
3378
|
+
ports: [],
|
|
3379
|
+
};
|
|
3380
|
+
nodes.push(node);
|
|
3381
|
+
nodeById.set(node.id, node);
|
|
3382
|
+
structureY += 72 + gap;
|
|
3383
|
+
}
|
|
3189
3384
|
// Second pass: map edges from page.connections
|
|
3190
3385
|
const conns = (page?.connections || []);
|
|
3191
3386
|
for (let i = 0; i < conns.length; i++) {
|
|
3192
3387
|
const c = conns[i];
|
|
3193
3388
|
const fromState = !this.isWidgetSource(c.from)
|
|
3194
|
-
? this.ensureStateNode(stateNodeIds, nodeById, nodes,
|
|
3389
|
+
? this.ensureStateNode(stateNodeIds, nodeById, nodes, structuredState, c.from.state, 'output', colWidth, gap, leftY)
|
|
3195
3390
|
: null;
|
|
3196
3391
|
const fromNodeId = this.isWidgetSource(c.from) ? c.from.widget : fromState.nodeId;
|
|
3197
3392
|
const fromPortId = this.isWidgetSource(c.from) ? `out:${c.from.output}` : 'out:state';
|
|
3198
3393
|
const toState = !this.isWidgetTarget(c.to)
|
|
3199
|
-
? this.ensureStateNode(stateNodeIds, nodeById, nodes,
|
|
3394
|
+
? this.ensureStateNode(stateNodeIds, nodeById, nodes, structuredState, c.to.state, 'input', colWidth, gap, rightY)
|
|
3200
3395
|
: null;
|
|
3201
3396
|
let toNodeId = this.isWidgetTarget(c.to) ? c.to.widget : toState.nodeId;
|
|
3202
3397
|
let toPortId = this.isWidgetTarget(c.to) ? `in:${c.to.input}` : 'in:state';
|
|
@@ -3232,13 +3427,20 @@ class GraphMapperService {
|
|
|
3232
3427
|
from: { nodeId: fromNodeId, portId: fromPortId },
|
|
3233
3428
|
to: { nodeId: toNodeId, portId: toPortId },
|
|
3234
3429
|
label: c.map || undefined,
|
|
3235
|
-
meta: {
|
|
3430
|
+
meta: {
|
|
3431
|
+
map: c.map,
|
|
3432
|
+
bindingOrder: this.isWidgetTarget(c.to) ? c.to.bindingOrder : undefined,
|
|
3433
|
+
...(friendlyPath ? { friendlyPath } : {}),
|
|
3434
|
+
fromLabel: this.connectionEndpointLabel(c.from, widgets, structuredState),
|
|
3435
|
+
toLabel: this.connectionEndpointLabel(c.to, widgets, structuredState),
|
|
3436
|
+
connectionKind: this.connectionKind(c),
|
|
3437
|
+
},
|
|
3236
3438
|
};
|
|
3237
3439
|
edges.push(edge);
|
|
3238
3440
|
}
|
|
3239
3441
|
return { nodes, edges };
|
|
3240
3442
|
}
|
|
3241
|
-
ensureStateNode(stateNodeIds, nodeById, nodes,
|
|
3443
|
+
ensureStateNode(stateNodeIds, nodeById, nodes, structuredState, statePath, kind, colWidth, gap, offsetY) {
|
|
3242
3444
|
const key = `${kind}:${statePath}`;
|
|
3243
3445
|
const existing = stateNodeIds.get(key);
|
|
3244
3446
|
if (existing)
|
|
@@ -3246,11 +3448,12 @@ class GraphMapperService {
|
|
|
3246
3448
|
const nodeId = `state:${kind}:${encodeURIComponent(statePath)}`;
|
|
3247
3449
|
const portId = kind === 'output' ? 'out:state' : 'in:state';
|
|
3248
3450
|
const x = kind === 'output' ? gap : colWidth + gap * 3;
|
|
3249
|
-
const structuredState = this.normalizeState(page?.state);
|
|
3250
3451
|
const isDerived = !!structuredState?.derived?.[statePath];
|
|
3452
|
+
const description = structuredState?.derived?.[statePath]?.description || structuredState?.schema?.[statePath]?.description;
|
|
3453
|
+
const titlePrefix = isDerived ? 'Derived State' : 'Page State';
|
|
3251
3454
|
const node = {
|
|
3252
3455
|
id: nodeId,
|
|
3253
|
-
label: `${
|
|
3456
|
+
label: description ? `${titlePrefix}: ${description}` : `${titlePrefix}: ${statePath}`,
|
|
3254
3457
|
icon: isDerived ? 'function' : 'account_tree',
|
|
3255
3458
|
type: isDerived ? 'page-derived-state' : 'page-state',
|
|
3256
3459
|
parentId: null,
|
|
@@ -3259,7 +3462,7 @@ class GraphMapperService {
|
|
|
3259
3462
|
ports: [{
|
|
3260
3463
|
id: portId,
|
|
3261
3464
|
label: statePath,
|
|
3262
|
-
description: statePath,
|
|
3465
|
+
description: description ? `${description}\n${statePath}` : statePath,
|
|
3263
3466
|
kind,
|
|
3264
3467
|
anchor: { x: kind === 'output' ? x : x + colWidth, y: gap + offsetY + 24 },
|
|
3265
3468
|
}],
|
|
@@ -3274,7 +3477,29 @@ class GraphMapperService {
|
|
|
3274
3477
|
return null;
|
|
3275
3478
|
if ('values' in state || 'schema' in state || 'derived' in state)
|
|
3276
3479
|
return state;
|
|
3277
|
-
return
|
|
3480
|
+
return { values: state };
|
|
3481
|
+
}
|
|
3482
|
+
connectionEndpointLabel(endpoint, widgets, structuredState) {
|
|
3483
|
+
if ('widget' in endpoint) {
|
|
3484
|
+
const widget = widgets.find((item) => item.key === endpoint.widget);
|
|
3485
|
+
const meta = widget ? this.registry.get(widget.definition.id) : null;
|
|
3486
|
+
if ('output' in endpoint)
|
|
3487
|
+
return `${meta?.friendlyName || endpoint.widget}.${endpoint.output}`;
|
|
3488
|
+
return `${meta?.friendlyName || endpoint.widget}.${endpoint.input}`;
|
|
3489
|
+
}
|
|
3490
|
+
const description = structuredState?.derived?.[endpoint.state]?.description || structuredState?.schema?.[endpoint.state]?.description;
|
|
3491
|
+
return description ? `${endpoint.state} - ${description}` : endpoint.state;
|
|
3492
|
+
}
|
|
3493
|
+
connectionKind(connection) {
|
|
3494
|
+
const fromWidget = this.isWidgetSource(connection.from);
|
|
3495
|
+
const toWidget = this.isWidgetTarget(connection.to);
|
|
3496
|
+
if (fromWidget && toWidget)
|
|
3497
|
+
return 'widget-to-widget';
|
|
3498
|
+
if (fromWidget && !toWidget)
|
|
3499
|
+
return 'widget-to-state';
|
|
3500
|
+
if (!fromWidget && toWidget)
|
|
3501
|
+
return 'state-to-widget';
|
|
3502
|
+
return 'state-to-state';
|
|
3278
3503
|
}
|
|
3279
3504
|
ensurePort(nodeById, nodeId, portId, kind, colWidth) {
|
|
3280
3505
|
const n = nodeById.get(nodeId);
|
|
@@ -3362,14 +3587,37 @@ class ConnectionGraphComponent {
|
|
|
3362
3587
|
return 'widget' in to;
|
|
3363
3588
|
}
|
|
3364
3589
|
fromLabel(c) {
|
|
3365
|
-
return this.isWidgetSource(c.from) ? `${c.from.widget}.${c.from.output}` : `state:${c.from.state}
|
|
3590
|
+
return this.currentEdge()?.meta?.fromLabel || (this.isWidgetSource(c.from) ? `${c.from.widget}.${c.from.output}` : `state:${c.from.state}`);
|
|
3366
3591
|
}
|
|
3367
3592
|
toLabel(c) {
|
|
3368
|
-
return this.isWidgetTarget(c.to) ? `${c.to.widget}.${c.to.input}` : `state:${c.to.state}
|
|
3593
|
+
return this.currentEdge()?.meta?.toLabel || (this.isWidgetTarget(c.to) ? `${c.to.widget}.${c.to.input}` : `state:${c.to.state}`);
|
|
3369
3594
|
}
|
|
3370
3595
|
bindingOrderLabel(c) {
|
|
3371
3596
|
return this.isWidgetTarget(c.to) && c.to.bindingOrder?.length ? c.to.bindingOrder.join(', ') : '';
|
|
3372
3597
|
}
|
|
3598
|
+
connectionKindSummary() {
|
|
3599
|
+
const counts = new Map();
|
|
3600
|
+
for (const edge of this.edges()) {
|
|
3601
|
+
const kind = edge.meta?.connectionKind;
|
|
3602
|
+
if (!kind)
|
|
3603
|
+
continue;
|
|
3604
|
+
counts.set(kind, (counts.get(kind) || 0) + 1);
|
|
3605
|
+
}
|
|
3606
|
+
return Array.from(counts.entries()).map(([kind, count]) => ({
|
|
3607
|
+
kind,
|
|
3608
|
+
count,
|
|
3609
|
+
label: this.connectionKindLabel(kind),
|
|
3610
|
+
}));
|
|
3611
|
+
}
|
|
3612
|
+
connectionKindLabel(kind) {
|
|
3613
|
+
switch (kind) {
|
|
3614
|
+
case 'widget-to-widget': return 'Widget -> Widget';
|
|
3615
|
+
case 'widget-to-state': return 'Widget -> State';
|
|
3616
|
+
case 'state-to-widget': return 'State -> Widget';
|
|
3617
|
+
case 'state-to-state': return 'State -> State';
|
|
3618
|
+
default: return 'Conexão';
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3373
3621
|
isPortConnected(nodeId, portId) {
|
|
3374
3622
|
const set = this.connectedPorts.get(nodeId);
|
|
3375
3623
|
return !!set && set.has(portId);
|
|
@@ -3508,11 +3756,10 @@ class ConnectionGraphComponent {
|
|
|
3508
3756
|
return routeElbow(from, to);
|
|
3509
3757
|
}
|
|
3510
3758
|
edgeTooltip(e) {
|
|
3511
|
-
const toNode = this.nodes().find(n => n.id === e.to.nodeId);
|
|
3512
|
-
const fromNode = this.nodes().find(n => n.id === e.from.nodeId);
|
|
3513
3759
|
const parts = [
|
|
3514
|
-
`
|
|
3515
|
-
`
|
|
3760
|
+
`Tipo: ${this.connectionKindLabel(e.meta?.connectionKind)}`,
|
|
3761
|
+
`De: ${e.meta?.fromLabel || e.from.nodeId}.${e.from.portId.replace('out:', '')}`,
|
|
3762
|
+
`Para: ${e.meta?.toLabel || e.to.nodeId}.${e.to.portId.replace('in:', '')}`,
|
|
3516
3763
|
];
|
|
3517
3764
|
if (e.meta && e.meta.friendlyPath)
|
|
3518
3765
|
parts.push(`Destino: ${e.meta.friendlyPath}`);
|
|
@@ -3708,6 +3955,7 @@ class ConnectionGraphComponent {
|
|
|
3708
3955
|
this.selectedEdgeIndex.set(index);
|
|
3709
3956
|
this.showDetails = true;
|
|
3710
3957
|
}
|
|
3958
|
+
currentEdge() { const i = this.selectedEdgeIndex(); return (i >= 0 ? this.edges()[i] : undefined); }
|
|
3711
3959
|
currentConn() { const i = this.selectedEdgeIndex(); return (i >= 0 ? this.connections()[i] : undefined); }
|
|
3712
3960
|
emitPageChange() {
|
|
3713
3961
|
const parsed = this.parsePage(this.page) || { widgets: this.widgets || [] };
|
|
@@ -3809,6 +4057,9 @@ class ConnectionGraphComponent {
|
|
|
3809
4057
|
</button>
|
|
3810
4058
|
<button mat-stroked-button color="primary" (click)="openBuilder()" aria-label="Editar no Builder"><mat-icon>tune</mat-icon><span>Editar no Builder</span></button>
|
|
3811
4059
|
<div class="cg-count" [attr.aria-label]="'Total de conexões: ' + connections().length">{{ connections().length }}</div>
|
|
4060
|
+
<div class="cg-kind-summary" *ngIf="connections().length">
|
|
4061
|
+
<span class="cg-kind-pill" *ngFor="let item of connectionKindSummary()">{{ item.label }}: {{ item.count }}</span>
|
|
4062
|
+
</div>
|
|
3812
4063
|
</div>
|
|
3813
4064
|
<div class="cg-canvas">
|
|
3814
4065
|
<!-- Edge details panel -->
|
|
@@ -3818,6 +4069,7 @@ class ConnectionGraphComponent {
|
|
|
3818
4069
|
<button mat-icon-button (click)="showDetails=false" aria-label="Fechar"><mat-icon>close</mat-icon></button>
|
|
3819
4070
|
</div>
|
|
3820
4071
|
<div class="details-body" *ngIf="currentConn() as c">
|
|
4072
|
+
<div><b>Tipo:</b> {{ connectionKindLabel(currentEdge()?.meta?.connectionKind) }}</div>
|
|
3821
4073
|
<div><b>De:</b> {{ fromLabel(c) }}</div>
|
|
3822
4074
|
<div><b>Para:</b> {{ toLabel(c) }}</div>
|
|
3823
4075
|
<div><b>map:</b> {{ c.map || 'payload' }}</div>
|
|
@@ -3922,7 +4174,7 @@ class ConnectionGraphComponent {
|
|
|
3922
4174
|
</div>
|
|
3923
4175
|
</div>
|
|
3924
4176
|
</div>
|
|
3925
|
-
`, isInline: true, styles: [".cg-root{display:flex;flex-direction:column;height:100%;width:100%}.cg-head{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--md-sys-color-surface);border-bottom:1px solid var(--md-sys-color-outline)}.cg-title{font-weight:600}.spacer{flex:1}.cg-count{color:var(--md-sys-color-on-surface-variant)}.cg-canvas{position:relative;flex:1;min-height:300px}svg{background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface)}.node-box{fill:var(--md-sys-color-surface-container);stroke:var(--md-sys-color-outline)}.node-title{fill:var(--md-sys-color-on-surface);font-size:14px;font-weight:600}.port-dot{fill:currentColor;r:6}.port-label{fill:var(--md-sys-color-on-surface-variant);font-size:13px}.edge{stroke:var(--md-sys-color-primary);stroke-width:2;fill:none}.edge.hover{stroke:var(--md-sys-color-tertiary)}.edge.temp{stroke-dasharray:4 4;opacity:.7}.edge-label{fill:var(--md-sys-color-on-surface);font-size:12px;dominant-baseline:middle;text-anchor:middle}.edge-label-bg{fill:var(--md-sys-color-surface);opacity:.72}.edge-toolbar .edge-toolbar-bg{fill:var(--md-sys-color-surface);stroke:var(--md-sys-color-outline)}.edge-toolbar .icon{fill:var(--md-sys-color-on-surface);font-size:12px;cursor:pointer;-webkit-user-select:none;user-select:none}.popover{position:absolute;transform:translate(-50%,-50%);z-index:10}.popover-body{background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;padding:8px;min-width:220px;box-shadow:var(--mat-elevation-level4)}.popover-title{font-weight:600;margin-bottom:8px}.popover-grid{display:grid;gap:6px;min-width:260px}.popover-chips{display:flex;gap:6px;flex-wrap:wrap}.popover-rows{display:grid;gap:6px}.popover-inline{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.popover-inline-field{display:inline-flex;align-items:center;gap:6px}.popover-body label{display:grid;gap:4px;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.popover-input{background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:6px;padding:4px 6px;font:inherit}.popover-input:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}.popover-input--short{width:90px}.popover-preview{font-size:12px;opacity:.8;color:var(--md-sys-color-on-surface-variant)}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.port-hit{fill:transparent;cursor:crosshair;height:24px}.cg-details{position:absolute;right:8px;top:48px;width:320px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;box-shadow:var(--mat-elevation-level3);z-index:12}.details-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;border-bottom:1px solid var(--md-sys-color-outline)}.details-title{font-weight:600}.details-body{padding:8px 10px;display:grid;gap:6px}.details-actions{display:flex;gap:8px;margin-top:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: DragConnectDirective, selector: "[dragConnect]", inputs: ["dragConnect"], outputs: ["connectDragStart", "connectDragMove", "connectDragEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4177
|
+
`, isInline: true, styles: [".cg-root{display:flex;flex-direction:column;height:100%;width:100%}.cg-head{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--md-sys-color-surface);border-bottom:1px solid var(--md-sys-color-outline)}.cg-title{font-weight:600}.spacer{flex:1}.cg-count{color:var(--md-sys-color-on-surface-variant)}.cg-kind-summary{display:flex;gap:8px;flex-wrap:wrap}.cg-kind-pill{padding:4px 10px;border-radius:999px;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);font-size:12px;font-weight:600}.cg-canvas{position:relative;flex:1;min-height:300px}svg{background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface)}.node-box{fill:var(--md-sys-color-surface-container);stroke:var(--md-sys-color-outline)}.node-title{fill:var(--md-sys-color-on-surface);font-size:14px;font-weight:600}.port-dot{fill:currentColor;r:6}.port-label{fill:var(--md-sys-color-on-surface-variant);font-size:13px}.edge{stroke:var(--md-sys-color-primary);stroke-width:2;fill:none}.edge.hover{stroke:var(--md-sys-color-tertiary)}.edge.temp{stroke-dasharray:4 4;opacity:.7}.edge-label{fill:var(--md-sys-color-on-surface);font-size:12px;dominant-baseline:middle;text-anchor:middle}.edge-label-bg{fill:var(--md-sys-color-surface);opacity:.72}.edge-toolbar .edge-toolbar-bg{fill:var(--md-sys-color-surface);stroke:var(--md-sys-color-outline)}.edge-toolbar .icon{fill:var(--md-sys-color-on-surface);font-size:12px;cursor:pointer;-webkit-user-select:none;user-select:none}.popover{position:absolute;transform:translate(-50%,-50%);z-index:10}.popover-body{background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;padding:8px;min-width:220px;box-shadow:var(--mat-elevation-level4)}.popover-title{font-weight:600;margin-bottom:8px}.popover-grid{display:grid;gap:6px;min-width:260px}.popover-chips{display:flex;gap:6px;flex-wrap:wrap}.popover-rows{display:grid;gap:6px}.popover-inline{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.popover-inline-field{display:inline-flex;align-items:center;gap:6px}.popover-body label{display:grid;gap:4px;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.popover-input{background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:6px;padding:4px 6px;font:inherit}.popover-input:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}.popover-input--short{width:90px}.popover-preview{font-size:12px;opacity:.8;color:var(--md-sys-color-on-surface-variant)}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.port-hit{fill:transparent;cursor:crosshair;height:24px}.cg-details{position:absolute;right:8px;top:48px;width:320px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;box-shadow:var(--mat-elevation-level3);z-index:12}.details-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;border-bottom:1px solid var(--md-sys-color-outline)}.details-title{font-weight:600}.details-body{padding:8px 10px;display:grid;gap:6px}.details-actions{display:flex;gap:8px;margin-top:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: DragConnectDirective, selector: "[dragConnect]", inputs: ["dragConnect"], outputs: ["connectDragStart", "connectDragMove", "connectDragEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3926
4178
|
}
|
|
3927
4179
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ConnectionGraphComponent, decorators: [{
|
|
3928
4180
|
type: Component,
|
|
@@ -3945,6 +4197,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3945
4197
|
</button>
|
|
3946
4198
|
<button mat-stroked-button color="primary" (click)="openBuilder()" aria-label="Editar no Builder"><mat-icon>tune</mat-icon><span>Editar no Builder</span></button>
|
|
3947
4199
|
<div class="cg-count" [attr.aria-label]="'Total de conexões: ' + connections().length">{{ connections().length }}</div>
|
|
4200
|
+
<div class="cg-kind-summary" *ngIf="connections().length">
|
|
4201
|
+
<span class="cg-kind-pill" *ngFor="let item of connectionKindSummary()">{{ item.label }}: {{ item.count }}</span>
|
|
4202
|
+
</div>
|
|
3948
4203
|
</div>
|
|
3949
4204
|
<div class="cg-canvas">
|
|
3950
4205
|
<!-- Edge details panel -->
|
|
@@ -3954,6 +4209,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3954
4209
|
<button mat-icon-button (click)="showDetails=false" aria-label="Fechar"><mat-icon>close</mat-icon></button>
|
|
3955
4210
|
</div>
|
|
3956
4211
|
<div class="details-body" *ngIf="currentConn() as c">
|
|
4212
|
+
<div><b>Tipo:</b> {{ connectionKindLabel(currentEdge()?.meta?.connectionKind) }}</div>
|
|
3957
4213
|
<div><b>De:</b> {{ fromLabel(c) }}</div>
|
|
3958
4214
|
<div><b>Para:</b> {{ toLabel(c) }}</div>
|
|
3959
4215
|
<div><b>map:</b> {{ c.map || 'payload' }}</div>
|
|
@@ -4058,7 +4314,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4058
4314
|
</div>
|
|
4059
4315
|
</div>
|
|
4060
4316
|
</div>
|
|
4061
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".cg-root{display:flex;flex-direction:column;height:100%;width:100%}.cg-head{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--md-sys-color-surface);border-bottom:1px solid var(--md-sys-color-outline)}.cg-title{font-weight:600}.spacer{flex:1}.cg-count{color:var(--md-sys-color-on-surface-variant)}.cg-canvas{position:relative;flex:1;min-height:300px}svg{background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface)}.node-box{fill:var(--md-sys-color-surface-container);stroke:var(--md-sys-color-outline)}.node-title{fill:var(--md-sys-color-on-surface);font-size:14px;font-weight:600}.port-dot{fill:currentColor;r:6}.port-label{fill:var(--md-sys-color-on-surface-variant);font-size:13px}.edge{stroke:var(--md-sys-color-primary);stroke-width:2;fill:none}.edge.hover{stroke:var(--md-sys-color-tertiary)}.edge.temp{stroke-dasharray:4 4;opacity:.7}.edge-label{fill:var(--md-sys-color-on-surface);font-size:12px;dominant-baseline:middle;text-anchor:middle}.edge-label-bg{fill:var(--md-sys-color-surface);opacity:.72}.edge-toolbar .edge-toolbar-bg{fill:var(--md-sys-color-surface);stroke:var(--md-sys-color-outline)}.edge-toolbar .icon{fill:var(--md-sys-color-on-surface);font-size:12px;cursor:pointer;-webkit-user-select:none;user-select:none}.popover{position:absolute;transform:translate(-50%,-50%);z-index:10}.popover-body{background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;padding:8px;min-width:220px;box-shadow:var(--mat-elevation-level4)}.popover-title{font-weight:600;margin-bottom:8px}.popover-grid{display:grid;gap:6px;min-width:260px}.popover-chips{display:flex;gap:6px;flex-wrap:wrap}.popover-rows{display:grid;gap:6px}.popover-inline{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.popover-inline-field{display:inline-flex;align-items:center;gap:6px}.popover-body label{display:grid;gap:4px;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.popover-input{background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:6px;padding:4px 6px;font:inherit}.popover-input:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}.popover-input--short{width:90px}.popover-preview{font-size:12px;opacity:.8;color:var(--md-sys-color-on-surface-variant)}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.port-hit{fill:transparent;cursor:crosshair;height:24px}.cg-details{position:absolute;right:8px;top:48px;width:320px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;box-shadow:var(--mat-elevation-level3);z-index:12}.details-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;border-bottom:1px solid var(--md-sys-color-outline)}.details-title{font-weight:600}.details-body{padding:8px 10px;display:grid;gap:6px}.details-actions{display:flex;gap:8px;margin-top:8px}\n"] }]
|
|
4317
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".cg-root{display:flex;flex-direction:column;height:100%;width:100%}.cg-head{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--md-sys-color-surface);border-bottom:1px solid var(--md-sys-color-outline)}.cg-title{font-weight:600}.spacer{flex:1}.cg-count{color:var(--md-sys-color-on-surface-variant)}.cg-kind-summary{display:flex;gap:8px;flex-wrap:wrap}.cg-kind-pill{padding:4px 10px;border-radius:999px;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);font-size:12px;font-weight:600}.cg-canvas{position:relative;flex:1;min-height:300px}svg{background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface)}.node-box{fill:var(--md-sys-color-surface-container);stroke:var(--md-sys-color-outline)}.node-title{fill:var(--md-sys-color-on-surface);font-size:14px;font-weight:600}.port-dot{fill:currentColor;r:6}.port-label{fill:var(--md-sys-color-on-surface-variant);font-size:13px}.edge{stroke:var(--md-sys-color-primary);stroke-width:2;fill:none}.edge.hover{stroke:var(--md-sys-color-tertiary)}.edge.temp{stroke-dasharray:4 4;opacity:.7}.edge-label{fill:var(--md-sys-color-on-surface);font-size:12px;dominant-baseline:middle;text-anchor:middle}.edge-label-bg{fill:var(--md-sys-color-surface);opacity:.72}.edge-toolbar .edge-toolbar-bg{fill:var(--md-sys-color-surface);stroke:var(--md-sys-color-outline)}.edge-toolbar .icon{fill:var(--md-sys-color-on-surface);font-size:12px;cursor:pointer;-webkit-user-select:none;user-select:none}.popover{position:absolute;transform:translate(-50%,-50%);z-index:10}.popover-body{background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;padding:8px;min-width:220px;box-shadow:var(--mat-elevation-level4)}.popover-title{font-weight:600;margin-bottom:8px}.popover-grid{display:grid;gap:6px;min-width:260px}.popover-chips{display:flex;gap:6px;flex-wrap:wrap}.popover-rows{display:grid;gap:6px}.popover-inline{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.popover-inline-field{display:inline-flex;align-items:center;gap:6px}.popover-body label{display:grid;gap:4px;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.popover-input{background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:6px;padding:4px 6px;font:inherit}.popover-input:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}.popover-input--short{width:90px}.popover-preview{font-size:12px;opacity:.8;color:var(--md-sys-color-on-surface-variant)}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.port-hit{fill:transparent;cursor:crosshair;height:24px}.cg-details{position:absolute;right:8px;top:48px;width:320px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;box-shadow:var(--mat-elevation-level3);z-index:12}.details-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;border-bottom:1px solid var(--md-sys-color-outline)}.details-title{font-weight:600}.details-body{padding:8px 10px;display:grid;gap:6px}.details-actions{display:flex;gap:8px;margin-top:8px}\n"] }]
|
|
4062
4318
|
}], ctorParameters: () => [{ type: GraphMapperService }, { type: i2$2.SettingsPanelService }, { type: undefined, decorators: [{
|
|
4063
4319
|
type: Optional
|
|
4064
4320
|
}, {
|
|
@@ -4084,6 +4340,93 @@ class ConnectionEditorComponent {
|
|
|
4084
4340
|
pageState = signal(undefined, ...(ngDevMode ? [{ debugName: "pageState" }] : []));
|
|
4085
4341
|
jsonControl = new FormControl('', { nonNullable: true });
|
|
4086
4342
|
jsonError = signal(null, ...(ngDevMode ? [{ debugName: "jsonError" }] : []));
|
|
4343
|
+
connectionSummary = computed(() => {
|
|
4344
|
+
const page = this.pageState();
|
|
4345
|
+
const connections = page?.connections || [];
|
|
4346
|
+
let widgetToWidget = 0;
|
|
4347
|
+
let widgetToState = 0;
|
|
4348
|
+
let stateToWidget = 0;
|
|
4349
|
+
let stateToState = 0;
|
|
4350
|
+
for (const connection of connections) {
|
|
4351
|
+
const fromWidget = 'widget' in connection.from;
|
|
4352
|
+
const toWidget = 'widget' in connection.to;
|
|
4353
|
+
if (fromWidget && toWidget)
|
|
4354
|
+
widgetToWidget += 1;
|
|
4355
|
+
if (fromWidget && !toWidget)
|
|
4356
|
+
widgetToState += 1;
|
|
4357
|
+
if (!fromWidget && toWidget)
|
|
4358
|
+
stateToWidget += 1;
|
|
4359
|
+
if (!fromWidget && !toWidget)
|
|
4360
|
+
stateToState += 1;
|
|
4361
|
+
}
|
|
4362
|
+
return {
|
|
4363
|
+
total: connections.length,
|
|
4364
|
+
widgetToWidget,
|
|
4365
|
+
widgetToState,
|
|
4366
|
+
stateToWidget,
|
|
4367
|
+
stateToState,
|
|
4368
|
+
};
|
|
4369
|
+
}, ...(ngDevMode ? [{ debugName: "connectionSummary" }] : []));
|
|
4370
|
+
stateSummary = computed(() => {
|
|
4371
|
+
const page = this.pageState();
|
|
4372
|
+
const normalized = this.normalizeState(page?.state);
|
|
4373
|
+
const items = new Map();
|
|
4374
|
+
for (const [path, config] of Object.entries(normalized.schema || {})) {
|
|
4375
|
+
items.set(path, {
|
|
4376
|
+
path,
|
|
4377
|
+
kind: 'primary',
|
|
4378
|
+
label: config.description ? `${path} - ${config.description}` : path,
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4381
|
+
for (const path of this.collectPaths(normalized.values || {})) {
|
|
4382
|
+
if (!items.has(path)) {
|
|
4383
|
+
items.set(path, { path, kind: 'primary', label: path });
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
for (const [path, config] of Object.entries(normalized.derived || {})) {
|
|
4387
|
+
items.set(path, {
|
|
4388
|
+
path,
|
|
4389
|
+
kind: 'derived',
|
|
4390
|
+
label: config.description ? `${path} - ${config.description}` : `${path} [derived]`,
|
|
4391
|
+
});
|
|
4392
|
+
}
|
|
4393
|
+
return Array.from(items.values()).sort((left, right) => left.path.localeCompare(right.path));
|
|
4394
|
+
}, ...(ngDevMode ? [{ debugName: "stateSummary" }] : []));
|
|
4395
|
+
primaryStateCount = computed(() => this.stateSummary().filter((item) => item.kind === 'primary').length, ...(ngDevMode ? [{ debugName: "primaryStateCount" }] : []));
|
|
4396
|
+
derivedStateCount = computed(() => this.stateSummary().filter((item) => item.kind === 'derived').length, ...(ngDevMode ? [{ debugName: "derivedStateCount" }] : []));
|
|
4397
|
+
groupingSummary = computed(() => {
|
|
4398
|
+
const page = this.pageState();
|
|
4399
|
+
return (page?.grouping || []).map((group) => ({
|
|
4400
|
+
id: group.id,
|
|
4401
|
+
kind: group.kind,
|
|
4402
|
+
label: group.kind === 'tabs'
|
|
4403
|
+
? `${group.id} - ${group.tabs.length} tabs`
|
|
4404
|
+
: `${group.id} - ${group.widgetKeys.length} widgets`,
|
|
4405
|
+
}));
|
|
4406
|
+
}, ...(ngDevMode ? [{ debugName: "groupingSummary" }] : []));
|
|
4407
|
+
deviceLayoutSummary = computed(() => {
|
|
4408
|
+
const page = this.pageState();
|
|
4409
|
+
const items = [];
|
|
4410
|
+
for (const device of ['desktop', 'tablet', 'mobile']) {
|
|
4411
|
+
const variant = page?.deviceLayouts?.[device];
|
|
4412
|
+
if (!variant)
|
|
4413
|
+
continue;
|
|
4414
|
+
const layoutBits = [];
|
|
4415
|
+
if (variant.layout?.orientation)
|
|
4416
|
+
layoutBits.push(variant.layout.orientation);
|
|
4417
|
+
if (variant.layout?.columns)
|
|
4418
|
+
layoutBits.push(`${variant.layout.columns} cols`);
|
|
4419
|
+
if (variant.groupingOverrides?.length)
|
|
4420
|
+
layoutBits.push(`${variant.groupingOverrides.length} grouping overrides`);
|
|
4421
|
+
if (variant.widgetOverrides && Object.keys(variant.widgetOverrides).length)
|
|
4422
|
+
layoutBits.push(`${Object.keys(variant.widgetOverrides).length} widget overrides`);
|
|
4423
|
+
items.push({
|
|
4424
|
+
device,
|
|
4425
|
+
label: layoutBits.join(' • ') || 'variant configured',
|
|
4426
|
+
});
|
|
4427
|
+
}
|
|
4428
|
+
return items;
|
|
4429
|
+
}, ...(ngDevMode ? [{ debugName: "deviceLayoutSummary" }] : []));
|
|
4087
4430
|
lastSerialized = '';
|
|
4088
4431
|
constructor() {
|
|
4089
4432
|
effect(() => {
|
|
@@ -4095,7 +4438,7 @@ class ConnectionEditorComponent {
|
|
|
4095
4438
|
this.jsonControl.setValue(serialized, { emitEvent: false });
|
|
4096
4439
|
this.jsonError.set(null);
|
|
4097
4440
|
}
|
|
4098
|
-
}
|
|
4441
|
+
});
|
|
4099
4442
|
this.jsonControl.valueChanges
|
|
4100
4443
|
.pipe(takeUntilDestroyed())
|
|
4101
4444
|
.subscribe((next) => this.onJsonInput(next));
|
|
@@ -4143,8 +4486,40 @@ class ConnectionEditorComponent {
|
|
|
4143
4486
|
}
|
|
4144
4487
|
return input;
|
|
4145
4488
|
}
|
|
4489
|
+
normalizeState(state) {
|
|
4490
|
+
if (!state)
|
|
4491
|
+
return { values: {} };
|
|
4492
|
+
const isStructured = typeof state === 'object'
|
|
4493
|
+
&& !Array.isArray(state)
|
|
4494
|
+
&& ('values' in state || 'schema' in state || 'derived' in state);
|
|
4495
|
+
if (isStructured) {
|
|
4496
|
+
const structured = state;
|
|
4497
|
+
return {
|
|
4498
|
+
values: this.clone(structured.values) || {},
|
|
4499
|
+
schema: this.clone(structured.schema),
|
|
4500
|
+
derived: this.clone(structured.derived),
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
return { values: this.clone(state) || {} };
|
|
4504
|
+
}
|
|
4505
|
+
collectPaths(source, prefix = '') {
|
|
4506
|
+
const paths = [];
|
|
4507
|
+
for (const [key, value] of Object.entries(source || {})) {
|
|
4508
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
4509
|
+
paths.push(path);
|
|
4510
|
+
if (value != null && typeof value === 'object' && !Array.isArray(value)) {
|
|
4511
|
+
paths.push(...this.collectPaths(value, path));
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
return paths;
|
|
4515
|
+
}
|
|
4516
|
+
clone(value) {
|
|
4517
|
+
if (value == null || typeof value !== 'object')
|
|
4518
|
+
return value;
|
|
4519
|
+
return JSON.parse(JSON.stringify(value));
|
|
4520
|
+
}
|
|
4146
4521
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ConnectionEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4147
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ConnectionEditorComponent, isStandalone: true, selector: "praxis-connection-editor", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, widgets: { classPropertyName: "widgets", publicName: "widgets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange" }, ngImport: i0, template: "<div class=\"conn-editor\">\n <mat-tab-group class=\"conn-tabs\">\n <mat-tab label=\"Construtor\">\n <praxis-connection-builder\n [page]=\"pageState() || undefined\"\n [widgets]=\"widgets() || undefined\"\n (pageChange)=\"onPageChange($event)\"\n ></praxis-connection-builder>\n </mat-tab>\n <mat-tab label=\"Diagrama\">\n <praxis-connection-graph\n [page]=\"pageState() || undefined\"\n [widgets]=\"widgets() || undefined\"\n (pageChange)=\"onPageChange($event)\"\n ></praxis-connection-graph>\n </mat-tab>\n <mat-tab label=\"JSON\">\n <div class=\"conn-json\">\n <mat-form-field class=\"json-field\" appearance=\"outline\">\n <mat-label>Pagina (JSON)</mat-label>\n <textarea matInput rows=\"16\" [formControl]=\"jsonControl\"></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use este JSON para copiar/colar a p\u00E1gina completa.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n @if (jsonError()) {\n <div class=\"json-error\" role=\"alert\">{{ jsonError() }}</div>\n }\n <div class=\"json-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"resetJson()\">Recarregar</button>\n </div>\n </div>\n </mat-tab>\n </mat-tab-group>\n</div>\n", styles: [".conn-editor{display:flex;flex-direction:column;height:100%;min-height:480px;color:var(--md-sys-color-on-surface)}.conn-tabs{flex:1}.conn-json{display:grid;gap:12px;padding:16px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.json-field{width:100%}.json-error{color:var(--md-sys-color-error);font-size:13px}.json-actions{display:flex;justify-content:flex-end}.conn-editor .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i9.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: ConnectionBuilderComponent, selector: "praxis-connection-builder", inputs: ["page", "widgets"], outputs: ["pageChange"] }, { kind: "component", type: ConnectionGraphComponent, selector: "praxis-connection-graph", inputs: ["page", "widgets"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4522
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ConnectionEditorComponent, isStandalone: true, selector: "praxis-connection-editor", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, widgets: { classPropertyName: "widgets", publicName: "widgets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange" }, ngImport: i0, template: "<div class=\"conn-editor\">\n <mat-tab-group class=\"conn-tabs\">\n <mat-tab label=\"Construtor\">\n <praxis-connection-builder\n [page]=\"pageState() || undefined\"\n [widgets]=\"widgets() || undefined\"\n (pageChange)=\"onPageChange($event)\"\n ></praxis-connection-builder>\n </mat-tab>\n <mat-tab label=\"Diagrama\">\n <praxis-connection-graph\n [page]=\"pageState() || undefined\"\n [widgets]=\"widgets() || undefined\"\n (pageChange)=\"onPageChange($event)\"\n ></praxis-connection-graph>\n </mat-tab>\n <mat-tab label=\"JSON\">\n <div class=\"conn-json\">\n <div class=\"json-summary\">\n <div class=\"summary-card\">\n <div class=\"summary-title\">Conex\u00F5es</div>\n <div class=\"summary-metrics\">\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().total }}</span>\n <span class=\"metric-label\">total</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().widgetToWidget }}</span>\n <span class=\"metric-label\">widget \u2192 widget</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().widgetToState }}</span>\n <span class=\"metric-label\">widget \u2192 state</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().stateToWidget }}</span>\n <span class=\"metric-label\">state \u2192 widget</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().stateToState }}</span>\n <span class=\"metric-label\">state \u2192 state</span>\n </div>\n </div>\n </div>\n\n <div class=\"summary-card\">\n <div class=\"summary-title\">Page State</div>\n <div class=\"summary-metrics compact\">\n <div class=\"metric\">\n <span class=\"metric-value\">{{ primaryStateCount() }}</span>\n <span class=\"metric-label\">paths prim\u00E1rios</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ derivedStateCount() }}</span>\n <span class=\"metric-label\">paths derivados</span>\n </div>\n </div>\n @if (stateSummary().length) {\n <div class=\"state-chip-list\">\n @for (item of stateSummary(); track item.path) {\n <div class=\"state-chip\" [class.derived]=\"item.kind === 'derived'\" [matTooltip]=\"item.label\">\n <mat-icon>{{ item.kind === 'derived' ? 'function' : 'account_tree' }}</mat-icon>\n <span>{{ item.path }}</span>\n </div>\n }\n </div>\n } @else {\n <div class=\"summary-empty\">Nenhum path de state detectado nesta p\u00E1gina.</div>\n }\n </div>\n <div class=\"summary-card\">\n <div class=\"summary-title\">Grouping</div>\n @if (groupingSummary().length) {\n <div class=\"state-chip-list\">\n @for (item of groupingSummary(); track item.id) {\n <div class=\"state-chip\" [matTooltip]=\"item.label\">\n <mat-icon>dashboard_customize</mat-icon>\n <span>{{ item.kind }}: {{ item.id }}</span>\n </div>\n }\n </div>\n } @else {\n <div class=\"summary-empty\">Nenhum grouping definido nesta p\u00C3\u00A1gina.</div>\n }\n </div>\n\n <div class=\"summary-card\">\n <div class=\"summary-title\">Device Layouts</div>\n @if (deviceLayoutSummary().length) {\n <div class=\"state-chip-list\">\n @for (item of deviceLayoutSummary(); track item.device) {\n <div class=\"state-chip\" [matTooltip]=\"item.label\">\n <mat-icon>devices</mat-icon>\n <span>{{ item.device }}</span>\n </div>\n }\n </div>\n } @else {\n <div class=\"summary-empty\">Nenhum override por device configurado.</div>\n }\n </div>\n </div>\n\n <mat-form-field class=\"json-field\" appearance=\"outline\">\n <mat-label>Pagina (JSON)</mat-label>\n <textarea matInput rows=\"16\" [formControl]=\"jsonControl\"></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use este JSON para copiar/colar a p\u00E1gina completa.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n @if (jsonError()) {\n <div class=\"json-error\" role=\"alert\">{{ jsonError() }}</div>\n }\n <div class=\"json-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"resetJson()\">Recarregar</button>\n </div>\n </div>\n </mat-tab>\n </mat-tab-group>\n</div>\n", styles: [".conn-editor{display:flex;flex-direction:column;height:100%;min-height:480px;color:var(--md-sys-color-on-surface)}.conn-tabs{flex:1}.conn-json{display:grid;gap:12px;padding:16px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.json-summary{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.summary-card{display:grid;gap:12px;padding:14px;border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 85%,transparent);background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface-container-low) 92%,transparent),transparent),var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface))}.summary-title{font-weight:600}.summary-metrics{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(92px,1fr))}.summary-metrics.compact{grid-template-columns:repeat(2,minmax(100px,1fr))}.metric{display:grid;gap:4px;padding:10px 12px;border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container) 88%,transparent)}.metric-value{font-size:20px;font-weight:700;line-height:1}.metric-label{color:var(--md-sys-color-on-surface-variant);font-size:12px}.state-chip-list{display:flex;flex-wrap:wrap;gap:8px}.state-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary-container) 70%,transparent);color:var(--md-sys-color-on-primary-container);font-size:12px}.state-chip.derived{background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 80%,transparent);color:var(--md-sys-color-on-tertiary-container)}.state-chip mat-icon{font-size:16px;width:16px;height:16px}.summary-empty{color:var(--md-sys-color-on-surface-variant);font-size:13px}.json-field{width:100%}.json-error{color:var(--md-sys-color-error);font-size:13px}.json-actions{display:flex;justify-content:flex-end}.conn-editor .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i9.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ConnectionBuilderComponent, selector: "praxis-connection-builder", inputs: ["page", "widgets"], outputs: ["pageChange"] }, { kind: "component", type: ConnectionGraphComponent, selector: "praxis-connection-graph", inputs: ["page", "widgets"], outputs: ["pageChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4148
4523
|
}
|
|
4149
4524
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ConnectionEditorComponent, decorators: [{
|
|
4150
4525
|
type: Component,
|
|
@@ -4156,9 +4531,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4156
4531
|
MatInputModule,
|
|
4157
4532
|
MatButtonModule,
|
|
4158
4533
|
MatIconModule,
|
|
4534
|
+
MatTooltipModule,
|
|
4159
4535
|
ConnectionBuilderComponent,
|
|
4160
4536
|
ConnectionGraphComponent,
|
|
4161
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"conn-editor\">\n <mat-tab-group class=\"conn-tabs\">\n <mat-tab label=\"Construtor\">\n <praxis-connection-builder\n [page]=\"pageState() || undefined\"\n [widgets]=\"widgets() || undefined\"\n (pageChange)=\"onPageChange($event)\"\n ></praxis-connection-builder>\n </mat-tab>\n <mat-tab label=\"Diagrama\">\n <praxis-connection-graph\n [page]=\"pageState() || undefined\"\n [widgets]=\"widgets() || undefined\"\n (pageChange)=\"onPageChange($event)\"\n ></praxis-connection-graph>\n </mat-tab>\n <mat-tab label=\"JSON\">\n <div class=\"conn-json\">\n <mat-form-field class=\"json-field\" appearance=\"outline\">\n <mat-label>Pagina (JSON)</mat-label>\n <textarea matInput rows=\"16\" [formControl]=\"jsonControl\"></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use este JSON para copiar/colar a p\u00E1gina completa.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n @if (jsonError()) {\n <div class=\"json-error\" role=\"alert\">{{ jsonError() }}</div>\n }\n <div class=\"json-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"resetJson()\">Recarregar</button>\n </div>\n </div>\n </mat-tab>\n </mat-tab-group>\n</div>\n", styles: [".conn-editor{display:flex;flex-direction:column;height:100%;min-height:480px;color:var(--md-sys-color-on-surface)}.conn-tabs{flex:1}.conn-json{display:grid;gap:12px;padding:16px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.json-field{width:100%}.json-error{color:var(--md-sys-color-error);font-size:13px}.json-actions{display:flex;justify-content:flex-end}.conn-editor .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}\n"] }]
|
|
4537
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"conn-editor\">\n <mat-tab-group class=\"conn-tabs\">\n <mat-tab label=\"Construtor\">\n <praxis-connection-builder\n [page]=\"pageState() || undefined\"\n [widgets]=\"widgets() || undefined\"\n (pageChange)=\"onPageChange($event)\"\n ></praxis-connection-builder>\n </mat-tab>\n <mat-tab label=\"Diagrama\">\n <praxis-connection-graph\n [page]=\"pageState() || undefined\"\n [widgets]=\"widgets() || undefined\"\n (pageChange)=\"onPageChange($event)\"\n ></praxis-connection-graph>\n </mat-tab>\n <mat-tab label=\"JSON\">\n <div class=\"conn-json\">\n <div class=\"json-summary\">\n <div class=\"summary-card\">\n <div class=\"summary-title\">Conex\u00F5es</div>\n <div class=\"summary-metrics\">\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().total }}</span>\n <span class=\"metric-label\">total</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().widgetToWidget }}</span>\n <span class=\"metric-label\">widget \u2192 widget</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().widgetToState }}</span>\n <span class=\"metric-label\">widget \u2192 state</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().stateToWidget }}</span>\n <span class=\"metric-label\">state \u2192 widget</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ connectionSummary().stateToState }}</span>\n <span class=\"metric-label\">state \u2192 state</span>\n </div>\n </div>\n </div>\n\n <div class=\"summary-card\">\n <div class=\"summary-title\">Page State</div>\n <div class=\"summary-metrics compact\">\n <div class=\"metric\">\n <span class=\"metric-value\">{{ primaryStateCount() }}</span>\n <span class=\"metric-label\">paths prim\u00E1rios</span>\n </div>\n <div class=\"metric\">\n <span class=\"metric-value\">{{ derivedStateCount() }}</span>\n <span class=\"metric-label\">paths derivados</span>\n </div>\n </div>\n @if (stateSummary().length) {\n <div class=\"state-chip-list\">\n @for (item of stateSummary(); track item.path) {\n <div class=\"state-chip\" [class.derived]=\"item.kind === 'derived'\" [matTooltip]=\"item.label\">\n <mat-icon>{{ item.kind === 'derived' ? 'function' : 'account_tree' }}</mat-icon>\n <span>{{ item.path }}</span>\n </div>\n }\n </div>\n } @else {\n <div class=\"summary-empty\">Nenhum path de state detectado nesta p\u00E1gina.</div>\n }\n </div>\n <div class=\"summary-card\">\n <div class=\"summary-title\">Grouping</div>\n @if (groupingSummary().length) {\n <div class=\"state-chip-list\">\n @for (item of groupingSummary(); track item.id) {\n <div class=\"state-chip\" [matTooltip]=\"item.label\">\n <mat-icon>dashboard_customize</mat-icon>\n <span>{{ item.kind }}: {{ item.id }}</span>\n </div>\n }\n </div>\n } @else {\n <div class=\"summary-empty\">Nenhum grouping definido nesta p\u00C3\u00A1gina.</div>\n }\n </div>\n\n <div class=\"summary-card\">\n <div class=\"summary-title\">Device Layouts</div>\n @if (deviceLayoutSummary().length) {\n <div class=\"state-chip-list\">\n @for (item of deviceLayoutSummary(); track item.device) {\n <div class=\"state-chip\" [matTooltip]=\"item.label\">\n <mat-icon>devices</mat-icon>\n <span>{{ item.device }}</span>\n </div>\n }\n </div>\n } @else {\n <div class=\"summary-empty\">Nenhum override por device configurado.</div>\n }\n </div>\n </div>\n\n <mat-form-field class=\"json-field\" appearance=\"outline\">\n <mat-label>Pagina (JSON)</mat-label>\n <textarea matInput rows=\"16\" [formControl]=\"jsonControl\"></textarea>\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n class=\"help-icon-button\"\n matTooltip=\"Use este JSON para copiar/colar a p\u00E1gina completa.\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </mat-form-field>\n @if (jsonError()) {\n <div class=\"json-error\" role=\"alert\">{{ jsonError() }}</div>\n }\n <div class=\"json-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"resetJson()\">Recarregar</button>\n </div>\n </div>\n </mat-tab>\n </mat-tab-group>\n</div>\n", styles: [".conn-editor{display:flex;flex-direction:column;height:100%;min-height:480px;color:var(--md-sys-color-on-surface)}.conn-tabs{flex:1}.conn-json{display:grid;gap:12px;padding:16px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.json-summary{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.summary-card{display:grid;gap:12px;padding:14px;border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 85%,transparent);background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface-container-low) 92%,transparent),transparent),var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface))}.summary-title{font-weight:600}.summary-metrics{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(92px,1fr))}.summary-metrics.compact{grid-template-columns:repeat(2,minmax(100px,1fr))}.metric{display:grid;gap:4px;padding:10px 12px;border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container) 88%,transparent)}.metric-value{font-size:20px;font-weight:700;line-height:1}.metric-label{color:var(--md-sys-color-on-surface-variant);font-size:12px}.state-chip-list{display:flex;flex-wrap:wrap;gap:8px}.state-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary-container) 70%,transparent);color:var(--md-sys-color-on-primary-container);font-size:12px}.state-chip.derived{background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 80%,transparent);color:var(--md-sys-color-on-tertiary-container)}.state-chip mat-icon{font-size:16px;width:16px;height:16px}.summary-empty{color:var(--md-sys-color-on-surface-variant);font-size:13px}.json-field{width:100%}.json-error{color:var(--md-sys-color-error);font-size:13px}.json-actions{display:flex;justify-content:flex-end}.conn-editor .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}\n"] }]
|
|
4162
4538
|
}], ctorParameters: () => [], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], widgets: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgets", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }] } });
|
|
4163
4539
|
|
|
4164
4540
|
class ConnectionEditorDialogComponent {
|
|
@@ -4253,11 +4629,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4253
4629
|
|
|
4254
4630
|
const DERIVED_OPERATORS = ['merge-objects', 'pick', 'omit'];
|
|
4255
4631
|
const MERGE_STRATEGIES = ['replace', 'merge', 'append', 'remove-keys'];
|
|
4632
|
+
const VALUE_MODES = ['json', 'string', 'number', 'boolean', 'null'];
|
|
4633
|
+
const GROUPING_KINDS = ['section', 'tabs', 'hero', 'rail'];
|
|
4256
4634
|
class PageConfigEditorComponent {
|
|
4257
4635
|
contextExample = 'Ex.: {"tenantId":"acme","locale":"pt-BR"}';
|
|
4258
4636
|
stateValuesExample = 'Ex.: {"query":{"filter":{"year":2026}},"selection":{"department":"Finance"}}';
|
|
4259
4637
|
derivedOperators = DERIVED_OPERATORS;
|
|
4260
4638
|
mergeStrategies = MERGE_STRATEGIES;
|
|
4639
|
+
valueModes = VALUE_MODES;
|
|
4640
|
+
groupingKinds = GROUPING_KINDS;
|
|
4641
|
+
layoutPresetEntries = Object.values(BUILTIN_PAGE_LAYOUT_PRESETS);
|
|
4642
|
+
themePresetEntries = Object.values(BUILTIN_PAGE_THEME_PRESETS);
|
|
4261
4643
|
page = input(null, ...(ngDevMode ? [{ debugName: "page" }] : []));
|
|
4262
4644
|
identity = input(null, ...(ngDevMode ? [{ debugName: "identity" }] : []));
|
|
4263
4645
|
gridsterOptions = input(null, ...(ngDevMode ? [{ debugName: "gridsterOptions" }] : []));
|
|
@@ -4268,12 +4650,23 @@ class PageConfigEditorComponent {
|
|
|
4268
4650
|
gap: new FormControl(null),
|
|
4269
4651
|
});
|
|
4270
4652
|
contextControl = new FormControl('', { nonNullable: true });
|
|
4653
|
+
layoutPresetControl = new FormControl('', { nonNullable: true });
|
|
4654
|
+
themePresetControl = new FormControl('', { nonNullable: true });
|
|
4655
|
+
groupingControl = new FormControl('', { nonNullable: true });
|
|
4656
|
+
deviceLayoutsControl = new FormControl('', { nonNullable: true });
|
|
4271
4657
|
stateValuesControl = new FormControl('', { nonNullable: true });
|
|
4658
|
+
groupingNodes = new FormArray([]);
|
|
4272
4659
|
schemaNodes = new FormArray([]);
|
|
4273
4660
|
derivedNodes = new FormArray([]);
|
|
4661
|
+
deviceDesktopForm = this.createDeviceLayoutForm();
|
|
4662
|
+
deviceTabletForm = this.createDeviceLayoutForm();
|
|
4663
|
+
deviceMobileForm = this.createDeviceLayoutForm();
|
|
4274
4664
|
contextError = signal(null, ...(ngDevMode ? [{ debugName: "contextError" }] : []));
|
|
4665
|
+
layoutError = signal(null, ...(ngDevMode ? [{ debugName: "layoutError" }] : []));
|
|
4275
4666
|
stateError = signal(null, ...(ngDevMode ? [{ debugName: "stateError" }] : []));
|
|
4276
4667
|
lastContext = '';
|
|
4668
|
+
lastGrouping = '';
|
|
4669
|
+
lastDeviceLayouts = '';
|
|
4277
4670
|
lastStateValues = '';
|
|
4278
4671
|
constructor() {
|
|
4279
4672
|
effect(() => {
|
|
@@ -4290,11 +4683,45 @@ class PageConfigEditorComponent {
|
|
|
4290
4683
|
this.contextControl.setValue(context, { emitEvent: false });
|
|
4291
4684
|
this.contextError.set(null);
|
|
4292
4685
|
}
|
|
4686
|
+
this.layoutPresetControl.setValue(next?.layoutPreset || '', { emitEvent: false });
|
|
4687
|
+
this.themePresetControl.setValue(next?.themePreset || '', { emitEvent: false });
|
|
4688
|
+
const grouping = next?.grouping ? JSON.stringify(next.grouping, null, 2) : '';
|
|
4689
|
+
if (grouping !== this.lastGrouping) {
|
|
4690
|
+
this.lastGrouping = grouping;
|
|
4691
|
+
this.groupingControl.setValue(grouping, { emitEvent: false });
|
|
4692
|
+
this.replaceGroupingNodes(next?.grouping);
|
|
4693
|
+
this.layoutError.set(null);
|
|
4694
|
+
}
|
|
4695
|
+
const deviceLayouts = next?.deviceLayouts ? JSON.stringify(next.deviceLayouts, null, 2) : '';
|
|
4696
|
+
if (deviceLayouts !== this.lastDeviceLayouts) {
|
|
4697
|
+
this.lastDeviceLayouts = deviceLayouts;
|
|
4698
|
+
this.deviceLayoutsControl.setValue(deviceLayouts, { emitEvent: false });
|
|
4699
|
+
this.hydrateDeviceLayoutControls(next?.deviceLayouts);
|
|
4700
|
+
this.layoutError.set(null);
|
|
4701
|
+
}
|
|
4293
4702
|
this.hydrateStateEditor(next?.state);
|
|
4294
4703
|
});
|
|
4295
4704
|
this.contextControl.valueChanges
|
|
4296
4705
|
.pipe(takeUntilDestroyed())
|
|
4297
4706
|
.subscribe(() => this.contextError.set(null));
|
|
4707
|
+
this.groupingControl.valueChanges
|
|
4708
|
+
.pipe(takeUntilDestroyed())
|
|
4709
|
+
.subscribe(() => this.layoutError.set(null));
|
|
4710
|
+
this.deviceLayoutsControl.valueChanges
|
|
4711
|
+
.pipe(takeUntilDestroyed())
|
|
4712
|
+
.subscribe(() => this.layoutError.set(null));
|
|
4713
|
+
this.groupingNodes.valueChanges
|
|
4714
|
+
.pipe(takeUntilDestroyed())
|
|
4715
|
+
.subscribe(() => this.layoutError.set(null));
|
|
4716
|
+
this.deviceDesktopForm.valueChanges
|
|
4717
|
+
.pipe(takeUntilDestroyed())
|
|
4718
|
+
.subscribe(() => this.layoutError.set(null));
|
|
4719
|
+
this.deviceTabletForm.valueChanges
|
|
4720
|
+
.pipe(takeUntilDestroyed())
|
|
4721
|
+
.subscribe(() => this.layoutError.set(null));
|
|
4722
|
+
this.deviceMobileForm.valueChanges
|
|
4723
|
+
.pipe(takeUntilDestroyed())
|
|
4724
|
+
.subscribe(() => this.layoutError.set(null));
|
|
4298
4725
|
this.stateValuesControl.valueChanges
|
|
4299
4726
|
.pipe(takeUntilDestroyed())
|
|
4300
4727
|
.subscribe(() => this.stateError.set(null));
|
|
@@ -4317,15 +4744,27 @@ class PageConfigEditorComponent {
|
|
|
4317
4744
|
removeDerivedNode(index) {
|
|
4318
4745
|
this.derivedNodes.removeAt(index);
|
|
4319
4746
|
}
|
|
4747
|
+
addGroupingNode(seed) {
|
|
4748
|
+
this.groupingNodes.push(this.createGroupingNodeGroup(seed));
|
|
4749
|
+
}
|
|
4750
|
+
removeGroupingNode(index) {
|
|
4751
|
+
this.groupingNodes.removeAt(index);
|
|
4752
|
+
}
|
|
4320
4753
|
apply() {
|
|
4321
4754
|
const next = this.parsePage(this.page()) || { widgets: [] };
|
|
4322
4755
|
const options = this.buildOptions(this.form.value);
|
|
4323
4756
|
const context = this.parseContext(this.contextControl.value);
|
|
4757
|
+
const grouping = this.buildGrouping();
|
|
4758
|
+
const deviceLayouts = this.buildDeviceLayouts();
|
|
4324
4759
|
const state = this.buildState();
|
|
4325
4760
|
if (context === null) {
|
|
4326
4761
|
this.contextError.set('JSON invalido');
|
|
4327
4762
|
return;
|
|
4328
4763
|
}
|
|
4764
|
+
if (grouping === null || deviceLayouts === null) {
|
|
4765
|
+
this.layoutError.set('JSON invalido');
|
|
4766
|
+
return;
|
|
4767
|
+
}
|
|
4329
4768
|
if (state === null) {
|
|
4330
4769
|
this.stateError.set('JSON invalido');
|
|
4331
4770
|
return;
|
|
@@ -4333,6 +4772,10 @@ class PageConfigEditorComponent {
|
|
|
4333
4772
|
const updated = {
|
|
4334
4773
|
...next,
|
|
4335
4774
|
options,
|
|
4775
|
+
layoutPreset: this.layoutPresetControl.value.trim() || undefined,
|
|
4776
|
+
themePreset: this.themePresetControl.value.trim() || undefined,
|
|
4777
|
+
grouping: grouping || undefined,
|
|
4778
|
+
deviceLayouts: deviceLayouts || undefined,
|
|
4336
4779
|
context: context || undefined,
|
|
4337
4780
|
state: state || undefined,
|
|
4338
4781
|
};
|
|
@@ -4350,8 +4793,167 @@ class PageConfigEditorComponent {
|
|
|
4350
4793
|
this.lastContext = context;
|
|
4351
4794
|
this.contextControl.setValue(context, { emitEvent: false });
|
|
4352
4795
|
this.contextError.set(null);
|
|
4796
|
+
this.layoutPresetControl.setValue(next?.layoutPreset || '', { emitEvent: false });
|
|
4797
|
+
this.themePresetControl.setValue(next?.themePreset || '', { emitEvent: false });
|
|
4798
|
+
const grouping = next?.grouping ? JSON.stringify(next.grouping, null, 2) : '';
|
|
4799
|
+
this.lastGrouping = grouping;
|
|
4800
|
+
this.groupingControl.setValue(grouping, { emitEvent: false });
|
|
4801
|
+
this.replaceGroupingNodes(next?.grouping);
|
|
4802
|
+
const deviceLayouts = next?.deviceLayouts ? JSON.stringify(next.deviceLayouts, null, 2) : '';
|
|
4803
|
+
this.lastDeviceLayouts = deviceLayouts;
|
|
4804
|
+
this.deviceLayoutsControl.setValue(deviceLayouts, { emitEvent: false });
|
|
4805
|
+
this.hydrateDeviceLayoutControls(next?.deviceLayouts);
|
|
4806
|
+
this.layoutError.set(null);
|
|
4353
4807
|
this.hydrateStateEditor(next?.state);
|
|
4354
4808
|
}
|
|
4809
|
+
replaceGroupingNodes(grouping) {
|
|
4810
|
+
this.groupingNodes.clear({ emitEvent: false });
|
|
4811
|
+
for (const group of grouping || []) {
|
|
4812
|
+
this.groupingNodes.push(this.createGroupingNodeGroup(group), { emitEvent: false });
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
createGroupingNodeGroup(seed) {
|
|
4816
|
+
const label = seed && 'label' in seed && typeof seed.label === 'string'
|
|
4817
|
+
? seed.label
|
|
4818
|
+
: '';
|
|
4819
|
+
return new FormGroup({
|
|
4820
|
+
kind: new FormControl(seed?.kind || 'section', { nonNullable: true }),
|
|
4821
|
+
id: new FormControl(seed?.id || '', { nonNullable: true, validators: [Validators.required] }),
|
|
4822
|
+
label: new FormControl(label, { nonNullable: true }),
|
|
4823
|
+
widgetKeysCsv: new FormControl(this.stringifyGroupingWidgetKeys(seed), { nonNullable: true }),
|
|
4824
|
+
layout: new FormControl(seed?.kind === 'section' ? seed.layout || '' : '', { nonNullable: true }),
|
|
4825
|
+
emphasis: new FormControl(seed?.kind === 'hero' ? seed.emphasis || '' : '', { nonNullable: true }),
|
|
4826
|
+
side: new FormControl(seed?.kind === 'rail' ? seed.side || 'right' : 'right', { nonNullable: true }),
|
|
4827
|
+
tabsJson: new FormControl(seed?.kind === 'tabs' ? JSON.stringify(seed.tabs || [], null, 2) : '', { nonNullable: true }),
|
|
4828
|
+
});
|
|
4829
|
+
}
|
|
4830
|
+
stringifyGroupingWidgetKeys(seed) {
|
|
4831
|
+
if (!seed || seed.kind === 'tabs')
|
|
4832
|
+
return '';
|
|
4833
|
+
return seed.widgetKeys.join(', ');
|
|
4834
|
+
}
|
|
4835
|
+
buildGrouping() {
|
|
4836
|
+
const grouping = [];
|
|
4837
|
+
for (const group of this.groupingNodes.controls) {
|
|
4838
|
+
const kind = group.controls.kind.value;
|
|
4839
|
+
const id = group.controls.id.value.trim();
|
|
4840
|
+
if (!id)
|
|
4841
|
+
continue;
|
|
4842
|
+
const label = group.controls.label.value.trim();
|
|
4843
|
+
if (kind === 'tabs') {
|
|
4844
|
+
const tabs = this.parseJson(group.controls.tabsJson.value, []);
|
|
4845
|
+
if (tabs === null)
|
|
4846
|
+
return null;
|
|
4847
|
+
grouping.push({
|
|
4848
|
+
kind: 'tabs',
|
|
4849
|
+
id,
|
|
4850
|
+
...(label ? { label } : {}),
|
|
4851
|
+
tabs,
|
|
4852
|
+
});
|
|
4853
|
+
continue;
|
|
4854
|
+
}
|
|
4855
|
+
const widgetKeys = group.controls.widgetKeysCsv.value
|
|
4856
|
+
.split(',')
|
|
4857
|
+
.map((item) => item.trim())
|
|
4858
|
+
.filter(Boolean);
|
|
4859
|
+
if (kind === 'section') {
|
|
4860
|
+
grouping.push({
|
|
4861
|
+
kind: 'section',
|
|
4862
|
+
id,
|
|
4863
|
+
...(label ? { label } : {}),
|
|
4864
|
+
widgetKeys,
|
|
4865
|
+
...(group.controls.layout.value ? { layout: group.controls.layout.value } : {}),
|
|
4866
|
+
});
|
|
4867
|
+
continue;
|
|
4868
|
+
}
|
|
4869
|
+
if (kind === 'hero') {
|
|
4870
|
+
grouping.push({
|
|
4871
|
+
kind: 'hero',
|
|
4872
|
+
id,
|
|
4873
|
+
widgetKeys,
|
|
4874
|
+
...(group.controls.emphasis.value ? { emphasis: group.controls.emphasis.value } : {}),
|
|
4875
|
+
});
|
|
4876
|
+
continue;
|
|
4877
|
+
}
|
|
4878
|
+
grouping.push({
|
|
4879
|
+
kind: 'rail',
|
|
4880
|
+
id,
|
|
4881
|
+
side: (group.controls.side.value || 'right'),
|
|
4882
|
+
widgetKeys,
|
|
4883
|
+
});
|
|
4884
|
+
}
|
|
4885
|
+
const json = grouping.length ? JSON.stringify(grouping, null, 2) : '';
|
|
4886
|
+
this.lastGrouping = json;
|
|
4887
|
+
this.groupingControl.setValue(json, { emitEvent: false });
|
|
4888
|
+
return grouping;
|
|
4889
|
+
}
|
|
4890
|
+
hydrateDeviceLayoutControls(deviceLayouts) {
|
|
4891
|
+
this.patchDeviceLayoutForm(this.deviceDesktopForm, deviceLayouts?.desktop);
|
|
4892
|
+
this.patchDeviceLayoutForm(this.deviceTabletForm, deviceLayouts?.tablet);
|
|
4893
|
+
this.patchDeviceLayoutForm(this.deviceMobileForm, deviceLayouts?.mobile);
|
|
4894
|
+
}
|
|
4895
|
+
buildDeviceLayouts() {
|
|
4896
|
+
const desktop = this.buildDeviceLayoutVariant(this.deviceDesktopForm);
|
|
4897
|
+
const tablet = this.buildDeviceLayoutVariant(this.deviceTabletForm);
|
|
4898
|
+
const mobile = this.buildDeviceLayoutVariant(this.deviceMobileForm);
|
|
4899
|
+
if (desktop === null || tablet === null || mobile === null)
|
|
4900
|
+
return null;
|
|
4901
|
+
const deviceLayouts = {};
|
|
4902
|
+
if (Object.keys(desktop).length)
|
|
4903
|
+
deviceLayouts.desktop = desktop;
|
|
4904
|
+
if (Object.keys(tablet).length)
|
|
4905
|
+
deviceLayouts.tablet = tablet;
|
|
4906
|
+
if (Object.keys(mobile).length)
|
|
4907
|
+
deviceLayouts.mobile = mobile;
|
|
4908
|
+
const json = Object.keys(deviceLayouts).length ? JSON.stringify(deviceLayouts, null, 2) : '';
|
|
4909
|
+
this.lastDeviceLayouts = json;
|
|
4910
|
+
this.deviceLayoutsControl.setValue(json, { emitEvent: false });
|
|
4911
|
+
return deviceLayouts;
|
|
4912
|
+
}
|
|
4913
|
+
createDeviceLayoutForm() {
|
|
4914
|
+
return new FormGroup({
|
|
4915
|
+
orientation: new FormControl('', { nonNullable: true }),
|
|
4916
|
+
columns: new FormControl(null),
|
|
4917
|
+
gap: new FormControl('', { nonNullable: true }),
|
|
4918
|
+
breakpointsJson: new FormControl('', { nonNullable: true }),
|
|
4919
|
+
groupingOverridesJson: new FormControl('', { nonNullable: true }),
|
|
4920
|
+
widgetOverridesJson: new FormControl('', { nonNullable: true }),
|
|
4921
|
+
});
|
|
4922
|
+
}
|
|
4923
|
+
patchDeviceLayoutForm(form, variant) {
|
|
4924
|
+
form.setValue({
|
|
4925
|
+
orientation: variant?.layout?.orientation || '',
|
|
4926
|
+
columns: variant?.layout?.columns ?? null,
|
|
4927
|
+
gap: variant?.layout?.gap || '',
|
|
4928
|
+
breakpointsJson: variant?.layout?.breakpoints ? JSON.stringify(variant.layout.breakpoints, null, 2) : '',
|
|
4929
|
+
groupingOverridesJson: variant?.groupingOverrides ? JSON.stringify(variant.groupingOverrides, null, 2) : '',
|
|
4930
|
+
widgetOverridesJson: variant?.widgetOverrides ? JSON.stringify(variant.widgetOverrides, null, 2) : '',
|
|
4931
|
+
}, { emitEvent: false });
|
|
4932
|
+
}
|
|
4933
|
+
buildDeviceLayoutVariant(form) {
|
|
4934
|
+
const breakpoints = this.parseJson(form.controls.breakpointsJson.value, {});
|
|
4935
|
+
const groupingOverrides = this.parseJson(form.controls.groupingOverridesJson.value, []);
|
|
4936
|
+
const widgetOverrides = this.parseJson(form.controls.widgetOverridesJson.value, {});
|
|
4937
|
+
if (breakpoints === null || groupingOverrides === null || widgetOverrides === null)
|
|
4938
|
+
return null;
|
|
4939
|
+
const layout = {};
|
|
4940
|
+
if (form.controls.orientation.value)
|
|
4941
|
+
layout.orientation = form.controls.orientation.value;
|
|
4942
|
+
if (form.controls.columns.value != null)
|
|
4943
|
+
layout.columns = form.controls.columns.value;
|
|
4944
|
+
if (form.controls.gap.value.trim())
|
|
4945
|
+
layout.gap = form.controls.gap.value.trim();
|
|
4946
|
+
if (Object.keys(breakpoints).length)
|
|
4947
|
+
layout.breakpoints = breakpoints;
|
|
4948
|
+
const variant = {};
|
|
4949
|
+
if (Object.keys(layout).length)
|
|
4950
|
+
variant.layout = layout;
|
|
4951
|
+
if (groupingOverrides.length)
|
|
4952
|
+
variant.groupingOverrides = groupingOverrides;
|
|
4953
|
+
if (Object.keys(widgetOverrides).length)
|
|
4954
|
+
variant.widgetOverrides = widgetOverrides;
|
|
4955
|
+
return variant;
|
|
4956
|
+
}
|
|
4355
4957
|
hydrateStateEditor(state) {
|
|
4356
4958
|
const normalized = this.normalizeState(state);
|
|
4357
4959
|
const nextValues = normalized.values ? JSON.stringify(normalized.values, null, 2) : '';
|
|
@@ -4377,10 +4979,13 @@ class PageConfigEditorComponent {
|
|
|
4377
4979
|
}
|
|
4378
4980
|
createSchemaNodeGroup(seed) {
|
|
4379
4981
|
const config = seed?.config || {};
|
|
4982
|
+
const initialValueConfig = this.describeValue(config.initial);
|
|
4380
4983
|
return new FormGroup({
|
|
4381
4984
|
path: new FormControl(seed?.path || '', { nonNullable: true, validators: [Validators.required] }),
|
|
4382
4985
|
type: new FormControl(config.type || '', { nonNullable: true }),
|
|
4383
|
-
|
|
4986
|
+
initialMode: new FormControl(initialValueConfig.mode, { nonNullable: true }),
|
|
4987
|
+
initialJson: new FormControl(initialValueConfig.json, { nonNullable: true }),
|
|
4988
|
+
initialScalar: new FormControl(initialValueConfig.scalar, { nonNullable: true }),
|
|
4384
4989
|
persist: new FormControl(typeof config.persist === 'boolean' ? String(config.persist) : '', { nonNullable: true }),
|
|
4385
4990
|
mergeStrategy: new FormControl(config.mergeStrategy || '', { nonNullable: true }),
|
|
4386
4991
|
description: new FormControl(config.description || '', { nonNullable: true }),
|
|
@@ -4401,6 +5006,12 @@ class PageConfigEditorComponent {
|
|
|
4401
5006
|
const keysCsv = Array.isArray(operatorOptions['keys'])
|
|
4402
5007
|
? operatorOptions['keys'].filter((item) => typeof item === 'string').join(', ')
|
|
4403
5008
|
: '';
|
|
5009
|
+
const templateValue = compute?.kind === 'template'
|
|
5010
|
+
? compute.value
|
|
5011
|
+
: compute?.kind === 'operator' && compute.operator === 'template'
|
|
5012
|
+
? compute.options?.['value']
|
|
5013
|
+
: undefined;
|
|
5014
|
+
const templateValueConfig = this.describeValue(templateValue);
|
|
4404
5015
|
return new FormGroup({
|
|
4405
5016
|
path: new FormControl(seed?.path || '', { nonNullable: true, validators: [Validators.required] }),
|
|
4406
5017
|
dependsOn: new FormControl((config?.dependsOn || []).join(', '), { nonNullable: true }),
|
|
@@ -4415,11 +5026,9 @@ class PageConfigEditorComponent {
|
|
|
4415
5026
|
options: new FormControl(compute?.kind === 'transformer'
|
|
4416
5027
|
? JSON.stringify(compute.options || {}, null, 2)
|
|
4417
5028
|
: '', { nonNullable: true }),
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
? JSON.stringify(compute.options?.['value'], null, 2)
|
|
4422
|
-
: '', { nonNullable: true }),
|
|
5029
|
+
templateMode: new FormControl(templateValueConfig.mode, { nonNullable: true }),
|
|
5030
|
+
templateJson: new FormControl(templateValueConfig.json, { nonNullable: true }),
|
|
5031
|
+
templateScalar: new FormControl(templateValueConfig.scalar, { nonNullable: true }),
|
|
4423
5032
|
description: new FormControl(config?.description || '', { nonNullable: true }),
|
|
4424
5033
|
});
|
|
4425
5034
|
}
|
|
@@ -4451,8 +5060,8 @@ class PageConfigEditorComponent {
|
|
|
4451
5060
|
const path = group.controls.path.value.trim();
|
|
4452
5061
|
if (!path)
|
|
4453
5062
|
continue;
|
|
4454
|
-
const initial = this.
|
|
4455
|
-
if (initial
|
|
5063
|
+
const initial = this.parseValueInput(group.controls.initialMode.value, group.controls.initialScalar.value, group.controls.initialJson.value, undefined);
|
|
5064
|
+
if (initial.invalid)
|
|
4456
5065
|
return null;
|
|
4457
5066
|
const node = {};
|
|
4458
5067
|
const type = group.controls.type.value.trim();
|
|
@@ -4461,8 +5070,8 @@ class PageConfigEditorComponent {
|
|
|
4461
5070
|
const description = group.controls.description.value.trim();
|
|
4462
5071
|
if (type)
|
|
4463
5072
|
node.type = type;
|
|
4464
|
-
if (initial !== undefined)
|
|
4465
|
-
node.initial = initial;
|
|
5073
|
+
if (initial.value !== undefined)
|
|
5074
|
+
node.initial = initial.value;
|
|
4466
5075
|
if (persist === 'true')
|
|
4467
5076
|
node.persist = true;
|
|
4468
5077
|
if (persist === 'false')
|
|
@@ -4489,10 +5098,10 @@ class PageConfigEditorComponent {
|
|
|
4489
5098
|
const description = group.controls.description.value.trim();
|
|
4490
5099
|
let compute;
|
|
4491
5100
|
if (computeKind === 'template') {
|
|
4492
|
-
const
|
|
4493
|
-
if (
|
|
5101
|
+
const templateValue = this.parseValueInput(group.controls.templateMode.value, group.controls.templateScalar.value, group.controls.templateJson.value, undefined);
|
|
5102
|
+
if (templateValue.invalid)
|
|
4494
5103
|
return null;
|
|
4495
|
-
compute = { kind: 'template', value };
|
|
5104
|
+
compute = { kind: 'template', value: templateValue.value };
|
|
4496
5105
|
}
|
|
4497
5106
|
else if (computeKind === 'expr') {
|
|
4498
5107
|
compute = { kind: 'expr', expression: group.controls.expression.value.trim() };
|
|
@@ -4565,6 +5174,26 @@ class PageConfigEditorComponent {
|
|
|
4565
5174
|
return null;
|
|
4566
5175
|
}
|
|
4567
5176
|
}
|
|
5177
|
+
parseGrouping(raw) {
|
|
5178
|
+
if (!raw.trim())
|
|
5179
|
+
return [];
|
|
5180
|
+
try {
|
|
5181
|
+
return JSON.parse(raw);
|
|
5182
|
+
}
|
|
5183
|
+
catch {
|
|
5184
|
+
return null;
|
|
5185
|
+
}
|
|
5186
|
+
}
|
|
5187
|
+
parseDeviceLayouts(raw) {
|
|
5188
|
+
if (!raw.trim())
|
|
5189
|
+
return {};
|
|
5190
|
+
try {
|
|
5191
|
+
return JSON.parse(raw);
|
|
5192
|
+
}
|
|
5193
|
+
catch {
|
|
5194
|
+
return null;
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
4568
5197
|
parseJson(raw, emptyValue) {
|
|
4569
5198
|
if (!raw.trim())
|
|
4570
5199
|
return emptyValue;
|
|
@@ -4575,6 +5204,43 @@ class PageConfigEditorComponent {
|
|
|
4575
5204
|
return null;
|
|
4576
5205
|
}
|
|
4577
5206
|
}
|
|
5207
|
+
parseValueInput(mode, scalarRaw, jsonRaw, emptyValue) {
|
|
5208
|
+
if (mode === 'json') {
|
|
5209
|
+
const parsed = this.parseJson(jsonRaw, emptyValue);
|
|
5210
|
+
return { value: parsed, invalid: parsed === null };
|
|
5211
|
+
}
|
|
5212
|
+
if (mode === 'string')
|
|
5213
|
+
return { value: scalarRaw, invalid: false };
|
|
5214
|
+
if (mode === 'number') {
|
|
5215
|
+
const trimmed = scalarRaw.trim();
|
|
5216
|
+
if (!trimmed)
|
|
5217
|
+
return { value: emptyValue, invalid: false };
|
|
5218
|
+
const parsed = Number(trimmed);
|
|
5219
|
+
return { value: parsed, invalid: Number.isNaN(parsed) };
|
|
5220
|
+
}
|
|
5221
|
+
if (mode === 'boolean')
|
|
5222
|
+
return { value: scalarRaw === 'true', invalid: false };
|
|
5223
|
+
if (mode === 'null')
|
|
5224
|
+
return { value: null, invalid: false };
|
|
5225
|
+
return { value: emptyValue, invalid: false };
|
|
5226
|
+
}
|
|
5227
|
+
describeValue(value) {
|
|
5228
|
+
if (value === undefined)
|
|
5229
|
+
return { mode: 'json', scalar: '', json: '' };
|
|
5230
|
+
if (value === null)
|
|
5231
|
+
return { mode: 'null', scalar: '', json: '' };
|
|
5232
|
+
if (typeof value === 'string')
|
|
5233
|
+
return { mode: 'string', scalar: value, json: '' };
|
|
5234
|
+
if (typeof value === 'number')
|
|
5235
|
+
return { mode: 'number', scalar: String(value), json: '' };
|
|
5236
|
+
if (typeof value === 'boolean')
|
|
5237
|
+
return { mode: 'boolean', scalar: String(value), json: '' };
|
|
5238
|
+
return {
|
|
5239
|
+
mode: 'json',
|
|
5240
|
+
scalar: '',
|
|
5241
|
+
json: JSON.stringify(value, null, 2),
|
|
5242
|
+
};
|
|
5243
|
+
}
|
|
4578
5244
|
buildOperatorOptions(group) {
|
|
4579
5245
|
const operator = group.controls.operator.value.trim();
|
|
4580
5246
|
if (operator === 'pick' || operator === 'omit') {
|
|
@@ -4645,6 +5311,226 @@ class PageConfigEditorComponent {
|
|
|
4645
5311
|
</mat-form-field>
|
|
4646
5312
|
</form>
|
|
4647
5313
|
|
|
5314
|
+
<div class="page-section">Layout Preset</div>
|
|
5315
|
+
<div class="grid-form">
|
|
5316
|
+
<mat-form-field appearance="outline">
|
|
5317
|
+
<mat-label>Layout preset</mat-label>
|
|
5318
|
+
<mat-select [formControl]="layoutPresetControl">
|
|
5319
|
+
<mat-option value="">Nenhum</mat-option>
|
|
5320
|
+
@for (preset of layoutPresetEntries; track preset.id) {
|
|
5321
|
+
<mat-option [value]="preset.id">{{ preset.label }}</mat-option>
|
|
5322
|
+
}
|
|
5323
|
+
</mat-select>
|
|
5324
|
+
</mat-form-field>
|
|
5325
|
+
<mat-form-field appearance="outline">
|
|
5326
|
+
<mat-label>Theme preset</mat-label>
|
|
5327
|
+
<mat-select [formControl]="themePresetControl">
|
|
5328
|
+
<mat-option value="">Padrão do layout</mat-option>
|
|
5329
|
+
@for (preset of themePresetEntries; track preset.id) {
|
|
5330
|
+
<mat-option [value]="preset.id">{{ preset.label }}</mat-option>
|
|
5331
|
+
}
|
|
5332
|
+
</mat-select>
|
|
5333
|
+
</mat-form-field>
|
|
5334
|
+
</div>
|
|
5335
|
+
|
|
5336
|
+
<div class="page-section">Grouping</div>
|
|
5337
|
+
<div class="state-group">
|
|
5338
|
+
<div class="state-group-head">
|
|
5339
|
+
<div class="state-group-title">Grouping</div>
|
|
5340
|
+
<button mat-stroked-button type="button" (click)="addGroupingNode()">
|
|
5341
|
+
<mat-icon>add</mat-icon>
|
|
5342
|
+
Adicionar grupo
|
|
5343
|
+
</button>
|
|
5344
|
+
</div>
|
|
5345
|
+
<div class="state-group-help">Organiza a página em section, tabs, hero e rail com intenção estrutural.</div>
|
|
5346
|
+
@if (!groupingNodes.length) {
|
|
5347
|
+
<div class="state-empty">Nenhum grupo configurado.</div>
|
|
5348
|
+
}
|
|
5349
|
+
<div class="state-list">
|
|
5350
|
+
@for (group of groupingNodes.controls; track $index) {
|
|
5351
|
+
<div class="state-card" [formGroup]="group">
|
|
5352
|
+
<div class="state-card-head">
|
|
5353
|
+
<div class="state-card-title">Grupo {{ $index + 1 }}</div>
|
|
5354
|
+
<button mat-icon-button type="button" (click)="removeGroupingNode($index)" aria-label="Remover grupo">
|
|
5355
|
+
<mat-icon>delete</mat-icon>
|
|
5356
|
+
</button>
|
|
5357
|
+
</div>
|
|
5358
|
+
<div class="state-card-grid">
|
|
5359
|
+
<mat-form-field appearance="outline">
|
|
5360
|
+
<mat-label>Kind</mat-label>
|
|
5361
|
+
<mat-select formControlName="kind">
|
|
5362
|
+
@for (kind of groupingKinds; track kind) {
|
|
5363
|
+
<mat-option [value]="kind">{{ kind }}</mat-option>
|
|
5364
|
+
}
|
|
5365
|
+
</mat-select>
|
|
5366
|
+
</mat-form-field>
|
|
5367
|
+
<mat-form-field appearance="outline">
|
|
5368
|
+
<mat-label>Id</mat-label>
|
|
5369
|
+
<input matInput formControlName="id" />
|
|
5370
|
+
</mat-form-field>
|
|
5371
|
+
<mat-form-field appearance="outline">
|
|
5372
|
+
<mat-label>Label</mat-label>
|
|
5373
|
+
<input matInput formControlName="label" />
|
|
5374
|
+
</mat-form-field>
|
|
5375
|
+
@if (group.controls.kind.value === 'section') {
|
|
5376
|
+
<mat-form-field appearance="outline">
|
|
5377
|
+
<mat-label>Layout</mat-label>
|
|
5378
|
+
<mat-select formControlName="layout">
|
|
5379
|
+
<mat-option value="">Padrão</mat-option>
|
|
5380
|
+
<mat-option value="stack">stack</mat-option>
|
|
5381
|
+
<mat-option value="grid">grid</mat-option>
|
|
5382
|
+
<mat-option value="row">row</mat-option>
|
|
5383
|
+
</mat-select>
|
|
5384
|
+
</mat-form-field>
|
|
5385
|
+
}
|
|
5386
|
+
@if (group.controls.kind.value === 'hero') {
|
|
5387
|
+
<mat-form-field appearance="outline">
|
|
5388
|
+
<mat-label>Emphasis</mat-label>
|
|
5389
|
+
<mat-select formControlName="emphasis">
|
|
5390
|
+
<mat-option value="">Padrão</mat-option>
|
|
5391
|
+
<mat-option value="high">high</mat-option>
|
|
5392
|
+
<mat-option value="medium">medium</mat-option>
|
|
5393
|
+
</mat-select>
|
|
5394
|
+
</mat-form-field>
|
|
5395
|
+
}
|
|
5396
|
+
@if (group.controls.kind.value === 'rail') {
|
|
5397
|
+
<mat-form-field appearance="outline">
|
|
5398
|
+
<mat-label>Side</mat-label>
|
|
5399
|
+
<mat-select formControlName="side">
|
|
5400
|
+
<mat-option value="left">left</mat-option>
|
|
5401
|
+
<mat-option value="right">right</mat-option>
|
|
5402
|
+
</mat-select>
|
|
5403
|
+
</mat-form-field>
|
|
5404
|
+
}
|
|
5405
|
+
@if (group.controls.kind.value === 'tabs') {
|
|
5406
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5407
|
+
<mat-label>Tabs (JSON)</mat-label>
|
|
5408
|
+
<textarea matInput rows="5" formControlName="tabsJson"></textarea>
|
|
5409
|
+
</mat-form-field>
|
|
5410
|
+
} @else {
|
|
5411
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5412
|
+
<mat-label>Widget keys (vírgula)</mat-label>
|
|
5413
|
+
<input matInput formControlName="widgetKeysCsv" />
|
|
5414
|
+
</mat-form-field>
|
|
5415
|
+
}
|
|
5416
|
+
</div>
|
|
5417
|
+
</div>
|
|
5418
|
+
}
|
|
5419
|
+
</div>
|
|
5420
|
+
</div>
|
|
5421
|
+
|
|
5422
|
+
<div class="page-section">Device Layouts</div>
|
|
5423
|
+
<div class="state-group">
|
|
5424
|
+
<div class="state-group-head">
|
|
5425
|
+
<div class="state-group-title">Device Layouts</div>
|
|
5426
|
+
</div>
|
|
5427
|
+
<div class="state-group-help">Overrides por device para layout, grouping e widgets com campos separados por intenção.</div>
|
|
5428
|
+
<div class="state-list">
|
|
5429
|
+
<div class="state-card" [formGroup]="deviceDesktopForm">
|
|
5430
|
+
<div class="state-card-head"><div class="state-card-title">Desktop</div></div>
|
|
5431
|
+
<div class="state-card-grid">
|
|
5432
|
+
<mat-form-field appearance="outline">
|
|
5433
|
+
<mat-label>Orientation</mat-label>
|
|
5434
|
+
<mat-select formControlName="orientation">
|
|
5435
|
+
<mat-option value="">Padrão</mat-option>
|
|
5436
|
+
<mat-option value="vertical">vertical</mat-option>
|
|
5437
|
+
<mat-option value="columns">columns</mat-option>
|
|
5438
|
+
</mat-select>
|
|
5439
|
+
</mat-form-field>
|
|
5440
|
+
<mat-form-field appearance="outline">
|
|
5441
|
+
<mat-label>Columns</mat-label>
|
|
5442
|
+
<input matInput type="number" min="1" formControlName="columns" />
|
|
5443
|
+
</mat-form-field>
|
|
5444
|
+
<mat-form-field appearance="outline">
|
|
5445
|
+
<mat-label>Gap</mat-label>
|
|
5446
|
+
<input matInput formControlName="gap" />
|
|
5447
|
+
</mat-form-field>
|
|
5448
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5449
|
+
<mat-label>Breakpoints (JSON)</mat-label>
|
|
5450
|
+
<textarea matInput rows="3" formControlName="breakpointsJson"></textarea>
|
|
5451
|
+
</mat-form-field>
|
|
5452
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5453
|
+
<mat-label>Grouping overrides (JSON)</mat-label>
|
|
5454
|
+
<textarea matInput rows="4" formControlName="groupingOverridesJson"></textarea>
|
|
5455
|
+
</mat-form-field>
|
|
5456
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5457
|
+
<mat-label>Widget overrides (JSON)</mat-label>
|
|
5458
|
+
<textarea matInput rows="4" formControlName="widgetOverridesJson"></textarea>
|
|
5459
|
+
</mat-form-field>
|
|
5460
|
+
</div>
|
|
5461
|
+
</div>
|
|
5462
|
+
<div class="state-card" [formGroup]="deviceTabletForm">
|
|
5463
|
+
<div class="state-card-head"><div class="state-card-title">Tablet</div></div>
|
|
5464
|
+
<div class="state-card-grid">
|
|
5465
|
+
<mat-form-field appearance="outline">
|
|
5466
|
+
<mat-label>Orientation</mat-label>
|
|
5467
|
+
<mat-select formControlName="orientation">
|
|
5468
|
+
<mat-option value="">Padrão</mat-option>
|
|
5469
|
+
<mat-option value="vertical">vertical</mat-option>
|
|
5470
|
+
<mat-option value="columns">columns</mat-option>
|
|
5471
|
+
</mat-select>
|
|
5472
|
+
</mat-form-field>
|
|
5473
|
+
<mat-form-field appearance="outline">
|
|
5474
|
+
<mat-label>Columns</mat-label>
|
|
5475
|
+
<input matInput type="number" min="1" formControlName="columns" />
|
|
5476
|
+
</mat-form-field>
|
|
5477
|
+
<mat-form-field appearance="outline">
|
|
5478
|
+
<mat-label>Gap</mat-label>
|
|
5479
|
+
<input matInput formControlName="gap" />
|
|
5480
|
+
</mat-form-field>
|
|
5481
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5482
|
+
<mat-label>Breakpoints (JSON)</mat-label>
|
|
5483
|
+
<textarea matInput rows="3" formControlName="breakpointsJson"></textarea>
|
|
5484
|
+
</mat-form-field>
|
|
5485
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5486
|
+
<mat-label>Grouping overrides (JSON)</mat-label>
|
|
5487
|
+
<textarea matInput rows="4" formControlName="groupingOverridesJson"></textarea>
|
|
5488
|
+
</mat-form-field>
|
|
5489
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5490
|
+
<mat-label>Widget overrides (JSON)</mat-label>
|
|
5491
|
+
<textarea matInput rows="4" formControlName="widgetOverridesJson"></textarea>
|
|
5492
|
+
</mat-form-field>
|
|
5493
|
+
</div>
|
|
5494
|
+
</div>
|
|
5495
|
+
<div class="state-card" [formGroup]="deviceMobileForm">
|
|
5496
|
+
<div class="state-card-head"><div class="state-card-title">Mobile</div></div>
|
|
5497
|
+
<div class="state-card-grid">
|
|
5498
|
+
<mat-form-field appearance="outline">
|
|
5499
|
+
<mat-label>Orientation</mat-label>
|
|
5500
|
+
<mat-select formControlName="orientation">
|
|
5501
|
+
<mat-option value="">Padrão</mat-option>
|
|
5502
|
+
<mat-option value="vertical">vertical</mat-option>
|
|
5503
|
+
<mat-option value="columns">columns</mat-option>
|
|
5504
|
+
</mat-select>
|
|
5505
|
+
</mat-form-field>
|
|
5506
|
+
<mat-form-field appearance="outline">
|
|
5507
|
+
<mat-label>Columns</mat-label>
|
|
5508
|
+
<input matInput type="number" min="1" formControlName="columns" />
|
|
5509
|
+
</mat-form-field>
|
|
5510
|
+
<mat-form-field appearance="outline">
|
|
5511
|
+
<mat-label>Gap</mat-label>
|
|
5512
|
+
<input matInput formControlName="gap" />
|
|
5513
|
+
</mat-form-field>
|
|
5514
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5515
|
+
<mat-label>Breakpoints (JSON)</mat-label>
|
|
5516
|
+
<textarea matInput rows="3" formControlName="breakpointsJson"></textarea>
|
|
5517
|
+
</mat-form-field>
|
|
5518
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5519
|
+
<mat-label>Grouping overrides (JSON)</mat-label>
|
|
5520
|
+
<textarea matInput rows="4" formControlName="groupingOverridesJson"></textarea>
|
|
5521
|
+
</mat-form-field>
|
|
5522
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5523
|
+
<mat-label>Widget overrides (JSON)</mat-label>
|
|
5524
|
+
<textarea matInput rows="4" formControlName="widgetOverridesJson"></textarea>
|
|
5525
|
+
</mat-form-field>
|
|
5526
|
+
</div>
|
|
5527
|
+
</div>
|
|
5528
|
+
</div>
|
|
5529
|
+
</div>
|
|
5530
|
+
@if (layoutError()) {
|
|
5531
|
+
<div class="page-error" role="alert">{{ layoutError() }}</div>
|
|
5532
|
+
}
|
|
5533
|
+
|
|
4648
5534
|
<div class="page-section">Contexto</div>
|
|
4649
5535
|
<mat-form-field appearance="outline">
|
|
4650
5536
|
<mat-label>Contexto (JSON)</mat-label>
|
|
@@ -4728,10 +5614,38 @@ class PageConfigEditorComponent {
|
|
|
4728
5614
|
}
|
|
4729
5615
|
</mat-select>
|
|
4730
5616
|
</mat-form-field>
|
|
4731
|
-
<mat-form-field appearance="outline"
|
|
4732
|
-
<mat-label>Initial
|
|
4733
|
-
<
|
|
5617
|
+
<mat-form-field appearance="outline">
|
|
5618
|
+
<mat-label>Initial</mat-label>
|
|
5619
|
+
<mat-select formControlName="initialMode">
|
|
5620
|
+
@for (mode of valueModes; track mode) {
|
|
5621
|
+
<mat-option [value]="mode">{{ mode }}</mat-option>
|
|
5622
|
+
}
|
|
5623
|
+
</mat-select>
|
|
4734
5624
|
</mat-form-field>
|
|
5625
|
+
@if (group.controls.initialMode.value === 'json') {
|
|
5626
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
5627
|
+
<mat-label>Initial (JSON)</mat-label>
|
|
5628
|
+
<textarea matInput rows="4" formControlName="initialJson"></textarea>
|
|
5629
|
+
</mat-form-field>
|
|
5630
|
+
} @else if (group.controls.initialMode.value === 'boolean') {
|
|
5631
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
5632
|
+
<mat-label>Initial</mat-label>
|
|
5633
|
+
<mat-select formControlName="initialScalar">
|
|
5634
|
+
<mat-option value="true">true</mat-option>
|
|
5635
|
+
<mat-option value="false">false</mat-option>
|
|
5636
|
+
</mat-select>
|
|
5637
|
+
</mat-form-field>
|
|
5638
|
+
} @else if (group.controls.initialMode.value === 'null') {
|
|
5639
|
+
<div class="state-operator-hint span-2">
|
|
5640
|
+
<mat-icon>block</mat-icon>
|
|
5641
|
+
<span>O valor inicial será null.</span>
|
|
5642
|
+
</div>
|
|
5643
|
+
} @else {
|
|
5644
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
5645
|
+
<mat-label>Initial</mat-label>
|
|
5646
|
+
<input matInput formControlName="initialScalar" />
|
|
5647
|
+
</mat-form-field>
|
|
5648
|
+
}
|
|
4735
5649
|
<mat-form-field appearance="outline" class="span-2">
|
|
4736
5650
|
<mat-label>Descrição</mat-label>
|
|
4737
5651
|
<input matInput formControlName="description" />
|
|
@@ -4807,10 +5721,38 @@ class PageConfigEditorComponent {
|
|
|
4807
5721
|
}
|
|
4808
5722
|
}
|
|
4809
5723
|
@if (group.controls.computeKind.value === 'template') {
|
|
4810
|
-
<mat-form-field appearance="outline"
|
|
4811
|
-
<mat-label>Template
|
|
4812
|
-
<
|
|
5724
|
+
<mat-form-field appearance="outline">
|
|
5725
|
+
<mat-label>Template</mat-label>
|
|
5726
|
+
<mat-select formControlName="templateMode">
|
|
5727
|
+
@for (mode of valueModes; track mode) {
|
|
5728
|
+
<mat-option [value]="mode">{{ mode }}</mat-option>
|
|
5729
|
+
}
|
|
5730
|
+
</mat-select>
|
|
4813
5731
|
</mat-form-field>
|
|
5732
|
+
@if (group.controls.templateMode.value === 'json') {
|
|
5733
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
5734
|
+
<mat-label>Template (JSON)</mat-label>
|
|
5735
|
+
<textarea matInput rows="4" formControlName="templateJson"></textarea>
|
|
5736
|
+
</mat-form-field>
|
|
5737
|
+
} @else if (group.controls.templateMode.value === 'boolean') {
|
|
5738
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
5739
|
+
<mat-label>Template</mat-label>
|
|
5740
|
+
<mat-select formControlName="templateScalar">
|
|
5741
|
+
<mat-option value="true">true</mat-option>
|
|
5742
|
+
<mat-option value="false">false</mat-option>
|
|
5743
|
+
</mat-select>
|
|
5744
|
+
</mat-form-field>
|
|
5745
|
+
} @else if (group.controls.templateMode.value === 'null') {
|
|
5746
|
+
<div class="state-operator-hint span-2">
|
|
5747
|
+
<mat-icon>block</mat-icon>
|
|
5748
|
+
<span>O template retornará null.</span>
|
|
5749
|
+
</div>
|
|
5750
|
+
} @else {
|
|
5751
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
5752
|
+
<mat-label>Template</mat-label>
|
|
5753
|
+
<input matInput formControlName="templateScalar" />
|
|
5754
|
+
</mat-form-field>
|
|
5755
|
+
}
|
|
4814
5756
|
}
|
|
4815
5757
|
@if (group.controls.computeKind.value === 'expr') {
|
|
4816
5758
|
<mat-form-field appearance="outline" class="span-3">
|
|
@@ -4896,6 +5838,226 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4896
5838
|
</mat-form-field>
|
|
4897
5839
|
</form>
|
|
4898
5840
|
|
|
5841
|
+
<div class="page-section">Layout Preset</div>
|
|
5842
|
+
<div class="grid-form">
|
|
5843
|
+
<mat-form-field appearance="outline">
|
|
5844
|
+
<mat-label>Layout preset</mat-label>
|
|
5845
|
+
<mat-select [formControl]="layoutPresetControl">
|
|
5846
|
+
<mat-option value="">Nenhum</mat-option>
|
|
5847
|
+
@for (preset of layoutPresetEntries; track preset.id) {
|
|
5848
|
+
<mat-option [value]="preset.id">{{ preset.label }}</mat-option>
|
|
5849
|
+
}
|
|
5850
|
+
</mat-select>
|
|
5851
|
+
</mat-form-field>
|
|
5852
|
+
<mat-form-field appearance="outline">
|
|
5853
|
+
<mat-label>Theme preset</mat-label>
|
|
5854
|
+
<mat-select [formControl]="themePresetControl">
|
|
5855
|
+
<mat-option value="">Padrão do layout</mat-option>
|
|
5856
|
+
@for (preset of themePresetEntries; track preset.id) {
|
|
5857
|
+
<mat-option [value]="preset.id">{{ preset.label }}</mat-option>
|
|
5858
|
+
}
|
|
5859
|
+
</mat-select>
|
|
5860
|
+
</mat-form-field>
|
|
5861
|
+
</div>
|
|
5862
|
+
|
|
5863
|
+
<div class="page-section">Grouping</div>
|
|
5864
|
+
<div class="state-group">
|
|
5865
|
+
<div class="state-group-head">
|
|
5866
|
+
<div class="state-group-title">Grouping</div>
|
|
5867
|
+
<button mat-stroked-button type="button" (click)="addGroupingNode()">
|
|
5868
|
+
<mat-icon>add</mat-icon>
|
|
5869
|
+
Adicionar grupo
|
|
5870
|
+
</button>
|
|
5871
|
+
</div>
|
|
5872
|
+
<div class="state-group-help">Organiza a página em section, tabs, hero e rail com intenção estrutural.</div>
|
|
5873
|
+
@if (!groupingNodes.length) {
|
|
5874
|
+
<div class="state-empty">Nenhum grupo configurado.</div>
|
|
5875
|
+
}
|
|
5876
|
+
<div class="state-list">
|
|
5877
|
+
@for (group of groupingNodes.controls; track $index) {
|
|
5878
|
+
<div class="state-card" [formGroup]="group">
|
|
5879
|
+
<div class="state-card-head">
|
|
5880
|
+
<div class="state-card-title">Grupo {{ $index + 1 }}</div>
|
|
5881
|
+
<button mat-icon-button type="button" (click)="removeGroupingNode($index)" aria-label="Remover grupo">
|
|
5882
|
+
<mat-icon>delete</mat-icon>
|
|
5883
|
+
</button>
|
|
5884
|
+
</div>
|
|
5885
|
+
<div class="state-card-grid">
|
|
5886
|
+
<mat-form-field appearance="outline">
|
|
5887
|
+
<mat-label>Kind</mat-label>
|
|
5888
|
+
<mat-select formControlName="kind">
|
|
5889
|
+
@for (kind of groupingKinds; track kind) {
|
|
5890
|
+
<mat-option [value]="kind">{{ kind }}</mat-option>
|
|
5891
|
+
}
|
|
5892
|
+
</mat-select>
|
|
5893
|
+
</mat-form-field>
|
|
5894
|
+
<mat-form-field appearance="outline">
|
|
5895
|
+
<mat-label>Id</mat-label>
|
|
5896
|
+
<input matInput formControlName="id" />
|
|
5897
|
+
</mat-form-field>
|
|
5898
|
+
<mat-form-field appearance="outline">
|
|
5899
|
+
<mat-label>Label</mat-label>
|
|
5900
|
+
<input matInput formControlName="label" />
|
|
5901
|
+
</mat-form-field>
|
|
5902
|
+
@if (group.controls.kind.value === 'section') {
|
|
5903
|
+
<mat-form-field appearance="outline">
|
|
5904
|
+
<mat-label>Layout</mat-label>
|
|
5905
|
+
<mat-select formControlName="layout">
|
|
5906
|
+
<mat-option value="">Padrão</mat-option>
|
|
5907
|
+
<mat-option value="stack">stack</mat-option>
|
|
5908
|
+
<mat-option value="grid">grid</mat-option>
|
|
5909
|
+
<mat-option value="row">row</mat-option>
|
|
5910
|
+
</mat-select>
|
|
5911
|
+
</mat-form-field>
|
|
5912
|
+
}
|
|
5913
|
+
@if (group.controls.kind.value === 'hero') {
|
|
5914
|
+
<mat-form-field appearance="outline">
|
|
5915
|
+
<mat-label>Emphasis</mat-label>
|
|
5916
|
+
<mat-select formControlName="emphasis">
|
|
5917
|
+
<mat-option value="">Padrão</mat-option>
|
|
5918
|
+
<mat-option value="high">high</mat-option>
|
|
5919
|
+
<mat-option value="medium">medium</mat-option>
|
|
5920
|
+
</mat-select>
|
|
5921
|
+
</mat-form-field>
|
|
5922
|
+
}
|
|
5923
|
+
@if (group.controls.kind.value === 'rail') {
|
|
5924
|
+
<mat-form-field appearance="outline">
|
|
5925
|
+
<mat-label>Side</mat-label>
|
|
5926
|
+
<mat-select formControlName="side">
|
|
5927
|
+
<mat-option value="left">left</mat-option>
|
|
5928
|
+
<mat-option value="right">right</mat-option>
|
|
5929
|
+
</mat-select>
|
|
5930
|
+
</mat-form-field>
|
|
5931
|
+
}
|
|
5932
|
+
@if (group.controls.kind.value === 'tabs') {
|
|
5933
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5934
|
+
<mat-label>Tabs (JSON)</mat-label>
|
|
5935
|
+
<textarea matInput rows="5" formControlName="tabsJson"></textarea>
|
|
5936
|
+
</mat-form-field>
|
|
5937
|
+
} @else {
|
|
5938
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5939
|
+
<mat-label>Widget keys (vírgula)</mat-label>
|
|
5940
|
+
<input matInput formControlName="widgetKeysCsv" />
|
|
5941
|
+
</mat-form-field>
|
|
5942
|
+
}
|
|
5943
|
+
</div>
|
|
5944
|
+
</div>
|
|
5945
|
+
}
|
|
5946
|
+
</div>
|
|
5947
|
+
</div>
|
|
5948
|
+
|
|
5949
|
+
<div class="page-section">Device Layouts</div>
|
|
5950
|
+
<div class="state-group">
|
|
5951
|
+
<div class="state-group-head">
|
|
5952
|
+
<div class="state-group-title">Device Layouts</div>
|
|
5953
|
+
</div>
|
|
5954
|
+
<div class="state-group-help">Overrides por device para layout, grouping e widgets com campos separados por intenção.</div>
|
|
5955
|
+
<div class="state-list">
|
|
5956
|
+
<div class="state-card" [formGroup]="deviceDesktopForm">
|
|
5957
|
+
<div class="state-card-head"><div class="state-card-title">Desktop</div></div>
|
|
5958
|
+
<div class="state-card-grid">
|
|
5959
|
+
<mat-form-field appearance="outline">
|
|
5960
|
+
<mat-label>Orientation</mat-label>
|
|
5961
|
+
<mat-select formControlName="orientation">
|
|
5962
|
+
<mat-option value="">Padrão</mat-option>
|
|
5963
|
+
<mat-option value="vertical">vertical</mat-option>
|
|
5964
|
+
<mat-option value="columns">columns</mat-option>
|
|
5965
|
+
</mat-select>
|
|
5966
|
+
</mat-form-field>
|
|
5967
|
+
<mat-form-field appearance="outline">
|
|
5968
|
+
<mat-label>Columns</mat-label>
|
|
5969
|
+
<input matInput type="number" min="1" formControlName="columns" />
|
|
5970
|
+
</mat-form-field>
|
|
5971
|
+
<mat-form-field appearance="outline">
|
|
5972
|
+
<mat-label>Gap</mat-label>
|
|
5973
|
+
<input matInput formControlName="gap" />
|
|
5974
|
+
</mat-form-field>
|
|
5975
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5976
|
+
<mat-label>Breakpoints (JSON)</mat-label>
|
|
5977
|
+
<textarea matInput rows="3" formControlName="breakpointsJson"></textarea>
|
|
5978
|
+
</mat-form-field>
|
|
5979
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5980
|
+
<mat-label>Grouping overrides (JSON)</mat-label>
|
|
5981
|
+
<textarea matInput rows="4" formControlName="groupingOverridesJson"></textarea>
|
|
5982
|
+
</mat-form-field>
|
|
5983
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
5984
|
+
<mat-label>Widget overrides (JSON)</mat-label>
|
|
5985
|
+
<textarea matInput rows="4" formControlName="widgetOverridesJson"></textarea>
|
|
5986
|
+
</mat-form-field>
|
|
5987
|
+
</div>
|
|
5988
|
+
</div>
|
|
5989
|
+
<div class="state-card" [formGroup]="deviceTabletForm">
|
|
5990
|
+
<div class="state-card-head"><div class="state-card-title">Tablet</div></div>
|
|
5991
|
+
<div class="state-card-grid">
|
|
5992
|
+
<mat-form-field appearance="outline">
|
|
5993
|
+
<mat-label>Orientation</mat-label>
|
|
5994
|
+
<mat-select formControlName="orientation">
|
|
5995
|
+
<mat-option value="">Padrão</mat-option>
|
|
5996
|
+
<mat-option value="vertical">vertical</mat-option>
|
|
5997
|
+
<mat-option value="columns">columns</mat-option>
|
|
5998
|
+
</mat-select>
|
|
5999
|
+
</mat-form-field>
|
|
6000
|
+
<mat-form-field appearance="outline">
|
|
6001
|
+
<mat-label>Columns</mat-label>
|
|
6002
|
+
<input matInput type="number" min="1" formControlName="columns" />
|
|
6003
|
+
</mat-form-field>
|
|
6004
|
+
<mat-form-field appearance="outline">
|
|
6005
|
+
<mat-label>Gap</mat-label>
|
|
6006
|
+
<input matInput formControlName="gap" />
|
|
6007
|
+
</mat-form-field>
|
|
6008
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
6009
|
+
<mat-label>Breakpoints (JSON)</mat-label>
|
|
6010
|
+
<textarea matInput rows="3" formControlName="breakpointsJson"></textarea>
|
|
6011
|
+
</mat-form-field>
|
|
6012
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
6013
|
+
<mat-label>Grouping overrides (JSON)</mat-label>
|
|
6014
|
+
<textarea matInput rows="4" formControlName="groupingOverridesJson"></textarea>
|
|
6015
|
+
</mat-form-field>
|
|
6016
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
6017
|
+
<mat-label>Widget overrides (JSON)</mat-label>
|
|
6018
|
+
<textarea matInput rows="4" formControlName="widgetOverridesJson"></textarea>
|
|
6019
|
+
</mat-form-field>
|
|
6020
|
+
</div>
|
|
6021
|
+
</div>
|
|
6022
|
+
<div class="state-card" [formGroup]="deviceMobileForm">
|
|
6023
|
+
<div class="state-card-head"><div class="state-card-title">Mobile</div></div>
|
|
6024
|
+
<div class="state-card-grid">
|
|
6025
|
+
<mat-form-field appearance="outline">
|
|
6026
|
+
<mat-label>Orientation</mat-label>
|
|
6027
|
+
<mat-select formControlName="orientation">
|
|
6028
|
+
<mat-option value="">Padrão</mat-option>
|
|
6029
|
+
<mat-option value="vertical">vertical</mat-option>
|
|
6030
|
+
<mat-option value="columns">columns</mat-option>
|
|
6031
|
+
</mat-select>
|
|
6032
|
+
</mat-form-field>
|
|
6033
|
+
<mat-form-field appearance="outline">
|
|
6034
|
+
<mat-label>Columns</mat-label>
|
|
6035
|
+
<input matInput type="number" min="1" formControlName="columns" />
|
|
6036
|
+
</mat-form-field>
|
|
6037
|
+
<mat-form-field appearance="outline">
|
|
6038
|
+
<mat-label>Gap</mat-label>
|
|
6039
|
+
<input matInput formControlName="gap" />
|
|
6040
|
+
</mat-form-field>
|
|
6041
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
6042
|
+
<mat-label>Breakpoints (JSON)</mat-label>
|
|
6043
|
+
<textarea matInput rows="3" formControlName="breakpointsJson"></textarea>
|
|
6044
|
+
</mat-form-field>
|
|
6045
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
6046
|
+
<mat-label>Grouping overrides (JSON)</mat-label>
|
|
6047
|
+
<textarea matInput rows="4" formControlName="groupingOverridesJson"></textarea>
|
|
6048
|
+
</mat-form-field>
|
|
6049
|
+
<mat-form-field appearance="outline" class="span-3">
|
|
6050
|
+
<mat-label>Widget overrides (JSON)</mat-label>
|
|
6051
|
+
<textarea matInput rows="4" formControlName="widgetOverridesJson"></textarea>
|
|
6052
|
+
</mat-form-field>
|
|
6053
|
+
</div>
|
|
6054
|
+
</div>
|
|
6055
|
+
</div>
|
|
6056
|
+
</div>
|
|
6057
|
+
@if (layoutError()) {
|
|
6058
|
+
<div class="page-error" role="alert">{{ layoutError() }}</div>
|
|
6059
|
+
}
|
|
6060
|
+
|
|
4899
6061
|
<div class="page-section">Contexto</div>
|
|
4900
6062
|
<mat-form-field appearance="outline">
|
|
4901
6063
|
<mat-label>Contexto (JSON)</mat-label>
|
|
@@ -4979,10 +6141,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
4979
6141
|
}
|
|
4980
6142
|
</mat-select>
|
|
4981
6143
|
</mat-form-field>
|
|
4982
|
-
<mat-form-field appearance="outline"
|
|
4983
|
-
<mat-label>Initial
|
|
4984
|
-
<
|
|
6144
|
+
<mat-form-field appearance="outline">
|
|
6145
|
+
<mat-label>Initial</mat-label>
|
|
6146
|
+
<mat-select formControlName="initialMode">
|
|
6147
|
+
@for (mode of valueModes; track mode) {
|
|
6148
|
+
<mat-option [value]="mode">{{ mode }}</mat-option>
|
|
6149
|
+
}
|
|
6150
|
+
</mat-select>
|
|
4985
6151
|
</mat-form-field>
|
|
6152
|
+
@if (group.controls.initialMode.value === 'json') {
|
|
6153
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
6154
|
+
<mat-label>Initial (JSON)</mat-label>
|
|
6155
|
+
<textarea matInput rows="4" formControlName="initialJson"></textarea>
|
|
6156
|
+
</mat-form-field>
|
|
6157
|
+
} @else if (group.controls.initialMode.value === 'boolean') {
|
|
6158
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
6159
|
+
<mat-label>Initial</mat-label>
|
|
6160
|
+
<mat-select formControlName="initialScalar">
|
|
6161
|
+
<mat-option value="true">true</mat-option>
|
|
6162
|
+
<mat-option value="false">false</mat-option>
|
|
6163
|
+
</mat-select>
|
|
6164
|
+
</mat-form-field>
|
|
6165
|
+
} @else if (group.controls.initialMode.value === 'null') {
|
|
6166
|
+
<div class="state-operator-hint span-2">
|
|
6167
|
+
<mat-icon>block</mat-icon>
|
|
6168
|
+
<span>O valor inicial será null.</span>
|
|
6169
|
+
</div>
|
|
6170
|
+
} @else {
|
|
6171
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
6172
|
+
<mat-label>Initial</mat-label>
|
|
6173
|
+
<input matInput formControlName="initialScalar" />
|
|
6174
|
+
</mat-form-field>
|
|
6175
|
+
}
|
|
4986
6176
|
<mat-form-field appearance="outline" class="span-2">
|
|
4987
6177
|
<mat-label>Descrição</mat-label>
|
|
4988
6178
|
<input matInput formControlName="description" />
|
|
@@ -5058,10 +6248,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
5058
6248
|
}
|
|
5059
6249
|
}
|
|
5060
6250
|
@if (group.controls.computeKind.value === 'template') {
|
|
5061
|
-
<mat-form-field appearance="outline"
|
|
5062
|
-
<mat-label>Template
|
|
5063
|
-
<
|
|
6251
|
+
<mat-form-field appearance="outline">
|
|
6252
|
+
<mat-label>Template</mat-label>
|
|
6253
|
+
<mat-select formControlName="templateMode">
|
|
6254
|
+
@for (mode of valueModes; track mode) {
|
|
6255
|
+
<mat-option [value]="mode">{{ mode }}</mat-option>
|
|
6256
|
+
}
|
|
6257
|
+
</mat-select>
|
|
5064
6258
|
</mat-form-field>
|
|
6259
|
+
@if (group.controls.templateMode.value === 'json') {
|
|
6260
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
6261
|
+
<mat-label>Template (JSON)</mat-label>
|
|
6262
|
+
<textarea matInput rows="4" formControlName="templateJson"></textarea>
|
|
6263
|
+
</mat-form-field>
|
|
6264
|
+
} @else if (group.controls.templateMode.value === 'boolean') {
|
|
6265
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
6266
|
+
<mat-label>Template</mat-label>
|
|
6267
|
+
<mat-select formControlName="templateScalar">
|
|
6268
|
+
<mat-option value="true">true</mat-option>
|
|
6269
|
+
<mat-option value="false">false</mat-option>
|
|
6270
|
+
</mat-select>
|
|
6271
|
+
</mat-form-field>
|
|
6272
|
+
} @else if (group.controls.templateMode.value === 'null') {
|
|
6273
|
+
<div class="state-operator-hint span-2">
|
|
6274
|
+
<mat-icon>block</mat-icon>
|
|
6275
|
+
<span>O template retornará null.</span>
|
|
6276
|
+
</div>
|
|
6277
|
+
} @else {
|
|
6278
|
+
<mat-form-field appearance="outline" class="span-2">
|
|
6279
|
+
<mat-label>Template</mat-label>
|
|
6280
|
+
<input matInput formControlName="templateScalar" />
|
|
6281
|
+
</mat-form-field>
|
|
6282
|
+
}
|
|
5065
6283
|
}
|
|
5066
6284
|
@if (group.controls.computeKind.value === 'expr') {
|
|
5067
6285
|
<mat-form-field appearance="outline" class="span-3">
|
|
@@ -5650,6 +6868,96 @@ class DynamicGridsterPageComponent {
|
|
|
5650
6868
|
getWidgetType(key) { return (this.widgets().find((w) => w.key === key)?.definition?.id) || undefined; }
|
|
5651
6869
|
getWidgetDef(key) { return this.widgets().find((w) => w.key === key)?.definition; }
|
|
5652
6870
|
getWidgetShell(key) { return this.widgets().find((w) => w.key === key)?.shell; }
|
|
6871
|
+
hasCanvasSummary() { return !!(this.layoutPresetLabel() || this.themePresetLabel() || this.groupingCount() || this.configuredDeviceKinds().length); }
|
|
6872
|
+
groupingCount() { return this.currentPage()?.grouping?.length || 0; }
|
|
6873
|
+
selectedWidgetKey() { return this.selectedKey(); }
|
|
6874
|
+
layoutPresetLabel() {
|
|
6875
|
+
const presetId = this.currentPage()?.layoutPreset;
|
|
6876
|
+
if (!presetId)
|
|
6877
|
+
return null;
|
|
6878
|
+
return BUILTIN_PAGE_LAYOUT_PRESETS[presetId]?.label || presetId;
|
|
6879
|
+
}
|
|
6880
|
+
themePresetLabel() {
|
|
6881
|
+
const presetId = this.currentPage()?.themePreset;
|
|
6882
|
+
if (!presetId)
|
|
6883
|
+
return null;
|
|
6884
|
+
return BUILTIN_PAGE_THEME_PRESETS[presetId]?.label || presetId;
|
|
6885
|
+
}
|
|
6886
|
+
configuredDeviceKinds() {
|
|
6887
|
+
const deviceLayouts = this.currentPage()?.deviceLayouts;
|
|
6888
|
+
if (!deviceLayouts)
|
|
6889
|
+
return [];
|
|
6890
|
+
return ['desktop', 'tablet', 'mobile'].filter((device) => !!deviceLayouts[device]);
|
|
6891
|
+
}
|
|
6892
|
+
hasTileMetadata(key) {
|
|
6893
|
+
return this.widgetSlotLabels(key).length > 0 || this.widgetGroupingLabels(key).length > 0 || this.widgetDeviceLabels(key).length > 0;
|
|
6894
|
+
}
|
|
6895
|
+
presetSlots() {
|
|
6896
|
+
return this.activeLayoutPreset()?.slotModel || [];
|
|
6897
|
+
}
|
|
6898
|
+
canAssignSelectedWidgetToSlot() {
|
|
6899
|
+
return !!(this.enableCustomization && this.selectedKey() && this.presetSlots().length);
|
|
6900
|
+
}
|
|
6901
|
+
slotOccupancy(slotId) {
|
|
6902
|
+
return this.widgets().filter((widget) => this.widgetSlotIds(widget.key).includes(slotId)).length;
|
|
6903
|
+
}
|
|
6904
|
+
slotCapacityLabel(slot) {
|
|
6905
|
+
return slot.maxItems != null ? String(slot.maxItems) : 'n';
|
|
6906
|
+
}
|
|
6907
|
+
widgetSlotLabels(key) {
|
|
6908
|
+
return this.widgetSlotIds(key)
|
|
6909
|
+
.map((slotId) => this.presetSlots().find((slot) => slot.id === slotId)?.label || slotId)
|
|
6910
|
+
.map((label) => `slot ${label}`);
|
|
6911
|
+
}
|
|
6912
|
+
isRecommendedSlotForSelectedWidget(slot) {
|
|
6913
|
+
const selected = this.selectedKey();
|
|
6914
|
+
if (!selected)
|
|
6915
|
+
return false;
|
|
6916
|
+
const widgetType = this.getWidgetType(selected);
|
|
6917
|
+
return !!this.activeLayoutPreset()?.widgetSuggestions?.some((suggestion) => suggestion.slot === slot.id && suggestion.widgetType === widgetType);
|
|
6918
|
+
}
|
|
6919
|
+
assignSelectedWidgetToSlot(slotId, event) {
|
|
6920
|
+
event?.preventDefault();
|
|
6921
|
+
event?.stopPropagation();
|
|
6922
|
+
const widgetKey = this.selectedKey();
|
|
6923
|
+
const preset = this.activeLayoutPreset();
|
|
6924
|
+
if (!widgetKey || !preset)
|
|
6925
|
+
return;
|
|
6926
|
+
const page = this.currentPage() || { widgets: this.widgets() };
|
|
6927
|
+
const grouping = this.reassignWidgetToSlot(page.grouping, preset, widgetKey, slotId);
|
|
6928
|
+
const next = { ...page, grouping };
|
|
6929
|
+
this.page = next;
|
|
6930
|
+
this.pageChange.emit(next);
|
|
6931
|
+
}
|
|
6932
|
+
widgetGroupingLabels(key) {
|
|
6933
|
+
const labels = [];
|
|
6934
|
+
for (const group of this.currentPage()?.grouping || []) {
|
|
6935
|
+
if (this.groupContainsWidget(group, key)) {
|
|
6936
|
+
const base = this.groupDisplayLabel(group);
|
|
6937
|
+
labels.push(group.kind === 'tabs' ? `tab ${base}` : `${group.kind} ${base}`);
|
|
6938
|
+
}
|
|
6939
|
+
}
|
|
6940
|
+
return labels;
|
|
6941
|
+
}
|
|
6942
|
+
widgetDeviceLabels(key) {
|
|
6943
|
+
const labels = [];
|
|
6944
|
+
const deviceLayouts = this.currentPage()?.deviceLayouts;
|
|
6945
|
+
for (const device of this.configuredDeviceKinds()) {
|
|
6946
|
+
const variant = deviceLayouts?.[device];
|
|
6947
|
+
if (!variant)
|
|
6948
|
+
continue;
|
|
6949
|
+
const widgetOverride = variant.widgetOverrides?.[key];
|
|
6950
|
+
const groupingOverride = variant.groupingOverrides?.some((override) => this.groupingOverrideContainsWidget(override, key));
|
|
6951
|
+
if (widgetOverride) {
|
|
6952
|
+
const detail = widgetOverride.hidden ? 'hidden' : widgetOverride.span ? `span ${widgetOverride.span}` : 'override';
|
|
6953
|
+
labels.push(`${device} ${detail}`);
|
|
6954
|
+
}
|
|
6955
|
+
else if (groupingOverride) {
|
|
6956
|
+
labels.push(`${device} grouping`);
|
|
6957
|
+
}
|
|
6958
|
+
}
|
|
6959
|
+
return labels;
|
|
6960
|
+
}
|
|
5653
6961
|
onTileClick(key, ev) {
|
|
5654
6962
|
if (!this.enableCustomization)
|
|
5655
6963
|
return;
|
|
@@ -6029,14 +7337,175 @@ class DynamicGridsterPageComponent {
|
|
|
6029
7337
|
parts.push(identity.locale);
|
|
6030
7338
|
return parts.join(':');
|
|
6031
7339
|
}
|
|
7340
|
+
currentPage() {
|
|
7341
|
+
return this.parsePage(this.page);
|
|
7342
|
+
}
|
|
7343
|
+
activeLayoutPreset() {
|
|
7344
|
+
const presetId = this.currentPage()?.layoutPreset;
|
|
7345
|
+
if (!presetId)
|
|
7346
|
+
return null;
|
|
7347
|
+
return BUILTIN_PAGE_LAYOUT_PRESETS[presetId] || null;
|
|
7348
|
+
}
|
|
7349
|
+
groupContainsWidget(group, widgetKey) {
|
|
7350
|
+
if (group.kind === 'tabs') {
|
|
7351
|
+
return group.tabs.some((tab) => tab.widgetKeys.includes(widgetKey));
|
|
7352
|
+
}
|
|
7353
|
+
return group.widgetKeys.includes(widgetKey);
|
|
7354
|
+
}
|
|
7355
|
+
groupDisplayLabel(group) {
|
|
7356
|
+
if ('label' in group && typeof group.label === 'string' && group.label.trim()) {
|
|
7357
|
+
return group.label.trim();
|
|
7358
|
+
}
|
|
7359
|
+
return group.id;
|
|
7360
|
+
}
|
|
7361
|
+
widgetSlotIds(widgetKey) {
|
|
7362
|
+
const preset = this.activeLayoutPreset();
|
|
7363
|
+
if (!preset)
|
|
7364
|
+
return [];
|
|
7365
|
+
const grouping = this.currentPage()?.grouping || preset.defaultGrouping || [];
|
|
7366
|
+
return preset.slotModel
|
|
7367
|
+
.filter((slot) => this.slotContainsWidget(grouping, slot.id, widgetKey, preset))
|
|
7368
|
+
.map((slot) => slot.id);
|
|
7369
|
+
}
|
|
7370
|
+
slotContainsWidget(grouping, slotId, widgetKey, preset) {
|
|
7371
|
+
const directTarget = this.findSlotTarget(grouping, slotId, preset);
|
|
7372
|
+
if (directTarget?.kind === 'group')
|
|
7373
|
+
return directTarget.group.widgetKeys.includes(widgetKey);
|
|
7374
|
+
if (directTarget?.kind === 'tab')
|
|
7375
|
+
return directTarget.tab.widgetKeys.includes(widgetKey);
|
|
7376
|
+
return false;
|
|
7377
|
+
}
|
|
7378
|
+
reassignWidgetToSlot(grouping, preset, widgetKey, slotId) {
|
|
7379
|
+
const nextGrouping = this.cloneGrouping(grouping?.length ? grouping : preset.defaultGrouping || []);
|
|
7380
|
+
this.removeWidgetFromGrouping(nextGrouping, widgetKey);
|
|
7381
|
+
const target = this.findSlotTarget(nextGrouping, slotId, preset);
|
|
7382
|
+
if (target?.kind === 'group') {
|
|
7383
|
+
if (!target.group.widgetKeys.includes(widgetKey))
|
|
7384
|
+
target.group.widgetKeys.push(widgetKey);
|
|
7385
|
+
return nextGrouping;
|
|
7386
|
+
}
|
|
7387
|
+
if (target?.kind === 'tab') {
|
|
7388
|
+
if (!target.tab.widgetKeys.includes(widgetKey))
|
|
7389
|
+
target.tab.widgetKeys.push(widgetKey);
|
|
7390
|
+
return nextGrouping;
|
|
7391
|
+
}
|
|
7392
|
+
const slot = preset.slotModel.find((entry) => entry.id === slotId);
|
|
7393
|
+
nextGrouping.push({
|
|
7394
|
+
kind: 'section',
|
|
7395
|
+
id: slotId,
|
|
7396
|
+
label: slot?.label || slotId,
|
|
7397
|
+
widgetKeys: [widgetKey],
|
|
7398
|
+
layout: 'stack',
|
|
7399
|
+
});
|
|
7400
|
+
return nextGrouping;
|
|
7401
|
+
}
|
|
7402
|
+
findSlotTarget(grouping, slotId, preset) {
|
|
7403
|
+
for (const group of grouping) {
|
|
7404
|
+
if (group.kind === 'tabs') {
|
|
7405
|
+
const directTab = group.tabs.find((tab) => tab.id === slotId);
|
|
7406
|
+
if (directTab)
|
|
7407
|
+
return { kind: 'tab', group, tab: directTab };
|
|
7408
|
+
continue;
|
|
7409
|
+
}
|
|
7410
|
+
if (group.id === slotId)
|
|
7411
|
+
return { kind: 'group', group };
|
|
7412
|
+
}
|
|
7413
|
+
const templateGrouping = preset.defaultGrouping || [];
|
|
7414
|
+
for (const templateGroup of templateGrouping) {
|
|
7415
|
+
if (templateGroup.kind === 'tabs') {
|
|
7416
|
+
const templateTab = templateGroup.tabs.find((tab) => tab.id === slotId || tab.widgetKeys.includes(slotId));
|
|
7417
|
+
if (!templateTab)
|
|
7418
|
+
continue;
|
|
7419
|
+
const currentGroup = grouping.find((candidate) => candidate.kind === 'tabs' && candidate.id === templateGroup.id);
|
|
7420
|
+
const currentTab = currentGroup?.tabs.find((tab) => tab.id === templateTab.id);
|
|
7421
|
+
if (currentGroup && currentTab)
|
|
7422
|
+
return { kind: 'tab', group: currentGroup, tab: currentTab };
|
|
7423
|
+
continue;
|
|
7424
|
+
}
|
|
7425
|
+
if (templateGroup.id === slotId || templateGroup.widgetKeys.includes(slotId)) {
|
|
7426
|
+
const currentGroup = grouping.find((candidate) => candidate.kind !== 'tabs' && candidate.id === templateGroup.id);
|
|
7427
|
+
if (currentGroup)
|
|
7428
|
+
return { kind: 'group', group: currentGroup };
|
|
7429
|
+
}
|
|
7430
|
+
}
|
|
7431
|
+
return null;
|
|
7432
|
+
}
|
|
7433
|
+
removeWidgetFromGrouping(grouping, widgetKey) {
|
|
7434
|
+
for (const group of grouping) {
|
|
7435
|
+
if (group.kind === 'tabs') {
|
|
7436
|
+
group.tabs = group.tabs.map((tab) => ({
|
|
7437
|
+
...tab,
|
|
7438
|
+
widgetKeys: tab.widgetKeys.filter((candidate) => candidate !== widgetKey),
|
|
7439
|
+
}));
|
|
7440
|
+
continue;
|
|
7441
|
+
}
|
|
7442
|
+
group.widgetKeys = group.widgetKeys.filter((candidate) => candidate !== widgetKey);
|
|
7443
|
+
}
|
|
7444
|
+
}
|
|
7445
|
+
cloneGrouping(grouping) {
|
|
7446
|
+
return grouping.map((group) => {
|
|
7447
|
+
if (group.kind === 'tabs') {
|
|
7448
|
+
return {
|
|
7449
|
+
...group,
|
|
7450
|
+
tabs: group.tabs.map((tab) => ({
|
|
7451
|
+
...tab,
|
|
7452
|
+
widgetKeys: [...tab.widgetKeys],
|
|
7453
|
+
})),
|
|
7454
|
+
};
|
|
7455
|
+
}
|
|
7456
|
+
return {
|
|
7457
|
+
...group,
|
|
7458
|
+
widgetKeys: [...group.widgetKeys],
|
|
7459
|
+
};
|
|
7460
|
+
});
|
|
7461
|
+
}
|
|
7462
|
+
groupingOverrideContainsWidget(override, widgetKey) {
|
|
7463
|
+
if (override.widgetKeys?.includes(widgetKey))
|
|
7464
|
+
return true;
|
|
7465
|
+
return override.tabs?.some((tab) => tab.widgetKeys.includes(widgetKey)) || false;
|
|
7466
|
+
}
|
|
6032
7467
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicGridsterPageComponent, deps: [{ token: i1$1.ConnectionManagerService }, { token: i2$3.Router }, { token: i0.ElementRef }, { token: i1.MatDialog }, { token: GraphMapperService }, { token: i1$1.ComponentMetadataRegistry }, { token: ASYNC_CONFIG_STORAGE }, { token: i1$1.ComponentKeyService }, { token: TABLE_CONFIG_EDITOR, optional: true }, { token: STEPPER_CONFIG_EDITOR, optional: true }, { token: SETTINGS_PANEL_BRIDGE, optional: true }, { token: i2$3.ActivatedRoute, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
6033
7468
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: DynamicGridsterPageComponent, isStandalone: true, selector: "praxis-dynamic-gridster-page", inputs: { page: "page", context: "context", strictValidation: "strictValidation", gridsterOptions: "gridsterOptions", enableCustomization: "enableCustomization", showSettingsButton: "showSettingsButton", pageIdentity: "pageIdentity", componentInstanceId: "componentInstanceId" }, outputs: { layoutChange: "layoutChange", pageChange: "pageChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
7469
|
+
<div class="pdx-canvas-summary" *ngIf="showSettings() && hasCanvasSummary()">
|
|
7470
|
+
<div class="pdx-canvas-summary__headline">Estrutura da Pagina</div>
|
|
7471
|
+
<div class="pdx-canvas-summary__chips">
|
|
7472
|
+
<span class="pdx-chip pdx-chip--preset" *ngIf="layoutPresetLabel() as label">{{ label }}</span>
|
|
7473
|
+
<span class="pdx-chip pdx-chip--theme" *ngIf="themePresetLabel() as label">{{ label }}</span>
|
|
7474
|
+
<span class="pdx-chip" *ngIf="groupingCount() > 0">{{ groupingCount() }} grupos</span>
|
|
7475
|
+
<span class="pdx-chip" *ngFor="let device of configuredDeviceKinds()">device {{ device }}</span>
|
|
7476
|
+
</div>
|
|
7477
|
+
<div class="pdx-slot-rail" *ngIf="presetSlots().length > 0">
|
|
7478
|
+
<div class="pdx-slot-rail__headline">Slots do preset</div>
|
|
7479
|
+
<div class="pdx-slot-rail__chips">
|
|
7480
|
+
<button
|
|
7481
|
+
type="button"
|
|
7482
|
+
class="pdx-slot-chip"
|
|
7483
|
+
*ngFor="let slot of presetSlots()"
|
|
7484
|
+
[class.pdx-slot-chip--assignable]="canAssignSelectedWidgetToSlot()"
|
|
7485
|
+
[class.pdx-slot-chip--recommended]="isRecommendedSlotForSelectedWidget(slot)"
|
|
7486
|
+
[attr.data-slot-id]="slot.id"
|
|
7487
|
+
[attr.data-slot-occupancy]="slotOccupancy(slot.id)"
|
|
7488
|
+
[disabled]="!canAssignSelectedWidgetToSlot()"
|
|
7489
|
+
(click)="assignSelectedWidgetToSlot(slot.id, $event)"
|
|
7490
|
+
>
|
|
7491
|
+
<span class="pdx-slot-chip__label">{{ slot.label }}</span>
|
|
7492
|
+
<span class="pdx-slot-chip__meta">{{ slotOccupancy(slot.id) }}/{{ slotCapacityLabel(slot) }}</span>
|
|
7493
|
+
</button>
|
|
7494
|
+
</div>
|
|
7495
|
+
<div class="pdx-slot-rail__help" *ngIf="selectedWidgetKey() as key">
|
|
7496
|
+
Widget selecionado: <strong>{{ getWidgetType(key) || key }}</strong>. Clique em um slot para reatribuir o widget dentro do preset.
|
|
7497
|
+
</div>
|
|
7498
|
+
</div>
|
|
7499
|
+
</div>
|
|
6034
7500
|
<gridster [options]="options" [class.editing]="overlayEnabled && showSettings()" [class.interacting]="overlayEnabled && interacting">
|
|
6035
7501
|
<gridster-item *ngFor="let it of items(); trackBy: trackItem" [item]="it">
|
|
6036
7502
|
<div class="pdx-gridster-item mat-elevation-z4"
|
|
6037
7503
|
[attr.id]="computeTileDomId(it.__key__)"
|
|
6038
7504
|
[attr.data-widget-key]="it.__key__"
|
|
6039
7505
|
[attr.data-widget-type]="getWidgetType(it.__key__)"
|
|
7506
|
+
[attr.data-group-count]="widgetGroupingLabels(it.__key__).length"
|
|
7507
|
+
[attr.data-device-count]="widgetDeviceLabels(it.__key__).length"
|
|
7508
|
+
[attr.data-slot-count]="widgetSlotLabels(it.__key__).length"
|
|
6040
7509
|
[class.selected]="isSelected(it.__key__)"
|
|
6041
7510
|
(click)="onTileClick(it.__key__, $event)"
|
|
6042
7511
|
tabindex="0">
|
|
@@ -6048,6 +7517,11 @@ class DynamicGridsterPageComponent {
|
|
|
6048
7517
|
(settings)="openWidgetSettings(it.__key__)"
|
|
6049
7518
|
(shell)="openWidgetShellSettings(it.__key__)"
|
|
6050
7519
|
></praxis-tile-toolbar>
|
|
7520
|
+
<div class="pdx-tile-meta" *ngIf="showSettings() && hasTileMetadata(it.__key__)">
|
|
7521
|
+
<span class="pdx-tile-chip pdx-tile-chip--slot" *ngFor="let label of widgetSlotLabels(it.__key__)">{{ label }}</span>
|
|
7522
|
+
<span class="pdx-tile-chip pdx-tile-chip--group" *ngFor="let label of widgetGroupingLabels(it.__key__)">{{ label }}</span>
|
|
7523
|
+
<span class="pdx-tile-chip pdx-tile-chip--device" *ngFor="let label of widgetDeviceLabels(it.__key__)">{{ label }}</span>
|
|
7524
|
+
</div>
|
|
6051
7525
|
<praxis-widget-shell
|
|
6052
7526
|
[shell]="getWidgetShell(it.__key__)"
|
|
6053
7527
|
[context]="mergedContext"
|
|
@@ -6092,17 +7566,51 @@ class DynamicGridsterPageComponent {
|
|
|
6092
7566
|
[adapter]="aiAdapter">
|
|
6093
7567
|
</praxis-ai-assistant>
|
|
6094
7568
|
</praxis-floating-toolbar>
|
|
6095
|
-
`, isInline: true, styles: [":host{display:block;position:relative}:host ::ng-deep gridster{position:relative}:host ::ng-deep gridster.editing:before,:host ::ng-deep gridster.interacting:before{content:\"\";position:absolute;inset:0;pointer-events:none;opacity:.6;z-index:0;background-image:radial-gradient(circle at 1px 1px,var(--md-sys-color-outline-variant) 1px,transparent 1px),radial-gradient(circle,var(--md-sys-color-outline-variant) 1px,transparent 1px);background-size:var(--pdx-col-step, 16px) var(--pdx-row-step, 16px),calc(var(--pdx-col-step, 16px) * 4) calc(var(--pdx-row-step, 16px) * 4)}.pdx-gridster-item{position:relative;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: GridsterComponent, selector: "gridster", inputs: ["options"] }, { kind: "component", type: GridsterItemComponent, selector: "gridster-item", inputs: ["item"], outputs: ["itemInit", "itemChange", "itemResize"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent"], exportAs: ["dynamicWidgetLoader"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "component", type: FloatingToolbarComponent, selector: "praxis-floating-toolbar", inputs: ["visible", "canUndo", "canRedo"], outputs: ["add", "undo", "redo", "settings", "preview", "connections", "connectionsEdit", "connectionsVisual", "save"] }, { kind: "component", type: TileToolbarComponent, selector: "praxis-tile-toolbar", inputs: ["selected", "widgetType"], outputs: ["remove", "settings", "shell"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }, { kind: "component", type: WidgetShellComponent, selector: "praxis-widget-shell", inputs: ["shell", "context"], outputs: ["action"] }] });
|
|
7569
|
+
`, isInline: true, styles: [":host{display:block;position:relative}:host ::ng-deep gridster{position:relative}.pdx-canvas-summary{display:grid;gap:8px;margin-bottom:12px;padding:12px 14px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);border-radius:16px;background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-surface-container-low) 92%,white 8%),color-mix(in srgb,var(--md-sys-color-surface-container) 96%,transparent)),radial-gradient(circle at top right,color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),transparent 58%)}.pdx-canvas-summary__headline{font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.pdx-canvas-summary__chips,.pdx-tile-meta{display:flex;flex-wrap:wrap;gap:6px}.pdx-slot-rail{display:grid;gap:8px;padding-top:4px}.pdx-slot-rail__headline{font-size:12px;font-weight:600;color:var(--md-sys-color-on-surface)}.pdx-slot-rail__chips{display:flex;flex-wrap:wrap;gap:8px}.pdx-slot-rail__help{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.pdx-chip,.pdx-tile-chip{display:inline-flex;align-items:center;min-height:24px;padding:0 10px;border-radius:999px;font-size:11px;font-weight:600;letter-spacing:.01em;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 88%,white 12%);color:var(--md-sys-color-on-surface)}.pdx-chip--preset,.pdx-tile-chip--group{background:color-mix(in srgb,var(--md-sys-color-primary-container) 86%,white 14%);color:var(--md-sys-color-on-primary-container)}.pdx-chip--theme,.pdx-tile-chip--device{background:color-mix(in srgb,var(--md-sys-color-secondary-container) 86%,white 14%);color:var(--md-sys-color-on-secondary-container)}.pdx-tile-chip--slot{background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 86%,white 14%);color:var(--md-sys-color-on-tertiary-container)}.pdx-slot-chip{display:inline-flex;align-items:center;gap:8px;min-height:32px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-highest) 82%,white 18%);color:var(--md-sys-color-on-surface);font:inherit;cursor:default;transition:transform .14s ease,box-shadow .14s ease,border-color .14s ease}.pdx-slot-chip--assignable{cursor:pointer;background:color-mix(in srgb,var(--md-sys-color-primary-container) 82%,white 18%);color:var(--md-sys-color-on-primary-container)}.pdx-slot-chip--assignable:hover{transform:translateY(-1px);box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.pdx-slot-chip--recommended{border-color:color-mix(in srgb,var(--md-sys-color-primary) 56%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent)}.pdx-slot-chip__label{font-weight:700}.pdx-slot-chip__meta{font-size:11px;color:inherit;opacity:.82}:host ::ng-deep gridster.editing:before,:host ::ng-deep gridster.interacting:before{content:\"\";position:absolute;inset:0;pointer-events:none;opacity:.6;z-index:0;background-image:radial-gradient(circle at 1px 1px,var(--md-sys-color-outline-variant) 1px,transparent 1px),radial-gradient(circle,var(--md-sys-color-outline-variant) 1px,transparent 1px);background-size:var(--pdx-col-step, 16px) var(--pdx-row-step, 16px),calc(var(--pdx-col-step, 16px) * 4) calc(var(--pdx-row-step, 16px) * 4)}.pdx-gridster-item{position:relative;height:100%}.pdx-tile-meta{position:absolute;top:44px;left:10px;right:10px;z-index:2;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: GridsterComponent, selector: "gridster", inputs: ["options"] }, { kind: "component", type: GridsterItemComponent, selector: "gridster-item", inputs: ["item"], outputs: ["itemInit", "itemChange", "itemResize"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent"], exportAs: ["dynamicWidgetLoader"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "component", type: FloatingToolbarComponent, selector: "praxis-floating-toolbar", inputs: ["visible", "canUndo", "canRedo"], outputs: ["add", "undo", "redo", "settings", "preview", "connections", "connectionsEdit", "connectionsVisual", "save"] }, { kind: "component", type: TileToolbarComponent, selector: "praxis-tile-toolbar", inputs: ["selected", "widgetType"], outputs: ["remove", "settings", "shell"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }, { kind: "component", type: WidgetShellComponent, selector: "praxis-widget-shell", inputs: ["shell", "context"], outputs: ["action"] }] });
|
|
6096
7570
|
}
|
|
6097
7571
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicGridsterPageComponent, decorators: [{
|
|
6098
7572
|
type: Component,
|
|
6099
7573
|
args: [{ selector: 'praxis-dynamic-gridster-page', standalone: true, imports: [CommonModule, GridsterComponent, GridsterItemComponent, DynamicWidgetLoaderDirective, MatButtonModule, MatIconModule, MatDialogModule, FloatingToolbarComponent, TileToolbarComponent, PraxisAiAssistantComponent, WidgetShellComponent], template: `
|
|
7574
|
+
<div class="pdx-canvas-summary" *ngIf="showSettings() && hasCanvasSummary()">
|
|
7575
|
+
<div class="pdx-canvas-summary__headline">Estrutura da Pagina</div>
|
|
7576
|
+
<div class="pdx-canvas-summary__chips">
|
|
7577
|
+
<span class="pdx-chip pdx-chip--preset" *ngIf="layoutPresetLabel() as label">{{ label }}</span>
|
|
7578
|
+
<span class="pdx-chip pdx-chip--theme" *ngIf="themePresetLabel() as label">{{ label }}</span>
|
|
7579
|
+
<span class="pdx-chip" *ngIf="groupingCount() > 0">{{ groupingCount() }} grupos</span>
|
|
7580
|
+
<span class="pdx-chip" *ngFor="let device of configuredDeviceKinds()">device {{ device }}</span>
|
|
7581
|
+
</div>
|
|
7582
|
+
<div class="pdx-slot-rail" *ngIf="presetSlots().length > 0">
|
|
7583
|
+
<div class="pdx-slot-rail__headline">Slots do preset</div>
|
|
7584
|
+
<div class="pdx-slot-rail__chips">
|
|
7585
|
+
<button
|
|
7586
|
+
type="button"
|
|
7587
|
+
class="pdx-slot-chip"
|
|
7588
|
+
*ngFor="let slot of presetSlots()"
|
|
7589
|
+
[class.pdx-slot-chip--assignable]="canAssignSelectedWidgetToSlot()"
|
|
7590
|
+
[class.pdx-slot-chip--recommended]="isRecommendedSlotForSelectedWidget(slot)"
|
|
7591
|
+
[attr.data-slot-id]="slot.id"
|
|
7592
|
+
[attr.data-slot-occupancy]="slotOccupancy(slot.id)"
|
|
7593
|
+
[disabled]="!canAssignSelectedWidgetToSlot()"
|
|
7594
|
+
(click)="assignSelectedWidgetToSlot(slot.id, $event)"
|
|
7595
|
+
>
|
|
7596
|
+
<span class="pdx-slot-chip__label">{{ slot.label }}</span>
|
|
7597
|
+
<span class="pdx-slot-chip__meta">{{ slotOccupancy(slot.id) }}/{{ slotCapacityLabel(slot) }}</span>
|
|
7598
|
+
</button>
|
|
7599
|
+
</div>
|
|
7600
|
+
<div class="pdx-slot-rail__help" *ngIf="selectedWidgetKey() as key">
|
|
7601
|
+
Widget selecionado: <strong>{{ getWidgetType(key) || key }}</strong>. Clique em um slot para reatribuir o widget dentro do preset.
|
|
7602
|
+
</div>
|
|
7603
|
+
</div>
|
|
7604
|
+
</div>
|
|
6100
7605
|
<gridster [options]="options" [class.editing]="overlayEnabled && showSettings()" [class.interacting]="overlayEnabled && interacting">
|
|
6101
7606
|
<gridster-item *ngFor="let it of items(); trackBy: trackItem" [item]="it">
|
|
6102
7607
|
<div class="pdx-gridster-item mat-elevation-z4"
|
|
6103
7608
|
[attr.id]="computeTileDomId(it.__key__)"
|
|
6104
7609
|
[attr.data-widget-key]="it.__key__"
|
|
6105
7610
|
[attr.data-widget-type]="getWidgetType(it.__key__)"
|
|
7611
|
+
[attr.data-group-count]="widgetGroupingLabels(it.__key__).length"
|
|
7612
|
+
[attr.data-device-count]="widgetDeviceLabels(it.__key__).length"
|
|
7613
|
+
[attr.data-slot-count]="widgetSlotLabels(it.__key__).length"
|
|
6106
7614
|
[class.selected]="isSelected(it.__key__)"
|
|
6107
7615
|
(click)="onTileClick(it.__key__, $event)"
|
|
6108
7616
|
tabindex="0">
|
|
@@ -6114,6 +7622,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6114
7622
|
(settings)="openWidgetSettings(it.__key__)"
|
|
6115
7623
|
(shell)="openWidgetShellSettings(it.__key__)"
|
|
6116
7624
|
></praxis-tile-toolbar>
|
|
7625
|
+
<div class="pdx-tile-meta" *ngIf="showSettings() && hasTileMetadata(it.__key__)">
|
|
7626
|
+
<span class="pdx-tile-chip pdx-tile-chip--slot" *ngFor="let label of widgetSlotLabels(it.__key__)">{{ label }}</span>
|
|
7627
|
+
<span class="pdx-tile-chip pdx-tile-chip--group" *ngFor="let label of widgetGroupingLabels(it.__key__)">{{ label }}</span>
|
|
7628
|
+
<span class="pdx-tile-chip pdx-tile-chip--device" *ngFor="let label of widgetDeviceLabels(it.__key__)">{{ label }}</span>
|
|
7629
|
+
</div>
|
|
6117
7630
|
<praxis-widget-shell
|
|
6118
7631
|
[shell]="getWidgetShell(it.__key__)"
|
|
6119
7632
|
[context]="mergedContext"
|
|
@@ -6158,7 +7671,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6158
7671
|
[adapter]="aiAdapter">
|
|
6159
7672
|
</praxis-ai-assistant>
|
|
6160
7673
|
</praxis-floating-toolbar>
|
|
6161
|
-
`, styles: [":host{display:block;position:relative}:host ::ng-deep gridster{position:relative}:host ::ng-deep gridster.editing:before,:host ::ng-deep gridster.interacting:before{content:\"\";position:absolute;inset:0;pointer-events:none;opacity:.6;z-index:0;background-image:radial-gradient(circle at 1px 1px,var(--md-sys-color-outline-variant) 1px,transparent 1px),radial-gradient(circle,var(--md-sys-color-outline-variant) 1px,transparent 1px);background-size:var(--pdx-col-step, 16px) var(--pdx-row-step, 16px),calc(var(--pdx-col-step, 16px) * 4) calc(var(--pdx-row-step, 16px) * 4)}.pdx-gridster-item{position:relative;height:100%}\n"] }]
|
|
7674
|
+
`, styles: [":host{display:block;position:relative}:host ::ng-deep gridster{position:relative}.pdx-canvas-summary{display:grid;gap:8px;margin-bottom:12px;padding:12px 14px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);border-radius:16px;background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-surface-container-low) 92%,white 8%),color-mix(in srgb,var(--md-sys-color-surface-container) 96%,transparent)),radial-gradient(circle at top right,color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),transparent 58%)}.pdx-canvas-summary__headline{font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.pdx-canvas-summary__chips,.pdx-tile-meta{display:flex;flex-wrap:wrap;gap:6px}.pdx-slot-rail{display:grid;gap:8px;padding-top:4px}.pdx-slot-rail__headline{font-size:12px;font-weight:600;color:var(--md-sys-color-on-surface)}.pdx-slot-rail__chips{display:flex;flex-wrap:wrap;gap:8px}.pdx-slot-rail__help{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.pdx-chip,.pdx-tile-chip{display:inline-flex;align-items:center;min-height:24px;padding:0 10px;border-radius:999px;font-size:11px;font-weight:600;letter-spacing:.01em;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 88%,white 12%);color:var(--md-sys-color-on-surface)}.pdx-chip--preset,.pdx-tile-chip--group{background:color-mix(in srgb,var(--md-sys-color-primary-container) 86%,white 14%);color:var(--md-sys-color-on-primary-container)}.pdx-chip--theme,.pdx-tile-chip--device{background:color-mix(in srgb,var(--md-sys-color-secondary-container) 86%,white 14%);color:var(--md-sys-color-on-secondary-container)}.pdx-tile-chip--slot{background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 86%,white 14%);color:var(--md-sys-color-on-tertiary-container)}.pdx-slot-chip{display:inline-flex;align-items:center;gap:8px;min-height:32px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-highest) 82%,white 18%);color:var(--md-sys-color-on-surface);font:inherit;cursor:default;transition:transform .14s ease,box-shadow .14s ease,border-color .14s ease}.pdx-slot-chip--assignable{cursor:pointer;background:color-mix(in srgb,var(--md-sys-color-primary-container) 82%,white 18%);color:var(--md-sys-color-on-primary-container)}.pdx-slot-chip--assignable:hover{transform:translateY(-1px);box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.pdx-slot-chip--recommended{border-color:color-mix(in srgb,var(--md-sys-color-primary) 56%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent)}.pdx-slot-chip__label{font-weight:700}.pdx-slot-chip__meta{font-size:11px;color:inherit;opacity:.82}:host ::ng-deep gridster.editing:before,:host ::ng-deep gridster.interacting:before{content:\"\";position:absolute;inset:0;pointer-events:none;opacity:.6;z-index:0;background-image:radial-gradient(circle at 1px 1px,var(--md-sys-color-outline-variant) 1px,transparent 1px),radial-gradient(circle,var(--md-sys-color-outline-variant) 1px,transparent 1px);background-size:var(--pdx-col-step, 16px) var(--pdx-row-step, 16px),calc(var(--pdx-col-step, 16px) * 4) calc(var(--pdx-row-step, 16px) * 4)}.pdx-gridster-item{position:relative;height:100%}.pdx-tile-meta{position:absolute;top:44px;left:10px;right:10px;z-index:2;pointer-events:none}\n"] }]
|
|
6162
7675
|
}], ctorParameters: () => [{ type: i1$1.ConnectionManagerService }, { type: i2$3.Router }, { type: i0.ElementRef }, { type: i1.MatDialog }, { type: GraphMapperService }, { type: i1$1.ComponentMetadataRegistry }, { type: undefined, decorators: [{
|
|
6163
7676
|
type: Inject,
|
|
6164
7677
|
args: [ASYNC_CONFIG_STORAGE]
|