@praxisui/page-builder
Version:
Page and widget builder utilities for Praxis UI (grid, dynamic widgets, editors).
597 lines (589 loc) • 84.8 kB
JavaScript
import * as i3$2 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { Inject, ChangeDetectionStrategy, Component, EventEmitter, Output, Input, signal, computed, effect } from '@angular/core';
import * as i2 from '@angular/material/button';
import { MatButtonModule } from '@angular/material/button';
import * as i1 from '@angular/material/dialog';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import * as i3 from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import * as i2$1 from '@praxisui/core';
import { PraxisIconDirective } from '@praxisui/core';
import * as i3$1 from '@angular/material/tooltip';
import { MatTooltipModule } from '@angular/material/tooltip';
import * as i6 from '@angular/material/form-field';
import { MatFormFieldModule } from '@angular/material/form-field';
import * as i7 from '@angular/material/input';
import { MatInputModule } from '@angular/material/input';
import * as i4 from '@angular/forms';
import { FormsModule } from '@angular/forms';
import * as i9 from '@angular/material/select';
import { MatSelectModule } from '@angular/material/select';
import * as i12 from '@angular/material/list';
import { MatListModule } from '@angular/material/list';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatMenuModule } from '@angular/material/menu';
import * as i3$3 from '@angular/material/snack-bar';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTabsModule } from '@angular/material/tabs';
import * as i8 from '@angular/cdk/scrolling';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatCheckboxModule } from '@angular/material/checkbox';
const PLACEHOLDER = 1;
class ConfirmDialogComponent {
data;
ref;
constructor(data, ref) {
this.data = data;
this.ref = ref;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogComponent, deps: [{ token: MAT_DIALOG_DATA }, { token: i1.MatDialogRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: ConfirmDialogComponent, isStandalone: true, selector: "praxis-confirm-dialog", ngImport: i0, template: `
<h2 mat-dialog-title>
<mat-icon aria-hidden="true" style="vertical-align: middle; margin-right: 8px;" [praxisIcon]="data.icon || 'warning'"></mat-icon>
{{ data.title || 'Confirmar ação' }}
</h2>
<div mat-dialog-content>
<p>{{ data.message || 'Tem certeza que deseja prosseguir?' }}</p>
</div>
<div mat-dialog-actions align="end">
<button mat-button mat-dialog-close>{{ data.cancelLabel || 'Cancelar' }}</button>
<button mat-raised-button color="warn" [mat-dialog-close]="true">{{ data.confirmLabel || 'Excluir' }}</button>
</div>
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { 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: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogComponent, decorators: [{
type: Component,
args: [{
selector: 'praxis-confirm-dialog',
standalone: true,
imports: [CommonModule, MatDialogModule, MatButtonModule, MatIconModule, PraxisIconDirective],
template: `
<h2 mat-dialog-title>
<mat-icon aria-hidden="true" style="vertical-align: middle; margin-right: 8px;" [praxisIcon]="data.icon || 'warning'"></mat-icon>
{{ data.title || 'Confirmar ação' }}
</h2>
<div mat-dialog-content>
<p>{{ data.message || 'Tem certeza que deseja prosseguir?' }}</p>
</div>
<div mat-dialog-actions align="end">
<button mat-button mat-dialog-close>{{ data.cancelLabel || 'Cancelar' }}</button>
<button mat-raised-button color="warn" [mat-dialog-close]="true">{{ data.confirmLabel || 'Excluir' }}</button>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Inject,
args: [MAT_DIALOG_DATA]
}] }, { type: i1.MatDialogRef }] });
class TileToolbarComponent {
selected = false;
widgetType = null;
remove = new EventEmitter();
settings = new EventEmitter();
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TileToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: TileToolbarComponent, isStandalone: true, selector: "praxis-tile-toolbar", inputs: { selected: "selected", widgetType: "widgetType" }, outputs: { remove: "remove", settings: "settings" }, ngImport: i0, template: `
<div class="pdx-tile-toolbar" [class.always-visible]="selected" role="toolbar" aria-label="Ações do widget">
<button
mat-mini-fab
(click)="settings.emit()"
[matTooltip]="'Configurar widget'"
aria-label="Configurar widget"
>
<mat-icon [praxisIcon]="'settings'"></mat-icon>
</button>
<button
mat-mini-fab
color="primary"
class="pdx-drag-handle"
[matTooltip]="'Arrastar tile'"
aria-label="Arrastar tile"
>
<mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
</button>
<button
mat-mini-fab
color="warn"
(click)="remove.emit()"
[matTooltip]="'Excluir widget'"
aria-label="Excluir widget"
>
<mat-icon [praxisIcon]="'delete'"></mat-icon>
</button>
</div>
`, isInline: true, styles: [":host{position:absolute;inset:0;pointer-events:none}.pdx-tile-toolbar{position:absolute;right:6px;top:-18px;display:flex;gap:6px;z-index:5;opacity:0;transition:opacity .12s ease-in-out;pointer-events:auto;background:color-mix(in oklab,var(--md-sys-color-surface) 92%,transparent);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border-radius:999px;padding:4px;box-shadow:var(--mat-elevation-level3, 0 4px 8px rgba(0,0,0,.2))}.pdx-tile-toolbar.always-visible{opacity:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: MatTooltipModule }, { kind: "directive", type: i3$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TileToolbarComponent, decorators: [{
type: Component,
args: [{ selector: 'praxis-tile-toolbar', standalone: true, imports: [CommonModule, MatButtonModule, MatIconModule, MatTooltipModule, PraxisIconDirective], template: `
<div class="pdx-tile-toolbar" [class.always-visible]="selected" role="toolbar" aria-label="Ações do widget">
<button
mat-mini-fab
(click)="settings.emit()"
[matTooltip]="'Configurar widget'"
aria-label="Configurar widget"
>
<mat-icon [praxisIcon]="'settings'"></mat-icon>
</button>
<button
mat-mini-fab
color="primary"
class="pdx-drag-handle"
[matTooltip]="'Arrastar tile'"
aria-label="Arrastar tile"
>
<mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
</button>
<button
mat-mini-fab
color="warn"
(click)="remove.emit()"
[matTooltip]="'Excluir widget'"
aria-label="Excluir widget"
>
<mat-icon [praxisIcon]="'delete'"></mat-icon>
</button>
</div>
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{position:absolute;inset:0;pointer-events:none}.pdx-tile-toolbar{position:absolute;right:6px;top:-18px;display:flex;gap:6px;z-index:5;opacity:0;transition:opacity .12s ease-in-out;pointer-events:auto;background:color-mix(in oklab,var(--md-sys-color-surface) 92%,transparent);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border-radius:999px;padding:4px;box-shadow:var(--mat-elevation-level3, 0 4px 8px rgba(0,0,0,.2))}.pdx-tile-toolbar.always-visible{opacity:1}\n"] }]
}], propDecorators: { selected: [{
type: Input
}], widgetType: [{
type: Input
}], remove: [{
type: Output
}], settings: [{
type: Output
}] } });
class FloatingToolbarComponent {
visible = false;
canUndo = false;
canRedo = false;
add = new EventEmitter();
undo = new EventEmitter();
redo = new EventEmitter();
settings = new EventEmitter();
preview = new EventEmitter();
connections = new EventEmitter();
connectionsEdit = new EventEmitter();
connectionsVisual = new EventEmitter();
save = new EventEmitter();
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FloatingToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: FloatingToolbarComponent, isStandalone: true, selector: "praxis-floating-toolbar", inputs: { visible: "visible", canUndo: "canUndo", canRedo: "canRedo" }, outputs: { add: "add", undo: "undo", redo: "redo", settings: "settings", preview: "preview", connections: "connections", connectionsEdit: "connectionsEdit", connectionsVisual: "connectionsVisual", save: "save" }, ngImport: i0, template: `
<div class="pdx-floating-toolbar" *ngIf="visible">
<button mat-fab color="primary" class="pdx-fab" [matTooltip]="'Ações rápidas'">
<mat-icon [praxisIcon]="'build'"></mat-icon>
</button>
<div class="pdx-actions">
<button mat-mini-fab color="primary" (click)="add.emit()" [matTooltip]="'Inserir componente'" aria-label="Inserir componente">
<mat-icon [praxisIcon]="'add'"></mat-icon>
</button>
<button mat-mini-fab (click)="connections.emit()" [matTooltip]="'Visualizar conexões (Diagrama)'" aria-label="Visualizar conexões (Diagrama)">
<mat-icon [praxisIcon]="'device_hub'"></mat-icon>
</button>
<button mat-mini-fab (click)="connectionsVisual.emit()" [matTooltip]="'Editor Visual (beta)'" aria-label="Editor Visual (beta)">
<mat-icon [praxisIcon]="'schema'"></mat-icon>
</button>
<button mat-mini-fab (click)="connectionsEdit.emit()" [matTooltip]="'Editar conexões (Builder)'" aria-label="Editar conexões (Builder)">
<mat-icon [praxisIcon]="'tune'"></mat-icon>
</button>
<button mat-mini-fab color="accent" (click)="save.emit()" [matTooltip]="'Salvar página'" aria-label="Salvar página">
<mat-icon [praxisIcon]="'save'"></mat-icon>
</button>
<button mat-mini-fab (click)="undo.emit()" [disabled]="!canUndo" [matTooltip]="'Desfazer'" aria-label="Desfazer">
<mat-icon [praxisIcon]="'undo'"></mat-icon>
</button>
<button mat-mini-fab (click)="redo.emit()" [disabled]="!canRedo" [matTooltip]="'Refazer'" aria-label="Refazer">
<mat-icon [praxisIcon]="'redo'"></mat-icon>
</button>
<button mat-mini-fab (click)="settings.emit()" [matTooltip]="'Configurações da página'" aria-label="Configurações da página">
<mat-icon [praxisIcon]="'settings'"></mat-icon>
</button>
<button mat-mini-fab (click)="preview.emit()" [matTooltip]="'Pré-visualizar'" aria-label="Pré-visualizar">
<mat-icon [praxisIcon]="'visibility'"></mat-icon>
</button>
</div>
</div>
`, isInline: true, styles: [":host{position:absolute;inset:0;pointer-events:none}.pdx-floating-toolbar{position:absolute;right:16px;bottom:16px;display:flex;gap:8px;align-items:center;pointer-events:auto}.pdx-actions{display:flex;gap:8px}.pdx-fab{box-shadow:var(--mat-elevation-transition),var(--mat-elevation-level6, 0 6px 12px rgba(0,0,0,.24))}@media (max-width: 720px){.pdx-actions{gap:6px}.pdx-floating-toolbar{right:12px;bottom:12px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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: "component", type: i2.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], 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: i3$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FloatingToolbarComponent, decorators: [{
type: Component,
args: [{ selector: 'praxis-floating-toolbar', standalone: true, imports: [CommonModule, MatButtonModule, MatIconModule, MatTooltipModule, PraxisIconDirective], template: `
<div class="pdx-floating-toolbar" *ngIf="visible">
<button mat-fab color="primary" class="pdx-fab" [matTooltip]="'Ações rápidas'">
<mat-icon [praxisIcon]="'build'"></mat-icon>
</button>
<div class="pdx-actions">
<button mat-mini-fab color="primary" (click)="add.emit()" [matTooltip]="'Inserir componente'" aria-label="Inserir componente">
<mat-icon [praxisIcon]="'add'"></mat-icon>
</button>
<button mat-mini-fab (click)="connections.emit()" [matTooltip]="'Visualizar conexões (Diagrama)'" aria-label="Visualizar conexões (Diagrama)">
<mat-icon [praxisIcon]="'device_hub'"></mat-icon>
</button>
<button mat-mini-fab (click)="connectionsVisual.emit()" [matTooltip]="'Editor Visual (beta)'" aria-label="Editor Visual (beta)">
<mat-icon [praxisIcon]="'schema'"></mat-icon>
</button>
<button mat-mini-fab (click)="connectionsEdit.emit()" [matTooltip]="'Editar conexões (Builder)'" aria-label="Editar conexões (Builder)">
<mat-icon [praxisIcon]="'tune'"></mat-icon>
</button>
<button mat-mini-fab color="accent" (click)="save.emit()" [matTooltip]="'Salvar página'" aria-label="Salvar página">
<mat-icon [praxisIcon]="'save'"></mat-icon>
</button>
<button mat-mini-fab (click)="undo.emit()" [disabled]="!canUndo" [matTooltip]="'Desfazer'" aria-label="Desfazer">
<mat-icon [praxisIcon]="'undo'"></mat-icon>
</button>
<button mat-mini-fab (click)="redo.emit()" [disabled]="!canRedo" [matTooltip]="'Refazer'" aria-label="Refazer">
<mat-icon [praxisIcon]="'redo'"></mat-icon>
</button>
<button mat-mini-fab (click)="settings.emit()" [matTooltip]="'Configurações da página'" aria-label="Configurações da página">
<mat-icon [praxisIcon]="'settings'"></mat-icon>
</button>
<button mat-mini-fab (click)="preview.emit()" [matTooltip]="'Pré-visualizar'" aria-label="Pré-visualizar">
<mat-icon [praxisIcon]="'visibility'"></mat-icon>
</button>
</div>
</div>
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{position:absolute;inset:0;pointer-events:none}.pdx-floating-toolbar{position:absolute;right:16px;bottom:16px;display:flex;gap:8px;align-items:center;pointer-events:auto}.pdx-actions{display:flex;gap:8px}.pdx-fab{box-shadow:var(--mat-elevation-transition),var(--mat-elevation-level6, 0 6px 12px rgba(0,0,0,.24))}@media (max-width: 720px){.pdx-actions{gap:6px}.pdx-floating-toolbar{right:12px;bottom:12px}}\n"] }]
}], propDecorators: { visible: [{
type: Input
}], canUndo: [{
type: Input
}], canRedo: [{
type: Input
}], add: [{
type: Output
}], undo: [{
type: Output
}], redo: [{
type: Output
}], settings: [{
type: Output
}], preview: [{
type: Output
}], connections: [{
type: Output
}], connectionsEdit: [{
type: Output
}], connectionsVisual: [{
type: Output
}], save: [{
type: Output
}] } });
class ComponentPaletteDialogComponent {
dialogRef;
registry;
data;
query = '';
all = signal([], ...(ngDevMode ? [{ debugName: "all" }] : []));
filtered = computed(() => this.applyFilters(), ...(ngDevMode ? [{ debugName: "filtered" }] : []));
density = computed(() => {
const n = this.filtered().length;
if (n <= 4)
return 'roomy';
if (n > 12)
return 'dense';
return 'normal';
}, ...(ngDevMode ? [{ debugName: "density" }] : []));
constructor(dialogRef, registry, data) {
this.dialogRef = dialogRef;
this.registry = registry;
this.data = data;
}
ngOnInit() {
this.all.set(this.registry.getAll());
}
select(id) {
this.dialogRef.close(id);
}
trackById = (_, m) => m.id;
getPreview(m) {
return (m.description || m.selector || m.friendlyName || m.id || '').toString();
}
// Create effect in injection context (field initializer is allowed)
_adjustSizeEffect = effect(() => {
const n = this.filtered().length;
let width = '720px';
if (n <= 4)
width = '560px';
else if (n <= 8)
width = '680px';
const container = this.dialogRef._containerInstance;
if (container && container._config) {
container._config.width = width;
}
}, ...(ngDevMode ? [{ debugName: "_adjustSizeEffect" }] : []));
applyFilters() {
const list = (this.all() || []);
const q = (this.query || '').toLowerCase();
const allowIds = new Set((this.data?.allowedWidgetIds || []).map((s) => s.toLowerCase()));
const allowTags = new Set((this.data?.allowedWidgetTags || []).map((s) => s.toLowerCase()));
return list.filter((m) => {
if (q) {
const hay = `${m.id} ${m.friendlyName || ''} ${m.description || ''} ${m.selector || ''}`.toLowerCase();
if (!hay.includes(q))
return false;
}
if (allowIds.size > 0 && !allowIds.has((m.id || '').toLowerCase()))
return false;
if (allowTags.size > 0 && !m.tags?.some(t => allowTags.has(t.toLowerCase())))
return false;
if (typeof this.data?.predicate === 'function' && !this.data.predicate(m))
return false;
return true;
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ComponentPaletteDialogComponent, deps: [{ token: i1.MatDialogRef }, { token: i2$1.ComponentMetadataRegistry }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: ComponentPaletteDialogComponent, isStandalone: true, selector: "praxis-component-palette-dialog", ngImport: i0, template: `
<h2 mat-dialog-title>
<div class="dlg-title">
<mat-icon class="dlg-icon" [praxisIcon]="'widgets'"></mat-icon>
<div class="dlg-texts">
<div class="dlg-head">{{ data?.title || 'Inserir componente' }}</div>
<div class="dlg-sub">Escolha um componente para adicionar à página · {{ filtered().length }} disponíveis</div>
</div>
</div>
</h2>
<div mat-dialog-content class="pdx-palette-content">
<div class="pdx-grid" [ngClass]="density()" *ngIf="filtered().length; else emptyState">
<div
*ngFor="let m of filtered(); trackBy: trackById"
class="pdx-card mat-elevation-z2"
tabindex="0"
role="button"
[attr.aria-label]="'Adicionar ' + (m.friendlyName || m.id)"
(click)="select(m.id)"
(keydown.enter)="select(m.id)"
(keydown.space)="select(m.id)"
>
<div class="pdx-card-icon">
<mat-icon [praxisIcon]="m.icon || 'widgets'"></mat-icon>
</div>
<div class="pdx-card-body">
<div class="pdx-card-title">{{ m.friendlyName || m.id }}</div>
<div class="pdx-card-desc">{{ m.description || m.selector }}</div>
</div>
</div>
</div>
<ng-template #emptyState>
<div class="pdx-empty">
<mat-icon [praxisIcon]="'sentiment_dissatisfied'"></mat-icon>
<div>Nenhum componente disponível</div>
</div>
</ng-template>
</div>
<div mat-dialog-actions align="end">
<button mat-stroked-button color="primary" mat-dialog-close>Cancelar</button>
</div>
`, isInline: true, styles: [":host{display:block}h2[mat-dialog-title]{margin:0;padding:12px 24px 10px;border-bottom:1px solid var(--md-sys-color-outline-variant, rgba(0,0,0,.12))}.dlg-title{display:flex;align-items:center;gap:10px}.dlg-icon{color:var(--md-sys-color-primary, #3f51b5)}.dlg-head{font-size:18px;font-weight:600;line-height:1.2}.dlg-sub{font-size:12px;opacity:.85;color:var(--md-sys-color-on-surface-variant, rgba(0,0,0,.62))}.pdx-palette-content{min-width:480px;max-width:920px;padding-top:8px}.pdx-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px}.pdx-grid.dense{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}.pdx-grid.roomy{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}.pdx-card{position:relative;display:grid;grid-template-columns:44px 1fr;grid-template-rows:auto auto;gap:10px;padding:14px;border:1px solid transparent;border-radius:14px;cursor:pointer;outline:none;min-height:92px;background:linear-gradient(var(--md-sys-color-surface),var(--md-sys-color-surface)) padding-box,linear-gradient(135deg,color-mix(in oklab,var(--md-sys-color-primary, #3f51b5) 55%,transparent),color-mix(in oklab,var(--md-sys-color-secondary, #ff4081) 45%,transparent)) border-box;transition:box-shadow .2s ease,background .25s ease,transform .06s ease}.pdx-grid.dense .pdx-card{padding:10px;border-radius:12px;min-height:84px}.pdx-grid.roomy .pdx-card{padding:16px;border-radius:16px;min-height:100px}.pdx-card:focus,.pdx-card:hover{box-shadow:var(--mat-elevation-transition),var(--mat-elevation-level6, 0 6px 12px rgba(0,0,0,.22));background:linear-gradient(var(--md-sys-color-surface),var(--md-sys-color-surface)) padding-box,linear-gradient(135deg,color-mix(in oklab,var(--md-sys-color-primary) 70%,transparent),color-mix(in oklab,var(--md-sys-color-secondary) 55%,transparent)) border-box}.pdx-card:active{transform:translateY(1px)}.pdx-card-icon{grid-row:span 2;display:flex;align-items:center;justify-content:center;color:color-mix(in oklab,var(--md-sys-color-primary) 70%,#666)}.pdx-card-title{font-weight:600;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pdx-card-desc{color:#000000b3;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.pdx-empty{padding:24px;display:grid;place-items:center;color:#0009;gap:8px}@media (max-width: 600px){.pdx-palette-content{min-width:320px}.pdx-grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { 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: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ComponentPaletteDialogComponent, decorators: [{
type: Component,
args: [{ selector: 'praxis-component-palette-dialog', standalone: true, imports: [CommonModule, FormsModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatIconModule, MatButtonModule, PraxisIconDirective], template: `
<h2 mat-dialog-title>
<div class="dlg-title">
<mat-icon class="dlg-icon" [praxisIcon]="'widgets'"></mat-icon>
<div class="dlg-texts">
<div class="dlg-head">{{ data?.title || 'Inserir componente' }}</div>
<div class="dlg-sub">Escolha um componente para adicionar à página · {{ filtered().length }} disponíveis</div>
</div>
</div>
</h2>
<div mat-dialog-content class="pdx-palette-content">
<div class="pdx-grid" [ngClass]="density()" *ngIf="filtered().length; else emptyState">
<div
*ngFor="let m of filtered(); trackBy: trackById"
class="pdx-card mat-elevation-z2"
tabindex="0"
role="button"
[attr.aria-label]="'Adicionar ' + (m.friendlyName || m.id)"
(click)="select(m.id)"
(keydown.enter)="select(m.id)"
(keydown.space)="select(m.id)"
>
<div class="pdx-card-icon">
<mat-icon [praxisIcon]="m.icon || 'widgets'"></mat-icon>
</div>
<div class="pdx-card-body">
<div class="pdx-card-title">{{ m.friendlyName || m.id }}</div>
<div class="pdx-card-desc">{{ m.description || m.selector }}</div>
</div>
</div>
</div>
<ng-template #emptyState>
<div class="pdx-empty">
<mat-icon [praxisIcon]="'sentiment_dissatisfied'"></mat-icon>
<div>Nenhum componente disponível</div>
</div>
</ng-template>
</div>
<div mat-dialog-actions align="end">
<button mat-stroked-button color="primary" mat-dialog-close>Cancelar</button>
</div>
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}h2[mat-dialog-title]{margin:0;padding:12px 24px 10px;border-bottom:1px solid var(--md-sys-color-outline-variant, rgba(0,0,0,.12))}.dlg-title{display:flex;align-items:center;gap:10px}.dlg-icon{color:var(--md-sys-color-primary, #3f51b5)}.dlg-head{font-size:18px;font-weight:600;line-height:1.2}.dlg-sub{font-size:12px;opacity:.85;color:var(--md-sys-color-on-surface-variant, rgba(0,0,0,.62))}.pdx-palette-content{min-width:480px;max-width:920px;padding-top:8px}.pdx-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px}.pdx-grid.dense{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}.pdx-grid.roomy{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}.pdx-card{position:relative;display:grid;grid-template-columns:44px 1fr;grid-template-rows:auto auto;gap:10px;padding:14px;border:1px solid transparent;border-radius:14px;cursor:pointer;outline:none;min-height:92px;background:linear-gradient(var(--md-sys-color-surface),var(--md-sys-color-surface)) padding-box,linear-gradient(135deg,color-mix(in oklab,var(--md-sys-color-primary, #3f51b5) 55%,transparent),color-mix(in oklab,var(--md-sys-color-secondary, #ff4081) 45%,transparent)) border-box;transition:box-shadow .2s ease,background .25s ease,transform .06s ease}.pdx-grid.dense .pdx-card{padding:10px;border-radius:12px;min-height:84px}.pdx-grid.roomy .pdx-card{padding:16px;border-radius:16px;min-height:100px}.pdx-card:focus,.pdx-card:hover{box-shadow:var(--mat-elevation-transition),var(--mat-elevation-level6, 0 6px 12px rgba(0,0,0,.22));background:linear-gradient(var(--md-sys-color-surface),var(--md-sys-color-surface)) padding-box,linear-gradient(135deg,color-mix(in oklab,var(--md-sys-color-primary) 70%,transparent),color-mix(in oklab,var(--md-sys-color-secondary) 55%,transparent)) border-box}.pdx-card:active{transform:translateY(1px)}.pdx-card-icon{grid-row:span 2;display:flex;align-items:center;justify-content:center;color:color-mix(in oklab,var(--md-sys-color-primary) 70%,#666)}.pdx-card-title{font-weight:600;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pdx-card-desc{color:#000000b3;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.pdx-empty{padding:24px;display:grid;place-items:center;color:#0009;gap:8px}@media (max-width: 600px){.pdx-palette-content{min-width:320px}.pdx-grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}}\n"] }]
}], ctorParameters: () => [{ type: i1.MatDialogRef }, { type: i2$1.ComponentMetadataRegistry }, { type: undefined, decorators: [{
type: Inject,
args: [MAT_DIALOG_DATA]
}] }] });
class ConnectionBuilderComponent {
dialog;
registry;
snack;
page;
widgets;
pageChange = new EventEmitter();
// State
originalSnapshot = '';
showOnlyIssues = false;
showFriendly = true;
filterText = '';
groupBy = 'none';
sortBy = 'from';
// Move braces-containing placeholder into TS (AGENTS.md policy)
mapPlaceholder = 'payload | payload.id | ${payload.id}';
// Signals
connections = signal([], ...(ngDevMode ? [{ debugName: "connections" }] : []));
selectedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "selectedIndex" }] : []));
// Derived lists
filteredConnections = computed(() => this.applyFilters(this.connections()), ...(ngDevMode ? [{ debugName: "filteredConnections" }] : []));
groupedConnections = computed(() => this.applyGrouping(this.filteredConnections()), ...(ngDevMode ? [{ debugName: "groupedConnections" }] : []));
constructor(dialog, registry, snack) {
this.dialog = dialog;
this.registry = registry;
this.snack = snack;
}
ngOnInit() {
const p = this.parsePage(this.page);
const ws = this.widgets || p?.widgets || [];
this.widgets = ws;
const conns = [...(p?.connections || [])];
this.connections.set(conns);
this.originalSnapshot = JSON.stringify(conns);
}
ngOnChanges(changes) {
if (changes['page'] || changes['widgets']) {
const p = this.parsePage(this.page);
this.widgets = this.widgets || p?.widgets || [];
const next = [...(p?.connections || [])];
this.connections.set(next);
this.originalSnapshot = JSON.stringify(next);
}
}
// UI helpers
isExpanded(i) { return i === this.selectedIndex(); }
toggleExpanded(i, ev) { if (ev)
ev.stopPropagation(); this.selectedIndex.set(this.selectedIndex() === i ? -1 : i); }
setSortBy(v) { this.sortBy = v; this.connections.set(this.sortList([...this.connections()])); }
onGroupByChange() { }
toggleShowOnlyIssues() { this.showOnlyIssues = !this.showOnlyIssues; }
toggleFriendly() { this.showFriendly = !this.showFriendly; }
trackByIndex = (i) => i;
// Labels
fromLabel(c) { return `${c.from.widget}.${c.from.output}`; }
toLabel(c) { return `${c.to.widget}.${c.to.input}`; }
fromFriendly(c) { return `${this.widgetFriendlyNameForKey(c.from.widget)}.${c.from.output}`; }
toFriendly(c) { return `${this.widgetFriendlyNameForKey(c.to.widget)}.${c.to.input}`; }
outputDescription(c) { return this.registry.get(this.widgetTypeByKey(c.from.widget))?.outputs?.find(o => o.name === c.from.output)?.description || ''; }
inputDescription(c) { return this.registry.get(this.widgetTypeByKey(c.to.widget))?.inputs?.find(i => i.name === c.to.input)?.description || ''; }
widgetFriendlyNameForKey(key) { const id = this.widgetTypeByKey(key); return this.registry.get(id)?.friendlyName || id; }
componentIconForKey(key) { const id = this.widgetTypeByKey(key); return this.registry.get(id)?.icon || 'widgets'; }
widgetTypeByKey(key) { return this.widgets?.find(w => w.key === key)?.definition?.id || key; }
applyFilters(list) {
const q = (this.filterText || '').toLowerCase();
const arr = list.filter((c) => {
if (this.showOnlyIssues && this.connectionStatus(c) === 'ok')
return false;
if (!q)
return true;
const hay = `${c.from.widget}.${c.from.output} ${c.to.widget}.${c.to.input} ${c.map || ''}`.toLowerCase();
return hay.includes(q);
});
return this.sortList(arr);
}
applyGrouping(list) {
switch (this.groupBy) {
case 'from': return this.groupByKey(list, c => `${c.from.widget}.${c.from.output}`);
case 'to': return this.groupByKey(list, c => `${c.to.widget}.${c.to.input}`);
case 'event': return this.groupByKey(list, c => `${c.from.output}`);
default: return [{ label: 'Todas', list }];
}
}
groupByKey(list, keyFn) {
const map = new Map();
for (const c of list) {
const k = keyFn(c);
const arr = map.get(k) || [];
arr.push(c);
map.set(k, arr);
}
return Array.from(map.entries()).map(([k, v]) => ({ label: k, list: v }));
}
sortList(list) {
return [...list].sort((a, b) => {
if (this.sortBy === 'from')
return this.fromLabel(a).localeCompare(this.fromLabel(b));
return this.toLabel(a).localeCompare(this.toLabel(b));
});
}
// Editing
createNew() {
const fromKey = this.widgets?.[0]?.key || '';
const toKey = this.widgets?.[1]?.key || '';
const conn = { from: { widget: fromKey, output: 'submit' }, to: { widget: toKey, input: 'context' } };
this.connections.set([conn, ...this.connections()]);
}
startEdit(index, _c) { this.selectedIndex.set(index); }
startEditByConn(c) { const i = this.connections().indexOf(c); if (i >= 0)
this.startEdit(i, c); }
duplicateConnection(index) { const next = [...this.connections()]; const c = next[index]; if (!c)
return; next.splice(index + 1, 0, { ...c }); this.connections.set(next); }
removeConnection(index) { const next = [...this.connections()]; if (index < 0 || index >= next.length)
return; next.splice(index, 1); this.connections.set(next); }
// Validation
connectionStatus(c) {
if (!c.from?.widget || !c.from?.output || !c.to?.widget || !c.to?.input)
return 'err';
const wFrom = this.widgetTypeByKey(c.from.widget);
const wTo = this.widgetTypeByKey(c.to.widget);
const outOk = !!this.registry.get(wFrom)?.outputs?.some(o => o.name === c.from.output);
const inOk = !!this.registry.get(wTo)?.inputs?.some(i => i.name === c.to.input);
if (!outOk || !inOk)
return 'warn';
return 'ok';
}
// Persist/apply
onSave() {
const p = this.parsePage(this.page) || {};
const next = { ...p, connections: this.connections() };
this.page = next;
this.pageChange.emit(next);
this.snack.open('Conexões salvas', undefined, { duration: 1000 });
this.originalSnapshot = JSON.stringify(this.connections());
}
// Diagram helpers
openDiagramFor(_c) {
import('./praxisui-page-builder-connection-graph.component-C6x--6--.mjs').then(m => {
this.dialog.open(m.ConnectionGraphComponent, { width: '95vw', height: '80vh', maxWidth: '100vw', panelClass: 'praxis-conn-graph-dialog' });
});
}
openDiagramFullscreen() {
import('./praxisui-page-builder-connection-graph.component-C6x--6--.mjs').then(m => {
this.dialog.open(m.ConnectionGraphComponent, { width: '95vw', height: '95vh', maxWidth: '100vw', panelClass: 'praxis-conn-graph-dialog', autoFocus: false, restoreFocus: false });
});
}
parsePage(input) {
if (!input)
return undefined;
if (typeof input === 'string') {
try {
return JSON.parse(input);
}
catch {
return undefined;
}
}
return input;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConnectionBuilderComponent, deps: [{ token: i1.MatDialog }, { token: i2$1.ComponentMetadataRegistry }, { token: i3$3.MatSnackBar }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", 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\u00F5es\">\n <div class=\"pdx-conn-head\">\n <span class=\"pdx-conn-title\">Conex\u00F5es</span>\n <span class=\"pdx-conn-count\" aria-label=\"Conex\u00F5es 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> Issues</button>\n <mat-form-field appearance=\"outline\" style=\"width: 160px;\">\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\" style=\"width: 170px;\">\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\u00E1vel/t\u00E9cnico\"><mat-icon [praxisIcon]=\"'translate'\"></mat-icon></button>\n <button mat-stroked-button (click)=\"createNew()\" aria-label=\"Nova conex\u00E3o\"><mat-icon [praxisIcon]=\"'add'\"></mat-icon> Nova Conex\u00E3o</button>\n <button mat-flat-button color=\"accent\" class=\"pdx-diagram-cta\" (click)=\"openDiagramFullscreen()\" aria-label=\"Abrir diagrama em tela cheia\" matTooltip=\"Visualizar conex\u00F5es em grafo\"><mat-icon [praxisIcon]=\"'schema'\"></mat-icon><span>Diagrama</span></button>\n </div>\n\n <div class=\"pdx-conn-grid\">\n <!-- Left: read-only list -->\n <div class=\"pdx-conn-list\" role=\"list\" aria-label=\"Lista de conex\u00F5es\" 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]=\"showFriendly ? (c.from.widget + '.' + c.from.output + ' \u2192 ' + (c.to.widget + '.' + c.to.input)) : ''\">\n <mat-icon class=\"comp-icon from\" [matTooltip]=\"widgetFriendlyNameForKey(c.from.widget)\">{{ componentIconForKey(c.from.widget) }}</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\">\u2192</span>\n <mat-icon class=\"comp-icon to\" [matTooltip]=\"widgetFriendlyNameForKey(c.to.widget)\">{{ componentIconForKey(c.to.widget) }}</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)\">\u2022</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\u00E7\u00E3o 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