@memberjunction/ng-dashboards 5.37.0 → 5.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts +14 -0
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts.map +1 -1
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js +450 -292
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.d.ts +73 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.js +512 -127
- package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
- package/dist/ComponentStudio/component-studio-resource.component.d.ts +22 -0
- package/dist/ComponentStudio/component-studio-resource.component.d.ts.map +1 -0
- package/dist/ComponentStudio/component-studio-resource.component.js +55 -0
- package/dist/ComponentStudio/component-studio-resource.component.js.map +1 -0
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +104 -45
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +234 -331
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
- package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.d.ts +54 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.d.ts.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.js +339 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.js.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.d.ts +65 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.d.ts.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.js +492 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.js.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.d.ts +88 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.d.ts.map +1 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.js +457 -0
- package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.js.map +1 -0
- package/dist/ComponentStudio/components/form-override-dialog.component.d.ts +106 -0
- package/dist/ComponentStudio/components/form-override-dialog.component.d.ts.map +1 -0
- package/dist/ComponentStudio/components/form-override-dialog.component.js +478 -0
- package/dist/ComponentStudio/components/form-override-dialog.component.js.map +1 -0
- package/dist/ComponentStudio/components/workspace/component-preview.component.d.ts +54 -0
- package/dist/ComponentStudio/components/workspace/component-preview.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/workspace/component-preview.component.js +361 -50
- package/dist/ComponentStudio/components/workspace/component-preview.component.js.map +1 -1
- package/dist/ComponentStudio/components/workspace/editor-tabs.component.d.ts +10 -0
- package/dist/ComponentStudio/components/workspace/editor-tabs.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/workspace/editor-tabs.component.js +114 -45
- package/dist/ComponentStudio/components/workspace/editor-tabs.component.js.map +1 -1
- package/dist/ComponentStudio/services/canvas-to-code.d.ts +32 -0
- package/dist/ComponentStudio/services/canvas-to-code.d.ts.map +1 -0
- package/dist/ComponentStudio/services/canvas-to-code.js +347 -0
- package/dist/ComponentStudio/services/canvas-to-code.js.map +1 -0
- package/dist/ComponentStudio/services/code-to-canvas.d.ts +32 -0
- package/dist/ComponentStudio/services/code-to-canvas.d.ts.map +1 -0
- package/dist/ComponentStudio/services/code-to-canvas.js +92 -0
- package/dist/ComponentStudio/services/code-to-canvas.js.map +1 -0
- package/dist/ComponentStudio/services/component-studio-state.service.d.ts +29 -0
- package/dist/ComponentStudio/services/component-studio-state.service.d.ts.map +1 -1
- package/dist/ComponentStudio/services/component-studio-state.service.js +76 -0
- package/dist/ComponentStudio/services/component-studio-state.service.js.map +1 -1
- package/dist/ComponentStudio/services/entity-form-override.service.d.ts +86 -0
- package/dist/ComponentStudio/services/entity-form-override.service.d.ts.map +1 -0
- package/dist/ComponentStudio/services/entity-form-override.service.js +246 -0
- package/dist/ComponentStudio/services/entity-form-override.service.js.map +1 -0
- package/dist/ComponentStudio/services/field-binding-scanner.d.ts +29 -0
- package/dist/ComponentStudio/services/field-binding-scanner.d.ts.map +1 -0
- package/dist/ComponentStudio/services/field-binding-scanner.js +110 -0
- package/dist/ComponentStudio/services/field-binding-scanner.js.map +1 -0
- package/dist/ComponentStudio/services/form-canvas-model.d.ts +56 -0
- package/dist/ComponentStudio/services/form-canvas-model.d.ts.map +1 -0
- package/dist/ComponentStudio/services/form-canvas-model.js +35 -0
- package/dist/ComponentStudio/services/form-canvas-model.js.map +1 -0
- package/dist/ComponentStudio/services/form-host-props-fixture.d.ts +10 -0
- package/dist/ComponentStudio/services/form-host-props-fixture.d.ts.map +1 -0
- package/dist/ComponentStudio/services/form-host-props-fixture.js +10 -0
- package/dist/ComponentStudio/services/form-host-props-fixture.js.map +1 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.js +2 -2
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/FormBuilder/form-builder-resource.component.d.ts +964 -0
- package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -0
- package/dist/FormBuilder/form-builder-resource.component.js +4487 -0
- package/dist/FormBuilder/form-builder-resource.component.js.map +1 -0
- package/dist/FormBuilder/form-builder-version-rail.helpers.d.ts +55 -0
- package/dist/FormBuilder/form-builder-version-rail.helpers.d.ts.map +1 -0
- package/dist/FormBuilder/form-builder-version-rail.helpers.js +73 -0
- package/dist/FormBuilder/form-builder-version-rail.helpers.js.map +1 -0
- package/dist/Home/home-application.d.ts +21 -1
- package/dist/Home/home-application.d.ts.map +1 -1
- package/dist/Home/home-application.js +60 -8
- package/dist/Home/home-application.js.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.d.ts +14 -14
- package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.js +11 -10
- package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
- package/dist/component-studio-dashboards.module.d.ts +34 -22
- package/dist/component-studio-dashboards.module.d.ts.map +1 -1
- package/dist/component-studio-dashboards.module.js +65 -9
- package/dist/component-studio-dashboards.module.js.map +1 -1
- package/package.json +54 -53
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, Output, inject, } from '@angular/core';
|
|
2
|
+
import { Subject, takeUntil } from 'rxjs';
|
|
3
|
+
import { LogError } from '@memberjunction/core';
|
|
4
|
+
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
5
|
+
import { ComponentStudioStateService } from '../../services/component-studio-state.service';
|
|
6
|
+
import { generateCodeFromCanvas } from '../../services/canvas-to-code';
|
|
7
|
+
import { parseCanvasFromCode } from '../../services/code-to-canvas';
|
|
8
|
+
import { buildEmptyCanvas, generateCanvasId, } from '../../services/form-canvas-model';
|
|
9
|
+
import * as i0 from "@angular/core";
|
|
10
|
+
import * as i1 from "./form-builder-canvas.component";
|
|
11
|
+
const _forTrack0 = ($index, $item) => $item.Name;
|
|
12
|
+
function FormBuilderTabComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
13
|
+
i0.ɵɵtext(0);
|
|
14
|
+
} if (rf & 2) {
|
|
15
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
16
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r0.state.FormTargetEntityName, " ");
|
|
17
|
+
} }
|
|
18
|
+
function FormBuilderTabComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
19
|
+
i0.ɵɵelementStart(0, "em");
|
|
20
|
+
i0.ɵɵtext(1, "Pick entity\u2026");
|
|
21
|
+
i0.ɵɵelementEnd();
|
|
22
|
+
} }
|
|
23
|
+
function FormBuilderTabComponent_Conditional_10_For_4_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
24
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
25
|
+
i0.ɵɵelementStart(0, "li", 27);
|
|
26
|
+
i0.ɵɵlistener("click", function FormBuilderTabComponent_Conditional_10_For_4_Conditional_0_Template_li_click_0_listener() { i0.ɵɵrestoreView(_r3); const e_r4 = i0.ɵɵnextContext().$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnEntityPicked(e_r4.Name)); });
|
|
27
|
+
i0.ɵɵelementStart(1, "span", 28);
|
|
28
|
+
i0.ɵɵtext(2);
|
|
29
|
+
i0.ɵɵelementEnd();
|
|
30
|
+
i0.ɵɵelementStart(3, "span", 29);
|
|
31
|
+
i0.ɵɵtext(4);
|
|
32
|
+
i0.ɵɵelementEnd()();
|
|
33
|
+
} if (rf & 2) {
|
|
34
|
+
const e_r4 = i0.ɵɵnextContext().$implicit;
|
|
35
|
+
i0.ɵɵadvance(2);
|
|
36
|
+
i0.ɵɵtextInterpolate(e_r4.DisplayName);
|
|
37
|
+
i0.ɵɵadvance(2);
|
|
38
|
+
i0.ɵɵtextInterpolate(e_r4.Name);
|
|
39
|
+
} }
|
|
40
|
+
function FormBuilderTabComponent_Conditional_10_For_4_Template(rf, ctx) { if (rf & 1) {
|
|
41
|
+
i0.ɵɵconditionalCreate(0, FormBuilderTabComponent_Conditional_10_For_4_Conditional_0_Template, 5, 2, "li", 26);
|
|
42
|
+
} if (rf & 2) {
|
|
43
|
+
const ɵ$index_30_r5 = ctx.$index;
|
|
44
|
+
i0.ɵɵconditional(ɵ$index_30_r5 < 50 ? 0 : -1);
|
|
45
|
+
} }
|
|
46
|
+
function FormBuilderTabComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
|
|
47
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
48
|
+
i0.ɵɵelementStart(0, "div", 23);
|
|
49
|
+
i0.ɵɵlistener("click", function FormBuilderTabComponent_Conditional_10_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r2); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
50
|
+
i0.ɵɵelementStart(1, "input", 24);
|
|
51
|
+
i0.ɵɵlistener("input", function FormBuilderTabComponent_Conditional_10_Template_input_input_1_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnEntityPickerSearch($event)); });
|
|
52
|
+
i0.ɵɵelementEnd();
|
|
53
|
+
i0.ɵɵelementStart(2, "ul", 25);
|
|
54
|
+
i0.ɵɵrepeaterCreate(3, FormBuilderTabComponent_Conditional_10_For_4_Template, 1, 1, null, null, _forTrack0);
|
|
55
|
+
i0.ɵɵelementEnd()();
|
|
56
|
+
} if (rf & 2) {
|
|
57
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
58
|
+
i0.ɵɵadvance();
|
|
59
|
+
i0.ɵɵproperty("value", ctx_r0.EntityPickerSearch);
|
|
60
|
+
i0.ɵɵadvance(2);
|
|
61
|
+
i0.ɵɵrepeater(ctx_r0.filteredEntityChoices);
|
|
62
|
+
} }
|
|
63
|
+
function FormBuilderTabComponent_Conditional_30_Template(rf, ctx) { if (rf & 1) {
|
|
64
|
+
i0.ɵɵelementStart(0, "div", 22);
|
|
65
|
+
i0.ɵɵelement(1, "i", 30);
|
|
66
|
+
i0.ɵɵelementStart(2, "h3");
|
|
67
|
+
i0.ɵɵtext(3, "Pick an entity to start the form");
|
|
68
|
+
i0.ɵɵelementEnd();
|
|
69
|
+
i0.ɵɵelementStart(4, "p");
|
|
70
|
+
i0.ɵɵtext(5, "Choose the target entity from the toolbar. Form Studio loads the curated schema and lets you drag fields onto the canvas.");
|
|
71
|
+
i0.ɵɵelementEnd()();
|
|
72
|
+
} }
|
|
73
|
+
function FormBuilderTabComponent_Conditional_31_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
74
|
+
i0.ɵɵelementStart(0, "div", 31);
|
|
75
|
+
i0.ɵɵelement(1, "i", 33);
|
|
76
|
+
i0.ɵɵtext(2, " Some parts of this form's code can't be represented on the canvas. Edit them in the Code tab to avoid losing them. ");
|
|
77
|
+
i0.ɵɵelementEnd();
|
|
78
|
+
} }
|
|
79
|
+
function FormBuilderTabComponent_Conditional_31_Template(rf, ctx) { if (rf & 1) {
|
|
80
|
+
const _r6 = i0.ɵɵgetCurrentView();
|
|
81
|
+
i0.ɵɵconditionalCreate(0, FormBuilderTabComponent_Conditional_31_Conditional_0_Template, 3, 0, "div", 31);
|
|
82
|
+
i0.ɵɵelementStart(1, "mj-form-builder-canvas", 32);
|
|
83
|
+
i0.ɵɵlistener("CanvasChanged", function FormBuilderTabComponent_Conditional_31_Template_mj_form_builder_canvas_CanvasChanged_1_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnCanvasChanged($event)); })("ElementSelected", function FormBuilderTabComponent_Conditional_31_Template_mj_form_builder_canvas_ElementSelected_1_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnElementSelected($event)); })("SectionSelected", function FormBuilderTabComponent_Conditional_31_Template_mj_form_builder_canvas_SectionSelected_1_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnSectionSelected($event)); })("Deselected", function FormBuilderTabComponent_Conditional_31_Template_mj_form_builder_canvas_Deselected_1_listener() { i0.ɵɵrestoreView(_r6); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnDeselected()); });
|
|
84
|
+
i0.ɵɵelementEnd();
|
|
85
|
+
} if (rf & 2) {
|
|
86
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
87
|
+
i0.ɵɵconditional(ctx_r0.state.FormCodeOnlySectionsDetected ? 0 : -1);
|
|
88
|
+
i0.ɵɵadvance();
|
|
89
|
+
i0.ɵɵproperty("Canvas", ctx_r0.state.FormCanvas)("Schema", ctx_r0.state.FormSchema)("SelectedElementId", ctx_r0.state.FormSelectedElementId)("SelectedSectionId", ctx_r0.state.FormSelectedSectionId);
|
|
90
|
+
} }
|
|
91
|
+
/**
|
|
92
|
+
* Form Builder tab — the visual drag-and-drop canvas surface that appears
|
|
93
|
+
* in Component Studio's editor-tabs strip when the active spec is form-role.
|
|
94
|
+
*
|
|
95
|
+
* The canvas is the source-of-truth while this tab is active. On save, the
|
|
96
|
+
* dashboard serialises the canvas to JSX via `generateCodeFromCanvas` and
|
|
97
|
+
* pushes that into `state.EditableCode`. When the user opens an existing
|
|
98
|
+
* form-role Component, `parseCanvasFromCode` reconstructs the canvas
|
|
99
|
+
* (lossily) from the stored code; if the round-trip is too lossy, we leave
|
|
100
|
+
* the canvas empty and steer the user toward the Code tab.
|
|
101
|
+
*
|
|
102
|
+
* The component is presentational where it can be — the heavy state
|
|
103
|
+
* (canvas, schema, selection, preview mode) lives on
|
|
104
|
+
* `ComponentStudioStateService`. This keeps the right-panel and the live
|
|
105
|
+
* preview pane in sync with whatever the user is doing in here.
|
|
106
|
+
*/
|
|
107
|
+
export class FormBuilderTabComponent {
|
|
108
|
+
/** Notifies the parent (dashboard) that the user wants to switch to the Code tab. */
|
|
109
|
+
RequestCodeTab = new EventEmitter();
|
|
110
|
+
/** Fired separately for the dashboard's NavigationService bridge. */
|
|
111
|
+
OpenInChatRequested = new EventEmitter();
|
|
112
|
+
IsEntityPickerOpen = false;
|
|
113
|
+
EntityPickerSearch = '';
|
|
114
|
+
EntityChoices = [];
|
|
115
|
+
state = inject(ComponentStudioStateService);
|
|
116
|
+
cdr = inject(ChangeDetectorRef);
|
|
117
|
+
notifications = inject(MJNotificationService);
|
|
118
|
+
destroy$ = new Subject();
|
|
119
|
+
// ------------------------------------------------------------------
|
|
120
|
+
// Lifecycle
|
|
121
|
+
// ------------------------------------------------------------------
|
|
122
|
+
ngOnInit() {
|
|
123
|
+
this.state.StateChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
124
|
+
this.cdr.markForCheck();
|
|
125
|
+
});
|
|
126
|
+
// Lazily hydrate the canvas if we have a target entity but no canvas
|
|
127
|
+
// (typical when the user lands here from the Code tab).
|
|
128
|
+
this.hydrateCanvasFromState();
|
|
129
|
+
this.refreshEntityChoices();
|
|
130
|
+
}
|
|
131
|
+
ngOnDestroy() {
|
|
132
|
+
this.destroy$.next();
|
|
133
|
+
this.destroy$.complete();
|
|
134
|
+
}
|
|
135
|
+
// ------------------------------------------------------------------
|
|
136
|
+
// Entity picker
|
|
137
|
+
// ------------------------------------------------------------------
|
|
138
|
+
ToggleEntityPicker() {
|
|
139
|
+
this.IsEntityPickerOpen = !this.IsEntityPickerOpen;
|
|
140
|
+
if (this.IsEntityPickerOpen)
|
|
141
|
+
this.refreshEntityChoices();
|
|
142
|
+
this.cdr.markForCheck();
|
|
143
|
+
}
|
|
144
|
+
OnEntityPickerSearch(event) {
|
|
145
|
+
this.EntityPickerSearch = event.target.value;
|
|
146
|
+
this.cdr.markForCheck();
|
|
147
|
+
}
|
|
148
|
+
OnEntityPicked(entityName) {
|
|
149
|
+
this.IsEntityPickerOpen = false;
|
|
150
|
+
const schema = this.state.BuildFormSchema(entityName);
|
|
151
|
+
if (!schema) {
|
|
152
|
+
this.notifications.CreateSimpleNotification(`Couldn't load schema for ${entityName}.`, 'error', 4000);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.state.FormTargetEntityName = entityName;
|
|
156
|
+
// Try to seed canvas from existing code first; otherwise start empty.
|
|
157
|
+
const existing = this.state.EditableCode ?? '';
|
|
158
|
+
if (existing.length > 0) {
|
|
159
|
+
const result = parseCanvasFromCode(existing, schema);
|
|
160
|
+
if (result.canvas) {
|
|
161
|
+
this.state.FormCanvas = result.canvas;
|
|
162
|
+
this.state.FormCodeOnlySectionsDetected = result.hasUnknownConstructs;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this.state.FormCanvas = buildEmptyCanvas(entityName, schema.displayName);
|
|
166
|
+
this.state.FormCodeOnlySectionsDetected = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
this.state.FormCanvas = buildEmptyCanvas(entityName, schema.displayName);
|
|
171
|
+
this.state.FormCodeOnlySectionsDetected = false;
|
|
172
|
+
}
|
|
173
|
+
this.state.FormSelectedElementId = null;
|
|
174
|
+
this.state.FormSelectedSectionId = this.state.FormCanvas?.sections[0]?.id ?? null;
|
|
175
|
+
this.regenerateCode();
|
|
176
|
+
this.cdr.markForCheck();
|
|
177
|
+
}
|
|
178
|
+
get filteredEntityChoices() {
|
|
179
|
+
const q = this.EntityPickerSearch.trim().toLowerCase();
|
|
180
|
+
if (!q)
|
|
181
|
+
return this.EntityChoices;
|
|
182
|
+
return this.EntityChoices.filter(e => e.Name.toLowerCase().includes(q) ||
|
|
183
|
+
e.DisplayName.toLowerCase().includes(q));
|
|
184
|
+
}
|
|
185
|
+
refreshEntityChoices() {
|
|
186
|
+
const provider = this.state.Provider;
|
|
187
|
+
if (!provider) {
|
|
188
|
+
this.EntityChoices = [];
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
this.EntityChoices = (provider.Entities ?? [])
|
|
192
|
+
.filter(e => e.AllowCreateAPI || e.AllowUpdateAPI)
|
|
193
|
+
.map(e => ({ Name: e.Name, DisplayName: e.DisplayName ?? e.Name }))
|
|
194
|
+
.sort((a, b) => a.DisplayName.localeCompare(b.DisplayName));
|
|
195
|
+
}
|
|
196
|
+
// ------------------------------------------------------------------
|
|
197
|
+
// Preview-mode pills (synced with state.FormPreviewMode)
|
|
198
|
+
// ------------------------------------------------------------------
|
|
199
|
+
SetPreviewMode(mode) {
|
|
200
|
+
this.state.FormPreviewMode = mode;
|
|
201
|
+
}
|
|
202
|
+
// ------------------------------------------------------------------
|
|
203
|
+
// Canvas events
|
|
204
|
+
// ------------------------------------------------------------------
|
|
205
|
+
OnCanvasChanged(next) {
|
|
206
|
+
this.state.FormCanvas = next;
|
|
207
|
+
this.state.HasUnsavedChanges = true;
|
|
208
|
+
this.regenerateCode();
|
|
209
|
+
}
|
|
210
|
+
OnElementSelected(payload) {
|
|
211
|
+
this.state.FormSelectedSectionId = null;
|
|
212
|
+
this.state.FormSelectedElementId = payload.elementId;
|
|
213
|
+
}
|
|
214
|
+
OnSectionSelected(sectionId) {
|
|
215
|
+
this.state.FormSelectedElementId = null;
|
|
216
|
+
this.state.FormSelectedSectionId = sectionId;
|
|
217
|
+
}
|
|
218
|
+
OnDeselected() {
|
|
219
|
+
this.state.FormSelectedElementId = null;
|
|
220
|
+
this.state.FormSelectedSectionId = null;
|
|
221
|
+
}
|
|
222
|
+
OnElementChanged(next) {
|
|
223
|
+
const canvas = this.state.FormCanvas;
|
|
224
|
+
if (!canvas)
|
|
225
|
+
return;
|
|
226
|
+
const updated = {
|
|
227
|
+
...canvas,
|
|
228
|
+
sections: canvas.sections.map(s => ({
|
|
229
|
+
...s,
|
|
230
|
+
elements: s.elements.map(e => e.id === next.id ? next : e),
|
|
231
|
+
})),
|
|
232
|
+
};
|
|
233
|
+
this.OnCanvasChanged(updated);
|
|
234
|
+
}
|
|
235
|
+
OnSectionChanged(next) {
|
|
236
|
+
const canvas = this.state.FormCanvas;
|
|
237
|
+
if (!canvas)
|
|
238
|
+
return;
|
|
239
|
+
const updated = {
|
|
240
|
+
...canvas,
|
|
241
|
+
sections: canvas.sections.map(s => s.id === next.id ? next : s),
|
|
242
|
+
};
|
|
243
|
+
this.OnCanvasChanged(updated);
|
|
244
|
+
}
|
|
245
|
+
OnElementDeleted(elementId) {
|
|
246
|
+
const canvas = this.state.FormCanvas;
|
|
247
|
+
if (!canvas)
|
|
248
|
+
return;
|
|
249
|
+
const updated = {
|
|
250
|
+
...canvas,
|
|
251
|
+
sections: canvas.sections.map(s => ({
|
|
252
|
+
...s,
|
|
253
|
+
elements: s.elements.filter(e => e.id !== elementId),
|
|
254
|
+
})),
|
|
255
|
+
};
|
|
256
|
+
this.state.FormSelectedElementId = null;
|
|
257
|
+
this.OnCanvasChanged(updated);
|
|
258
|
+
}
|
|
259
|
+
OnSectionDeleted(sectionId) {
|
|
260
|
+
const canvas = this.state.FormCanvas;
|
|
261
|
+
if (!canvas)
|
|
262
|
+
return;
|
|
263
|
+
const updated = {
|
|
264
|
+
...canvas,
|
|
265
|
+
sections: canvas.sections.filter(s => s.id !== sectionId),
|
|
266
|
+
};
|
|
267
|
+
this.state.FormSelectedSectionId = null;
|
|
268
|
+
this.OnCanvasChanged(updated);
|
|
269
|
+
}
|
|
270
|
+
OnFieldAddedFromPalette(payload) {
|
|
271
|
+
const canvas = this.state.FormCanvas;
|
|
272
|
+
if (!canvas)
|
|
273
|
+
return;
|
|
274
|
+
const target = this.findFocusedSection(canvas);
|
|
275
|
+
if (!target)
|
|
276
|
+
return;
|
|
277
|
+
const updated = {
|
|
278
|
+
...canvas,
|
|
279
|
+
sections: canvas.sections.map(s => s.id === target.id
|
|
280
|
+
? { ...s, elements: [...s.elements, {
|
|
281
|
+
id: generateCanvasId('field'),
|
|
282
|
+
type: 'field',
|
|
283
|
+
fieldName: payload.fieldName,
|
|
284
|
+
span: 1,
|
|
285
|
+
}] }
|
|
286
|
+
: s),
|
|
287
|
+
};
|
|
288
|
+
this.OnCanvasChanged(updated);
|
|
289
|
+
}
|
|
290
|
+
findFocusedSection(canvas) {
|
|
291
|
+
if (this.state.FormSelectedSectionId) {
|
|
292
|
+
const s = canvas.sections.find(s => s.id === this.state.FormSelectedSectionId);
|
|
293
|
+
if (s)
|
|
294
|
+
return s;
|
|
295
|
+
}
|
|
296
|
+
if (this.state.FormSelectedElementId) {
|
|
297
|
+
const s = canvas.sections.find(sec => sec.elements.some(e => e.id === this.state.FormSelectedElementId));
|
|
298
|
+
if (s)
|
|
299
|
+
return s;
|
|
300
|
+
}
|
|
301
|
+
return canvas.sections[0] ?? null;
|
|
302
|
+
}
|
|
303
|
+
// ------------------------------------------------------------------
|
|
304
|
+
// Open in Chat
|
|
305
|
+
// ------------------------------------------------------------------
|
|
306
|
+
/**
|
|
307
|
+
* Bubble up — the dashboard wires up `navigationService.SetAgentContext`
|
|
308
|
+
* via the state event because we don't extend BaseResourceComponent
|
|
309
|
+
* here. The tab also fires a local Output for the editor-tabs parent in
|
|
310
|
+
* case it wants to react.
|
|
311
|
+
*/
|
|
312
|
+
OnOpenInChat() {
|
|
313
|
+
this.OpenInChatRequested.emit();
|
|
314
|
+
this.state.OpenInChatRequested.emit();
|
|
315
|
+
}
|
|
316
|
+
// ------------------------------------------------------------------
|
|
317
|
+
// View Code toggle
|
|
318
|
+
// ------------------------------------------------------------------
|
|
319
|
+
OnViewCode() {
|
|
320
|
+
// Make sure the latest canvas is reflected in code before switching.
|
|
321
|
+
this.regenerateCode();
|
|
322
|
+
this.RequestCodeTab.emit();
|
|
323
|
+
}
|
|
324
|
+
// ------------------------------------------------------------------
|
|
325
|
+
// Keyboard
|
|
326
|
+
// ------------------------------------------------------------------
|
|
327
|
+
OnKeyDown(event) {
|
|
328
|
+
const target = event.target;
|
|
329
|
+
const isInput = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
|
|
330
|
+
if (event.key === 'Delete' && !isInput && this.state.FormSelectedElementId) {
|
|
331
|
+
this.OnElementDeleted(this.state.FormSelectedElementId);
|
|
332
|
+
}
|
|
333
|
+
if (event.key === 'Escape' && !isInput) {
|
|
334
|
+
this.OnDeselected();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// ------------------------------------------------------------------
|
|
338
|
+
// Helpers
|
|
339
|
+
// ------------------------------------------------------------------
|
|
340
|
+
/**
|
|
341
|
+
* Build a fresh canvas if the spec already has a target entity but no
|
|
342
|
+
* canvas yet (e.g. tab activated for the first time). Best-effort — if
|
|
343
|
+
* the spec's code is too lossy to parse, we leave the canvas empty and
|
|
344
|
+
* show a banner.
|
|
345
|
+
*/
|
|
346
|
+
hydrateCanvasFromState() {
|
|
347
|
+
const entity = this.state.FormTargetEntityName;
|
|
348
|
+
if (!entity || this.state.FormCanvas)
|
|
349
|
+
return;
|
|
350
|
+
const schema = this.state.BuildFormSchema(entity);
|
|
351
|
+
if (!schema)
|
|
352
|
+
return;
|
|
353
|
+
const existing = this.state.EditableCode ?? '';
|
|
354
|
+
if (existing.length > 0) {
|
|
355
|
+
const result = parseCanvasFromCode(existing, schema);
|
|
356
|
+
if (result.canvas) {
|
|
357
|
+
this.state.FormCanvas = result.canvas;
|
|
358
|
+
this.state.FormCodeOnlySectionsDetected = result.hasUnknownConstructs;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
this.state.FormCanvas = buildEmptyCanvas(entity, schema.displayName);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Mirror the canvas into ComponentStudioStateService.EditableCode so the
|
|
366
|
+
* Code tab, the preview, and the eventual Save flow all see the same
|
|
367
|
+
* source. Quietly no-op if we don't have enough to render.
|
|
368
|
+
*/
|
|
369
|
+
regenerateCode() {
|
|
370
|
+
try {
|
|
371
|
+
const canvas = this.state.FormCanvas;
|
|
372
|
+
const schema = this.state.FormSchema;
|
|
373
|
+
if (!canvas || !schema)
|
|
374
|
+
return;
|
|
375
|
+
const name = canvas.title?.trim() || schema.displayName;
|
|
376
|
+
const code = generateCodeFromCanvas(canvas, schema, name);
|
|
377
|
+
this.state.EditableCode = code;
|
|
378
|
+
}
|
|
379
|
+
catch (err) {
|
|
380
|
+
LogError(`FormBuilderTab.regenerateCode: ${err instanceof Error ? err.message : String(err)}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
static ɵfac = function FormBuilderTabComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FormBuilderTabComponent)(); };
|
|
384
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FormBuilderTabComponent, selectors: [["mj-form-builder-tab"]], hostBindings: function FormBuilderTabComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
385
|
+
i0.ɵɵlistener("keydown", function FormBuilderTabComponent_keydown_HostBindingHandler($event) { return ctx.OnKeyDown($event); }, i0.ɵɵresolveDocument);
|
|
386
|
+
} }, outputs: { RequestCodeTab: "RequestCodeTab", OpenInChatRequested: "OpenInChatRequested" }, standalone: false, decls: 32, vars: 12, consts: [[1, "form-builder-tab"], [1, "fbt-toolbar"], [1, "fbt-toolbar-left"], [1, "fbt-entity-picker"], [1, "fbt-entity-btn", 3, "click"], [1, "fa-solid", "fa-table"], [1, "fa-solid", "fa-chevron-down", "chev"], [1, "fbt-entity-popover"], ["role", "radiogroup", "aria-label", "Preview mode", 1, "fbt-mode-pills"], ["title", "View mode \u2014 read-only", 1, "fbt-mode-pill", 3, "click"], [1, "fa-solid", "fa-eye"], ["title", "Edit mode \u2014 fields editable", 1, "fbt-mode-pill", 3, "click"], [1, "fa-solid", "fa-pen"], ["title", "Create mode \u2014 empty record", 1, "fbt-mode-pill", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "fbt-toolbar-spacer"], [1, "fbt-toolbar-right"], ["title", "View generated code", 1, "fbt-tool-btn", 3, "click"], [1, "fa-solid", "fa-code"], ["title", "Hand off the current canvas to the chat agent", 1, "fbt-tool-btn", 3, "click", "disabled"], [1, "fa-solid", "fa-comment-dots"], [1, "fbt-canvas-host"], [1, "fbt-empty"], [1, "fbt-entity-popover", 3, "click"], ["type", "text", "placeholder", "Search entities\u2026", 1, "fbt-entity-search", 3, "input", "value"], [1, "fbt-entity-list"], [1, "fbt-entity-item"], [1, "fbt-entity-item", 3, "click"], [1, "fbt-entity-display"], [1, "fbt-entity-name"], [1, "fa-solid", "fa-table-list"], ["role", "alert", 1, "fbt-lossy-banner"], [3, "CanvasChanged", "ElementSelected", "SectionSelected", "Deselected", "Canvas", "Schema", "SelectedElementId", "SelectedSectionId"], [1, "fa-solid", "fa-triangle-exclamation"]], template: function FormBuilderTabComponent_Template(rf, ctx) { if (rf & 1) {
|
|
387
|
+
i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2)(3, "div", 3)(4, "button", 4);
|
|
388
|
+
i0.ɵɵlistener("click", function FormBuilderTabComponent_Template_button_click_4_listener() { return ctx.ToggleEntityPicker(); });
|
|
389
|
+
i0.ɵɵelement(5, "i", 5);
|
|
390
|
+
i0.ɵɵelementStart(6, "span");
|
|
391
|
+
i0.ɵɵconditionalCreate(7, FormBuilderTabComponent_Conditional_7_Template, 1, 1)(8, FormBuilderTabComponent_Conditional_8_Template, 2, 0, "em");
|
|
392
|
+
i0.ɵɵelementEnd();
|
|
393
|
+
i0.ɵɵelement(9, "i", 6);
|
|
394
|
+
i0.ɵɵelementEnd();
|
|
395
|
+
i0.ɵɵconditionalCreate(10, FormBuilderTabComponent_Conditional_10_Template, 5, 1, "div", 7);
|
|
396
|
+
i0.ɵɵelementEnd();
|
|
397
|
+
i0.ɵɵelementStart(11, "div", 8)(12, "button", 9);
|
|
398
|
+
i0.ɵɵlistener("click", function FormBuilderTabComponent_Template_button_click_12_listener() { return ctx.SetPreviewMode("view"); });
|
|
399
|
+
i0.ɵɵelement(13, "i", 10);
|
|
400
|
+
i0.ɵɵtext(14, " View ");
|
|
401
|
+
i0.ɵɵelementEnd();
|
|
402
|
+
i0.ɵɵelementStart(15, "button", 11);
|
|
403
|
+
i0.ɵɵlistener("click", function FormBuilderTabComponent_Template_button_click_15_listener() { return ctx.SetPreviewMode("edit"); });
|
|
404
|
+
i0.ɵɵelement(16, "i", 12);
|
|
405
|
+
i0.ɵɵtext(17, " Edit ");
|
|
406
|
+
i0.ɵɵelementEnd();
|
|
407
|
+
i0.ɵɵelementStart(18, "button", 13);
|
|
408
|
+
i0.ɵɵlistener("click", function FormBuilderTabComponent_Template_button_click_18_listener() { return ctx.SetPreviewMode("create"); });
|
|
409
|
+
i0.ɵɵelement(19, "i", 14);
|
|
410
|
+
i0.ɵɵtext(20, " Create ");
|
|
411
|
+
i0.ɵɵelementEnd()()();
|
|
412
|
+
i0.ɵɵelement(21, "div", 15);
|
|
413
|
+
i0.ɵɵelementStart(22, "div", 16)(23, "button", 17);
|
|
414
|
+
i0.ɵɵlistener("click", function FormBuilderTabComponent_Template_button_click_23_listener() { return ctx.OnViewCode(); });
|
|
415
|
+
i0.ɵɵelement(24, "i", 18);
|
|
416
|
+
i0.ɵɵtext(25, " View Code ");
|
|
417
|
+
i0.ɵɵelementEnd();
|
|
418
|
+
i0.ɵɵelementStart(26, "button", 19);
|
|
419
|
+
i0.ɵɵlistener("click", function FormBuilderTabComponent_Template_button_click_26_listener() { return ctx.OnOpenInChat(); });
|
|
420
|
+
i0.ɵɵelement(27, "i", 20);
|
|
421
|
+
i0.ɵɵtext(28, " Open in Chat ");
|
|
422
|
+
i0.ɵɵelementEnd()()();
|
|
423
|
+
i0.ɵɵelementStart(29, "div", 21);
|
|
424
|
+
i0.ɵɵconditionalCreate(30, FormBuilderTabComponent_Conditional_30_Template, 6, 0, "div", 22)(31, FormBuilderTabComponent_Conditional_31_Template, 2, 5);
|
|
425
|
+
i0.ɵɵelementEnd()();
|
|
426
|
+
} if (rf & 2) {
|
|
427
|
+
i0.ɵɵadvance(3);
|
|
428
|
+
i0.ɵɵclassProp("open", ctx.IsEntityPickerOpen);
|
|
429
|
+
i0.ɵɵadvance(4);
|
|
430
|
+
i0.ɵɵconditional(ctx.state.FormTargetEntityName ? 7 : 8);
|
|
431
|
+
i0.ɵɵadvance(3);
|
|
432
|
+
i0.ɵɵconditional(ctx.IsEntityPickerOpen ? 10 : -1);
|
|
433
|
+
i0.ɵɵadvance(2);
|
|
434
|
+
i0.ɵɵclassProp("active", ctx.state.FormPreviewMode === "view");
|
|
435
|
+
i0.ɵɵadvance(3);
|
|
436
|
+
i0.ɵɵclassProp("active", ctx.state.FormPreviewMode === "edit");
|
|
437
|
+
i0.ɵɵadvance(3);
|
|
438
|
+
i0.ɵɵclassProp("active", ctx.state.FormPreviewMode === "create");
|
|
439
|
+
i0.ɵɵadvance(8);
|
|
440
|
+
i0.ɵɵproperty("disabled", !ctx.state.FormCanvas);
|
|
441
|
+
i0.ɵɵadvance(4);
|
|
442
|
+
i0.ɵɵconditional(!ctx.state.FormTargetEntityName ? 30 : 31);
|
|
443
|
+
} }, dependencies: [i1.FormBuilderCanvasComponent], styles: ["[_nghost-%COMP%] {\n display: flex;\n flex: 1;\n overflow: hidden;\n}\n\n.form-builder-tab[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1;\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n\n\n\n\n.fbt-toolbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.fbt-toolbar-left[_ngcontent-%COMP%], \n.fbt-toolbar-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.fbt-toolbar-spacer[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n\n\n\n\n.fbt-entity-picker[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.fbt-entity-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n color: var(--mj-text-primary);\n font-size: 13px;\n cursor: pointer;\n font-family: inherit;\n transition: background 0.15s ease;\n}\n\n.fbt-entity-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.fbt-entity-btn[_ngcontent-%COMP%] em[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n.fbt-entity-btn[_ngcontent-%COMP%] .chev[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-muted);\n}\n\n.fbt-entity-popover[_ngcontent-%COMP%] {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n width: 320px;\n max-height: 360px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n z-index: 30;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.fbt-entity-search[_ngcontent-%COMP%] {\n padding: 8px 10px;\n border: none;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n font-family: inherit;\n outline: none;\n}\n\n.fbt-entity-search[_ngcontent-%COMP%]:focus {\n background: var(--mj-bg-surface-hover);\n}\n\n.fbt-entity-list[_ngcontent-%COMP%] {\n list-style: none;\n margin: 0;\n padding: 4px;\n overflow-y: auto;\n flex: 1;\n}\n\n.fbt-entity-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px 10px;\n border-radius: 4px;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.fbt-entity-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.fbt-entity-display[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-primary);\n font-weight: 500;\n}\n\n.fbt-entity-name[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n\n\n\n\n.fbt-mode-pills[_ngcontent-%COMP%] {\n display: inline-flex;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n overflow: hidden;\n}\n\n.fbt-mode-pill[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 5px 12px;\n background: transparent;\n border: none;\n color: var(--mj-text-secondary);\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n transition: all 0.15s ease;\n border-right: 1px solid var(--mj-border-default);\n}\n\n.fbt-mode-pill[_ngcontent-%COMP%]:last-child {\n border-right: none;\n}\n\n.fbt-mode-pill[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.fbt-mode-pill.active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n\n\n\n\n.fbt-tool-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n color: var(--mj-text-primary);\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.fbt-tool-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n.fbt-tool-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n\n\n\n\n.fbt-canvas-host[_ngcontent-%COMP%] {\n flex: 1;\n overflow: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.fbt-canvas-host[_ngcontent-%COMP%] > mj-form-builder-canvas[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.fbt-empty[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n text-align: center;\n color: var(--mj-text-muted);\n padding: 48px;\n}\n\n.fbt-empty[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 36px;\n color: var(--mj-text-disabled);\n}\n\n.fbt-empty[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 16px;\n color: var(--mj-text-primary);\n}\n\n.fbt-empty[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n max-width: 380px;\n font-size: 13px;\n line-height: 1.5;\n}\n\n.fbt-lossy-banner[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border: 1px solid var(--mj-status-warning-border);\n border-radius: 6px;\n font-size: 12px;\n}"], changeDetection: 0 });
|
|
444
|
+
}
|
|
445
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FormBuilderTabComponent, [{
|
|
446
|
+
type: Component,
|
|
447
|
+
args: [{ standalone: false, selector: 'mj-form-builder-tab', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"form-builder-tab\">\n\n <!-- ============================================================\n TOP TOOLBAR \u2014 entity picker, mode pills, code & chat handoff\n ============================================================ -->\n <div class=\"fbt-toolbar\">\n <div class=\"fbt-toolbar-left\">\n <!-- Entity picker -->\n <div class=\"fbt-entity-picker\" [class.open]=\"IsEntityPickerOpen\">\n <button class=\"fbt-entity-btn\" (click)=\"ToggleEntityPicker()\">\n <i class=\"fa-solid fa-table\"></i>\n <span>\n @if (state.FormTargetEntityName) {\n {{ state.FormTargetEntityName }}\n } @else {\n <em>Pick entity\u2026</em>\n }\n </span>\n <i class=\"fa-solid fa-chevron-down chev\"></i>\n </button>\n @if (IsEntityPickerOpen) {\n <div class=\"fbt-entity-popover\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\"\n class=\"fbt-entity-search\"\n placeholder=\"Search entities\u2026\"\n [value]=\"EntityPickerSearch\"\n (input)=\"OnEntityPickerSearch($event)\" />\n <ul class=\"fbt-entity-list\">\n @for (e of filteredEntityChoices; track e.Name; let idx = $index) {\n @if (idx < 50) {\n <li class=\"fbt-entity-item\" (click)=\"OnEntityPicked(e.Name)\">\n <span class=\"fbt-entity-display\">{{ e.DisplayName }}</span>\n <span class=\"fbt-entity-name\">{{ e.Name }}</span>\n </li>\n }\n }\n </ul>\n </div>\n }\n </div>\n\n <!-- Mode pills -->\n <div class=\"fbt-mode-pills\" role=\"radiogroup\" aria-label=\"Preview mode\">\n <button class=\"fbt-mode-pill\"\n [class.active]=\"state.FormPreviewMode === 'view'\"\n (click)=\"SetPreviewMode('view')\"\n title=\"View mode \u2014 read-only\">\n <i class=\"fa-solid fa-eye\"></i> View\n </button>\n <button class=\"fbt-mode-pill\"\n [class.active]=\"state.FormPreviewMode === 'edit'\"\n (click)=\"SetPreviewMode('edit')\"\n title=\"Edit mode \u2014 fields editable\">\n <i class=\"fa-solid fa-pen\"></i> Edit\n </button>\n <button class=\"fbt-mode-pill\"\n [class.active]=\"state.FormPreviewMode === 'create'\"\n (click)=\"SetPreviewMode('create')\"\n title=\"Create mode \u2014 empty record\">\n <i class=\"fa-solid fa-plus\"></i> Create\n </button>\n </div>\n </div>\n\n <div class=\"fbt-toolbar-spacer\"></div>\n\n <div class=\"fbt-toolbar-right\">\n <button class=\"fbt-tool-btn\"\n (click)=\"OnViewCode()\"\n title=\"View generated code\">\n <i class=\"fa-solid fa-code\"></i> View Code\n </button>\n <button class=\"fbt-tool-btn\"\n [disabled]=\"!state.FormCanvas\"\n (click)=\"OnOpenInChat()\"\n title=\"Hand off the current canvas to the chat agent\">\n <i class=\"fa-solid fa-comment-dots\"></i> Open in Chat\n </button>\n </div>\n </div>\n\n <!-- ============================================================\n CANVAS BODY\n ============================================================ -->\n <div class=\"fbt-canvas-host\">\n @if (!state.FormTargetEntityName) {\n <div class=\"fbt-empty\">\n <i class=\"fa-solid fa-table-list\"></i>\n <h3>Pick an entity to start the form</h3>\n <p>Choose the target entity from the toolbar. Form Studio loads the curated schema and lets you drag fields onto the canvas.</p>\n </div>\n } @else {\n @if (state.FormCodeOnlySectionsDetected) {\n <div class=\"fbt-lossy-banner\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n Some parts of this form's code can't be represented on the canvas. Edit them in the Code tab to avoid losing them.\n </div>\n }\n <mj-form-builder-canvas\n [Canvas]=\"state.FormCanvas\"\n [Schema]=\"state.FormSchema\"\n [SelectedElementId]=\"state.FormSelectedElementId\"\n [SelectedSectionId]=\"state.FormSelectedSectionId\"\n (CanvasChanged)=\"OnCanvasChanged($event)\"\n (ElementSelected)=\"OnElementSelected($event)\"\n (SectionSelected)=\"OnSectionSelected($event)\"\n (Deselected)=\"OnDeselected()\">\n </mj-form-builder-canvas>\n }\n </div>\n</div>\n", styles: [":host {\n display: flex;\n flex: 1;\n overflow: hidden;\n}\n\n.form-builder-tab {\n display: flex;\n flex-direction: column;\n flex: 1;\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n/* ============================================================\n TOOLBAR\n ============================================================ */\n.fbt-toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.fbt-toolbar-left,\n.fbt-toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.fbt-toolbar-spacer {\n flex: 1;\n}\n\n/* ============================================================\n ENTITY PICKER\n ============================================================ */\n.fbt-entity-picker {\n position: relative;\n}\n\n.fbt-entity-btn {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n color: var(--mj-text-primary);\n font-size: 13px;\n cursor: pointer;\n font-family: inherit;\n transition: background 0.15s ease;\n}\n\n.fbt-entity-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.fbt-entity-btn em {\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n.fbt-entity-btn .chev {\n font-size: 10px;\n color: var(--mj-text-muted);\n}\n\n.fbt-entity-popover {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n width: 320px;\n max-height: 360px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n z-index: 30;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.fbt-entity-search {\n padding: 8px 10px;\n border: none;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n font-family: inherit;\n outline: none;\n}\n\n.fbt-entity-search:focus {\n background: var(--mj-bg-surface-hover);\n}\n\n.fbt-entity-list {\n list-style: none;\n margin: 0;\n padding: 4px;\n overflow-y: auto;\n flex: 1;\n}\n\n.fbt-entity-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px 10px;\n border-radius: 4px;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.fbt-entity-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.fbt-entity-display {\n font-size: 13px;\n color: var(--mj-text-primary);\n font-weight: 500;\n}\n\n.fbt-entity-name {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n/* ============================================================\n MODE PILLS\n ============================================================ */\n.fbt-mode-pills {\n display: inline-flex;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n overflow: hidden;\n}\n\n.fbt-mode-pill {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 5px 12px;\n background: transparent;\n border: none;\n color: var(--mj-text-secondary);\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n transition: all 0.15s ease;\n border-right: 1px solid var(--mj-border-default);\n}\n\n.fbt-mode-pill:last-child {\n border-right: none;\n}\n\n.fbt-mode-pill:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.fbt-mode-pill.active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n/* ============================================================\n RIGHT-SIDE TOOLBAR BUTTONS\n ============================================================ */\n.fbt-tool-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n color: var(--mj-text-primary);\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.fbt-tool-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n.fbt-tool-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* ============================================================\n CANVAS HOST + EMPTY STATE\n ============================================================ */\n.fbt-canvas-host {\n flex: 1;\n overflow: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.fbt-canvas-host > mj-form-builder-canvas {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.fbt-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n text-align: center;\n color: var(--mj-text-muted);\n padding: 48px;\n}\n\n.fbt-empty i {\n font-size: 36px;\n color: var(--mj-text-disabled);\n}\n\n.fbt-empty h3 {\n margin: 0;\n font-size: 16px;\n color: var(--mj-text-primary);\n}\n\n.fbt-empty p {\n margin: 0;\n max-width: 380px;\n font-size: 13px;\n line-height: 1.5;\n}\n\n.fbt-lossy-banner {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border: 1px solid var(--mj-status-warning-border);\n border-radius: 6px;\n font-size: 12px;\n}\n"] }]
|
|
448
|
+
}], null, { RequestCodeTab: [{
|
|
449
|
+
type: Output
|
|
450
|
+
}], OpenInChatRequested: [{
|
|
451
|
+
type: Output
|
|
452
|
+
}], OnKeyDown: [{
|
|
453
|
+
type: HostListener,
|
|
454
|
+
args: ['document:keydown', ['$event']]
|
|
455
|
+
}] }); })();
|
|
456
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FormBuilderTabComponent, { className: "FormBuilderTabComponent", filePath: "src/ComponentStudio/components/form-builder/form-builder-tab.component.ts", lineNumber: 48 }); })();
|
|
457
|
+
//# sourceMappingURL=form-builder-tab.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-builder-tab.component.js","sourceRoot":"","sources":["../../../../src/ComponentStudio/components/form-builder/form-builder-tab.component.ts","../../../../src/ComponentStudio/components/form-builder/form-builder-tab.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n HostListener,\n Output,\n inject,\n} from '@angular/core';\nimport { Subject, takeUntil } from 'rxjs';\nimport { LogError } from '@memberjunction/core';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport type { FormMode } from '@memberjunction/interactive-component-types/forms';\nimport { ComponentStudioStateService } from '../../services/component-studio-state.service';\nimport { generateCodeFromCanvas } from '../../services/canvas-to-code';\nimport { parseCanvasFromCode } from '../../services/code-to-canvas';\nimport {\n buildEmptyCanvas,\n generateCanvasId,\n type FormCanvasElement,\n type FormCanvasModel,\n type FormCanvasSection,\n} from '../../services/form-canvas-model';\n\n/**\n * Form Builder tab — the visual drag-and-drop canvas surface that appears\n * in Component Studio's editor-tabs strip when the active spec is form-role.\n *\n * The canvas is the source-of-truth while this tab is active. On save, the\n * dashboard serialises the canvas to JSX via `generateCodeFromCanvas` and\n * pushes that into `state.EditableCode`. When the user opens an existing\n * form-role Component, `parseCanvasFromCode` reconstructs the canvas\n * (lossily) from the stored code; if the round-trip is too lossy, we leave\n * the canvas empty and steer the user toward the Code tab.\n *\n * The component is presentational where it can be — the heavy state\n * (canvas, schema, selection, preview mode) lives on\n * `ComponentStudioStateService`. This keeps the right-panel and the live\n * preview pane in sync with whatever the user is doing in here.\n */\n@Component({\n standalone: false,\n selector: 'mj-form-builder-tab',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './form-builder-tab.component.html',\n styleUrls: ['./form-builder-tab.component.css'],\n})\nexport class FormBuilderTabComponent {\n\n /** Notifies the parent (dashboard) that the user wants to switch to the Code tab. */\n @Output() RequestCodeTab = new EventEmitter<void>();\n\n /** Fired separately for the dashboard's NavigationService bridge. */\n @Output() OpenInChatRequested = new EventEmitter<void>();\n\n public IsEntityPickerOpen = false;\n public EntityPickerSearch = '';\n public EntityChoices: Array<{ Name: string; DisplayName: string }> = [];\n\n public readonly state = inject(ComponentStudioStateService);\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly notifications = inject(MJNotificationService);\n private readonly destroy$ = new Subject<void>();\n\n // ------------------------------------------------------------------\n // Lifecycle\n // ------------------------------------------------------------------\n\n ngOnInit(): void {\n this.state.StateChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {\n this.cdr.markForCheck();\n });\n // Lazily hydrate the canvas if we have a target entity but no canvas\n // (typical when the user lands here from the Code tab).\n this.hydrateCanvasFromState();\n this.refreshEntityChoices();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ------------------------------------------------------------------\n // Entity picker\n // ------------------------------------------------------------------\n\n public ToggleEntityPicker(): void {\n this.IsEntityPickerOpen = !this.IsEntityPickerOpen;\n if (this.IsEntityPickerOpen) this.refreshEntityChoices();\n this.cdr.markForCheck();\n }\n\n public OnEntityPickerSearch(event: Event): void {\n this.EntityPickerSearch = (event.target as HTMLInputElement).value;\n this.cdr.markForCheck();\n }\n\n public OnEntityPicked(entityName: string): void {\n this.IsEntityPickerOpen = false;\n const schema = this.state.BuildFormSchema(entityName);\n if (!schema) {\n this.notifications.CreateSimpleNotification(\n `Couldn't load schema for ${entityName}.`, 'error', 4000,\n );\n return;\n }\n this.state.FormTargetEntityName = entityName;\n // Try to seed canvas from existing code first; otherwise start empty.\n const existing = this.state.EditableCode ?? '';\n if (existing.length > 0) {\n const result = parseCanvasFromCode(existing, schema);\n if (result.canvas) {\n this.state.FormCanvas = result.canvas;\n this.state.FormCodeOnlySectionsDetected = result.hasUnknownConstructs;\n } else {\n this.state.FormCanvas = buildEmptyCanvas(entityName, schema.displayName);\n this.state.FormCodeOnlySectionsDetected = true;\n }\n } else {\n this.state.FormCanvas = buildEmptyCanvas(entityName, schema.displayName);\n this.state.FormCodeOnlySectionsDetected = false;\n }\n this.state.FormSelectedElementId = null;\n this.state.FormSelectedSectionId = this.state.FormCanvas?.sections[0]?.id ?? null;\n this.regenerateCode();\n this.cdr.markForCheck();\n }\n\n public get filteredEntityChoices(): Array<{ Name: string; DisplayName: string }> {\n const q = this.EntityPickerSearch.trim().toLowerCase();\n if (!q) return this.EntityChoices;\n return this.EntityChoices.filter(e =>\n e.Name.toLowerCase().includes(q) ||\n e.DisplayName.toLowerCase().includes(q));\n }\n\n private refreshEntityChoices(): void {\n const provider = this.state.Provider;\n if (!provider) {\n this.EntityChoices = [];\n return;\n }\n this.EntityChoices = (provider.Entities ?? [])\n .filter(e => e.AllowCreateAPI || e.AllowUpdateAPI)\n .map(e => ({ Name: e.Name, DisplayName: e.DisplayName ?? e.Name }))\n .sort((a, b) => a.DisplayName.localeCompare(b.DisplayName));\n }\n\n // ------------------------------------------------------------------\n // Preview-mode pills (synced with state.FormPreviewMode)\n // ------------------------------------------------------------------\n\n public SetPreviewMode(mode: FormMode): void {\n this.state.FormPreviewMode = mode;\n }\n\n // ------------------------------------------------------------------\n // Canvas events\n // ------------------------------------------------------------------\n\n public OnCanvasChanged(next: FormCanvasModel): void {\n this.state.FormCanvas = next;\n this.state.HasUnsavedChanges = true;\n this.regenerateCode();\n }\n\n public OnElementSelected(payload: { sectionId: string; elementId: string }): void {\n this.state.FormSelectedSectionId = null;\n this.state.FormSelectedElementId = payload.elementId;\n }\n\n public OnSectionSelected(sectionId: string): void {\n this.state.FormSelectedElementId = null;\n this.state.FormSelectedSectionId = sectionId;\n }\n\n public OnDeselected(): void {\n this.state.FormSelectedElementId = null;\n this.state.FormSelectedSectionId = null;\n }\n\n public OnElementChanged(next: FormCanvasElement): void {\n const canvas = this.state.FormCanvas;\n if (!canvas) return;\n const updated: FormCanvasModel = {\n ...canvas,\n sections: canvas.sections.map(s => ({\n ...s,\n elements: s.elements.map(e => e.id === next.id ? next : e),\n })),\n };\n this.OnCanvasChanged(updated);\n }\n\n public OnSectionChanged(next: FormCanvasSection): void {\n const canvas = this.state.FormCanvas;\n if (!canvas) return;\n const updated: FormCanvasModel = {\n ...canvas,\n sections: canvas.sections.map(s => s.id === next.id ? next : s),\n };\n this.OnCanvasChanged(updated);\n }\n\n public OnElementDeleted(elementId: string): void {\n const canvas = this.state.FormCanvas;\n if (!canvas) return;\n const updated: FormCanvasModel = {\n ...canvas,\n sections: canvas.sections.map(s => ({\n ...s,\n elements: s.elements.filter(e => e.id !== elementId),\n })),\n };\n this.state.FormSelectedElementId = null;\n this.OnCanvasChanged(updated);\n }\n\n public OnSectionDeleted(sectionId: string): void {\n const canvas = this.state.FormCanvas;\n if (!canvas) return;\n const updated: FormCanvasModel = {\n ...canvas,\n sections: canvas.sections.filter(s => s.id !== sectionId),\n };\n this.state.FormSelectedSectionId = null;\n this.OnCanvasChanged(updated);\n }\n\n public OnFieldAddedFromPalette(payload: { fieldName: string }): void {\n const canvas = this.state.FormCanvas;\n if (!canvas) return;\n const target = this.findFocusedSection(canvas);\n if (!target) return;\n const updated: FormCanvasModel = {\n ...canvas,\n sections: canvas.sections.map(s => s.id === target.id\n ? { ...s, elements: [...s.elements, {\n id: generateCanvasId('field'),\n type: 'field',\n fieldName: payload.fieldName,\n span: 1,\n }] }\n : s),\n };\n this.OnCanvasChanged(updated);\n }\n\n private findFocusedSection(canvas: FormCanvasModel): FormCanvasSection | null {\n if (this.state.FormSelectedSectionId) {\n const s = canvas.sections.find(s => s.id === this.state.FormSelectedSectionId);\n if (s) return s;\n }\n if (this.state.FormSelectedElementId) {\n const s = canvas.sections.find(sec =>\n sec.elements.some(e => e.id === this.state.FormSelectedElementId));\n if (s) return s;\n }\n return canvas.sections[0] ?? null;\n }\n\n // ------------------------------------------------------------------\n // Open in Chat\n // ------------------------------------------------------------------\n\n /**\n * Bubble up — the dashboard wires up `navigationService.SetAgentContext`\n * via the state event because we don't extend BaseResourceComponent\n * here. The tab also fires a local Output for the editor-tabs parent in\n * case it wants to react.\n */\n public OnOpenInChat(): void {\n this.OpenInChatRequested.emit();\n this.state.OpenInChatRequested.emit();\n }\n\n // ------------------------------------------------------------------\n // View Code toggle\n // ------------------------------------------------------------------\n\n public OnViewCode(): void {\n // Make sure the latest canvas is reflected in code before switching.\n this.regenerateCode();\n this.RequestCodeTab.emit();\n }\n\n // ------------------------------------------------------------------\n // Keyboard\n // ------------------------------------------------------------------\n\n @HostListener('document:keydown', ['$event'])\n public OnKeyDown(event: KeyboardEvent): void {\n const target = event.target as HTMLElement;\n const isInput = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;\n if (event.key === 'Delete' && !isInput && this.state.FormSelectedElementId) {\n this.OnElementDeleted(this.state.FormSelectedElementId);\n }\n if (event.key === 'Escape' && !isInput) {\n this.OnDeselected();\n }\n }\n\n // ------------------------------------------------------------------\n // Helpers\n // ------------------------------------------------------------------\n\n /**\n * Build a fresh canvas if the spec already has a target entity but no\n * canvas yet (e.g. tab activated for the first time). Best-effort — if\n * the spec's code is too lossy to parse, we leave the canvas empty and\n * show a banner.\n */\n private hydrateCanvasFromState(): void {\n const entity = this.state.FormTargetEntityName;\n if (!entity || this.state.FormCanvas) return;\n const schema = this.state.BuildFormSchema(entity);\n if (!schema) return;\n const existing = this.state.EditableCode ?? '';\n if (existing.length > 0) {\n const result = parseCanvasFromCode(existing, schema);\n if (result.canvas) {\n this.state.FormCanvas = result.canvas;\n this.state.FormCodeOnlySectionsDetected = result.hasUnknownConstructs;\n return;\n }\n }\n this.state.FormCanvas = buildEmptyCanvas(entity, schema.displayName);\n }\n\n /**\n * Mirror the canvas into ComponentStudioStateService.EditableCode so the\n * Code tab, the preview, and the eventual Save flow all see the same\n * source. Quietly no-op if we don't have enough to render.\n */\n private regenerateCode(): void {\n try {\n const canvas = this.state.FormCanvas;\n const schema = this.state.FormSchema;\n if (!canvas || !schema) return;\n const name = canvas.title?.trim() || schema.displayName;\n const code = generateCodeFromCanvas(canvas, schema, name);\n this.state.EditableCode = code;\n } catch (err) {\n LogError(`FormBuilderTab.regenerateCode: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n}\n","<div class=\"form-builder-tab\">\n\n <!-- ============================================================\n TOP TOOLBAR — entity picker, mode pills, code & chat handoff\n ============================================================ -->\n <div class=\"fbt-toolbar\">\n <div class=\"fbt-toolbar-left\">\n <!-- Entity picker -->\n <div class=\"fbt-entity-picker\" [class.open]=\"IsEntityPickerOpen\">\n <button class=\"fbt-entity-btn\" (click)=\"ToggleEntityPicker()\">\n <i class=\"fa-solid fa-table\"></i>\n <span>\n @if (state.FormTargetEntityName) {\n {{ state.FormTargetEntityName }}\n } @else {\n <em>Pick entity…</em>\n }\n </span>\n <i class=\"fa-solid fa-chevron-down chev\"></i>\n </button>\n @if (IsEntityPickerOpen) {\n <div class=\"fbt-entity-popover\" (click)=\"$event.stopPropagation()\">\n <input type=\"text\"\n class=\"fbt-entity-search\"\n placeholder=\"Search entities…\"\n [value]=\"EntityPickerSearch\"\n (input)=\"OnEntityPickerSearch($event)\" />\n <ul class=\"fbt-entity-list\">\n @for (e of filteredEntityChoices; track e.Name; let idx = $index) {\n @if (idx < 50) {\n <li class=\"fbt-entity-item\" (click)=\"OnEntityPicked(e.Name)\">\n <span class=\"fbt-entity-display\">{{ e.DisplayName }}</span>\n <span class=\"fbt-entity-name\">{{ e.Name }}</span>\n </li>\n }\n }\n </ul>\n </div>\n }\n </div>\n\n <!-- Mode pills -->\n <div class=\"fbt-mode-pills\" role=\"radiogroup\" aria-label=\"Preview mode\">\n <button class=\"fbt-mode-pill\"\n [class.active]=\"state.FormPreviewMode === 'view'\"\n (click)=\"SetPreviewMode('view')\"\n title=\"View mode — read-only\">\n <i class=\"fa-solid fa-eye\"></i> View\n </button>\n <button class=\"fbt-mode-pill\"\n [class.active]=\"state.FormPreviewMode === 'edit'\"\n (click)=\"SetPreviewMode('edit')\"\n title=\"Edit mode — fields editable\">\n <i class=\"fa-solid fa-pen\"></i> Edit\n </button>\n <button class=\"fbt-mode-pill\"\n [class.active]=\"state.FormPreviewMode === 'create'\"\n (click)=\"SetPreviewMode('create')\"\n title=\"Create mode — empty record\">\n <i class=\"fa-solid fa-plus\"></i> Create\n </button>\n </div>\n </div>\n\n <div class=\"fbt-toolbar-spacer\"></div>\n\n <div class=\"fbt-toolbar-right\">\n <button class=\"fbt-tool-btn\"\n (click)=\"OnViewCode()\"\n title=\"View generated code\">\n <i class=\"fa-solid fa-code\"></i> View Code\n </button>\n <button class=\"fbt-tool-btn\"\n [disabled]=\"!state.FormCanvas\"\n (click)=\"OnOpenInChat()\"\n title=\"Hand off the current canvas to the chat agent\">\n <i class=\"fa-solid fa-comment-dots\"></i> Open in Chat\n </button>\n </div>\n </div>\n\n <!-- ============================================================\n CANVAS BODY\n ============================================================ -->\n <div class=\"fbt-canvas-host\">\n @if (!state.FormTargetEntityName) {\n <div class=\"fbt-empty\">\n <i class=\"fa-solid fa-table-list\"></i>\n <h3>Pick an entity to start the form</h3>\n <p>Choose the target entity from the toolbar. Form Studio loads the curated schema and lets you drag fields onto the canvas.</p>\n </div>\n } @else {\n @if (state.FormCodeOnlySectionsDetected) {\n <div class=\"fbt-lossy-banner\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n Some parts of this form's code can't be represented on the canvas. Edit them in the Code tab to avoid losing them.\n </div>\n }\n <mj-form-builder-canvas\n [Canvas]=\"state.FormCanvas\"\n [Schema]=\"state.FormSchema\"\n [SelectedElementId]=\"state.FormSelectedElementId\"\n [SelectedSectionId]=\"state.FormSelectedSectionId\"\n (CanvasChanged)=\"OnCanvasChanged($event)\"\n (ElementSelected)=\"OnElementSelected($event)\"\n (SectionSelected)=\"OnSectionSelected($event)\"\n (Deselected)=\"OnDeselected()\">\n </mj-form-builder-canvas>\n }\n </div>\n</div>\n"]}
|