@masterteam/flowplus-workflow 0.0.7 → 0.0.8
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.
|
@@ -1969,7 +1969,8 @@ function friendlyValidationIssue(issue) {
|
|
|
1969
1969
|
suggestedFix: 'Open Configure and fill Left, Operator, and Right. The saved config must contain condition.left, condition.operator, and condition.right.',
|
|
1970
1970
|
};
|
|
1971
1971
|
}
|
|
1972
|
-
if (isNodeIssue &&
|
|
1972
|
+
if (isNodeIssue &&
|
|
1973
|
+
/^Node '.+' is unreachable from the start node\.$/i.test(message)) {
|
|
1973
1974
|
return {
|
|
1974
1975
|
field: 'routes',
|
|
1975
1976
|
fieldPath: 'routes',
|
|
@@ -10915,7 +10916,9 @@ let FlowplusWorkflowState = class FlowplusWorkflowState {
|
|
|
10915
10916
|
cancelAutomationExecution(ctx, action) {
|
|
10916
10917
|
const state = ctx.getState();
|
|
10917
10918
|
const executionId = action.executionId ?? state.runtime.activeExecutionId;
|
|
10918
|
-
if (executionId == null ||
|
|
10919
|
+
if (executionId == null ||
|
|
10920
|
+
state.runtime.canceling ||
|
|
10921
|
+
!state.runtime.running)
|
|
10919
10922
|
return EMPTY;
|
|
10920
10923
|
const previousStatus = state.runtime.status;
|
|
10921
10924
|
const wasRunning = state.runtime.running;
|
|
@@ -20283,7 +20286,7 @@ class AutomationSmartEditorComponent {
|
|
|
20283
20286
|
.subscribe((result) => this.subworkflowAutomations.set(result.items ?? []));
|
|
20284
20287
|
}
|
|
20285
20288
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AutomationSmartEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
20286
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: AutomationSmartEditorComponent, isStandalone: true, selector: "fp-automation-smart-editor", inputs: { step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block h-full min-h-0" }, ngImport: i0, template: "<div\n class=\"fp-scroll flex h-full min-h-0 flex-col overflow-y-auto\"\n fpDropData\n [fpDropAutoInsert]=\"true\"\n (dataDropEvent)=\"insertExpressionDrop($event)\"\n>\n <div class=\"space-y-4 px-5 py-5\">\n @if (helperError()) {\n <div\n class=\"rounded-lg border border-[rgb(var(--fp-warning))]/30 bg-[rgb(var(--fp-warning))]/10 px-3 py-2 text-[12px] leading-5 text-(--p-text-color)\"\n >\n {{ helperError() }}\n </div>\n }\n\n @if (sectionInMain(\"startConnection\") && trigger()) {\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n Start connection\n </h3>\n <div class=\"space-y-3 p-4\">\n @if (startConnection().key) {\n @if (startConnection().step) {\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\n <div class=\"min-w-0 space-y-1\">\n <div\n class=\"text-[12px] font-semibold text-(--p-text-muted-color)\"\n >\n First node connected\n </div>\n <div\n class=\"truncate text-[14px] font-semibold text-(--p-text-color)\"\n >\n {{ startConnection().label }}\n </div>\n <div\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\n >\n <span>Managed on canvas</span>\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ startConnection().key }}\n </span>\n </div>\n </div>\n <div class=\"flex shrink-0 flex-wrap gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"secondary\"\n label=\"Focus connected node\"\n (onClick)=\"focusStartConnection()\"\n />\n </div>\n </div>\n } @else {\n <div class=\"space-y-2\">\n <div class=\"text-[14px] font-semibold text-(--p-text-color)\">\n No first node connected\n </div>\n <p\n class=\"m-0 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n The saved start connection points to a node key that is not on\n the canvas. Connect this trigger to the first node on the\n canvas.\n </p>\n <div\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\n >\n <span>Technical key</span>\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ startConnection().key }}\n </span>\n <span>Managed on canvas</span>\n </div>\n </div>\n }\n } @else {\n <div\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"text-[14px] font-semibold text-(--p-text-color)\">\n No first node connected\n </div>\n <p\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n Connect this trigger to the first node on the canvas.\n </p>\n </div>\n }\n </div>\n </section>\n }\n\n @switch (editorType()) {\n @case (\"ManualTrigger\") {\n @if (sectionInMain(\"manualTrigger\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Manual run input</div>\n @if (\n hasTriggerPayloadSchema() && triggerPayloadSchema();\n as schema\n ) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Payload schema</div>\n <pre class=\"fp-ae-code\">{{ schemaText(schema) }}</pre>\n </div>\n }\n @if (triggerPayloadSample(); as sample) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Sample payload</div>\n <pre class=\"fp-ae-code\">{{ schemaText(sample) }}</pre>\n </div>\n }\n @if (!hasTriggerPayloadSchema() && !triggerPayloadSample()) {\n <p class=\"fp-ae-copy\">\n This manual trigger has no backend-provided input schema. It can\n still be connected to the first step on the canvas.\n </p>\n }\n </section>\n }\n }\n @case (\"WebhookTrigger\") {\n @if (sectionInMain(\"webhookSetup\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Webhook setup</div>\n @if (webhookSetup(); as setup) {\n <div class=\"flex gap-2\">\n <mt-text-field\n class=\"flex-1 font-mono\"\n [ngModel]=\"setup.webhookUrl ?? ''\"\n [readonly]=\"true\"\n label=\"Webhook URL\"\n hint=\"Backend-generated endpoint clients should call to start this trigger.\"\n />\n <mt-button\n class=\"self-end\"\n size=\"small\"\n variant=\"outlined\"\n label=\"Copy\"\n (onClick)=\"copyWebhookUrl()\"\n />\n </div>\n <div class=\"grid gap-2 md:grid-cols-2\">\n <div class=\"fp-ae-kv\">\n <span>Auth mode</span>\n <strong>{{ setup.authMode ?? \"Backend default\" }}</strong>\n </div>\n <div class=\"fp-ae-kv\">\n <span>Required headers</span>\n <strong>{{\n (setup.requiredHeaders ?? []).join(\", \") || \"-\"\n }}</strong>\n </div>\n </div>\n @if (setup.hmacSigning) {\n <p class=\"fp-ae-copy\">{{ setup.hmacSigning }}</p>\n }\n @if (setup.sampleRequest) {\n <details\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Sample request\n </summary>\n <pre class=\"fp-ae-code\">{{\n schemaText(setup.sampleRequest)\n }}</pre>\n </details>\n }\n } @else {\n <p class=\"fp-ae-copy\">\n Webhook setup is not available for this draft yet.\n </p>\n }\n </section>\n }\n @if (sectionInMain(\"authPolicy\") && hasAuthPolicy()) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Authentication policy</div>\n <div class=\"grid gap-3 md:grid-cols-3\">\n <mt-select-field\n [ngModel]=\"authPolicy()['mode'] ?? ''\"\n (ngModelChange)=\"onAuthFieldChange('mode', $event)\"\n label=\"Mode\"\n hint=\"Authentication mode required by the backend webhook policy.\"\n [required]=\"isAuthFieldRequired('mode')\"\n [options]=\"authModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"authPolicy()['signatureHeaderName'] ?? ''\"\n (ngModelChange)=\"\n onAuthFieldChange('signatureHeaderName', $event)\n \"\n label=\"Signature header\"\n hint=\"Header name that carries the webhook signature when the policy requires signed requests.\"\n [required]=\"isAuthFieldRequired('signatureHeaderName')\"\n />\n <mt-text-field\n [ngModel]=\"authPolicy()['timestampHeaderName'] ?? ''\"\n (ngModelChange)=\"\n onAuthFieldChange('timestampHeaderName', $event)\n \"\n label=\"Timestamp header\"\n hint=\"Header name that carries the request timestamp for replay protection.\"\n [required]=\"isAuthFieldRequired('timestampHeaderName')\"\n />\n </div>\n </section>\n }\n }\n @case (\"FormSubmitTrigger\") {\n @if (sectionInMain(\"formBinding\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Form binding</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"selectedFormId() || formBinding()?.formId || ''\"\n (ngModelChange)=\"onFormChange($event)\"\n label=\"Form\"\n hint=\"Choose a backend form that will submit data into this trigger.\"\n [required]=\"true\"\n [options]=\"formOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"\n selectedFormVersionId() || formBinding()?.formVersionId || ''\n \"\n (ngModelChange)=\"onFormVersionChange($event)\"\n label=\"Form version\"\n hint=\"Persist the exact backend formVersionId. Do not type or generate IDs manually.\"\n [required]=\"true\"\n [options]=\"formVersionOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Save binding\"\n (onClick)=\"saveFormBinding()\"\n />\n @if (formBinding()) {\n <span\n class=\"rounded-lg bg-(--p-surface-100) px-2 py-1 font-mono text-[11px] text-(--p-text-muted-color)\"\n >\n {{ formBinding()!.formVersionId }}\n </span>\n }\n </div>\n @if (formSchema(); as schema) {\n <details\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Form schema preview\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(schema.schema) }}</pre>\n </details>\n }\n </section>\n }\n }\n @case (\"ScheduleTrigger\") {\n @if (sectionInMain(\"schedule\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Schedule</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"scheduleMode()\"\n (ngModelChange)=\"onScheduleModeChange($event)\"\n label=\"Mode\"\n hint=\"Pick one schedule mode: cron, interval, or once. Backend validation requires exactly one mode.\"\n [required]=\"true\"\n [options]=\"scheduleModeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['timezone'] ?? 'UTC'\"\n (ngModelChange)=\"onConfigFieldChange('timezone', $event)\"\n label=\"Timezone\"\n hint=\"Timezone used to calculate the next fire time.\"\n [required]=\"isConfigFieldRequired('timezone')\"\n [options]=\"timeZoneOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (showScheduleCron()) {\n <mt-text-field\n class=\"font-mono md:col-span-2\"\n [ngModel]=\"config()['cron'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('cron', $event)\"\n label=\"Cron\"\n hint=\"Cron expression, for example 0 9 * * *.\"\n [required]=\"showScheduleCron()\"\n />\n }\n @if (showScheduleInterval()) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['intervalSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('intervalSeconds', $event)\n \"\n label=\"Interval seconds\"\n hint=\"Repeat interval in seconds.\"\n [required]=\"showScheduleInterval()\"\n [min]=\"0\"\n />\n }\n @if (showScheduleOnce()) {\n <mt-date-field\n class=\"md:col-span-2\"\n [ngModel]=\"\n config()['runAt'] ??\n config()['runAtUtc'] ??\n config()['oneTimeAt'] ??\n config()['at'] ??\n ''\n \"\n (ngModelChange)=\"onConfigFieldChange('runAt', $event)\"\n label=\"Run at UTC\"\n hint=\"UTC date/time for the one-time schedule.\"\n [required]=\"showScheduleOnce()\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n @if (showScheduleStartDate()) {\n <mt-date-field\n [ngModel]=\"config()['startDate'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('startDate', $event)\"\n label=\"Start date UTC\"\n hint=\"Optional first allowed run time for this schedule.\"\n [required]=\"isConfigFieldRequired('startDate')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n <mt-select-field\n [ngModel]=\"config()['misfirePolicy'] ?? 'SkipMissed'\"\n (ngModelChange)=\"onConfigFieldChange('misfirePolicy', $event)\"\n label=\"Misfire policy\"\n hint=\"Backend behavior when a scheduled run is missed while the automation is unavailable.\"\n [required]=\"isConfigFieldRequired('misfirePolicy')\"\n [options]=\"misfirePolicyOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Validate\"\n (onClick)=\"validateSchedule()\"\n />\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Preview next fire\"\n (onClick)=\"previewSchedule()\"\n />\n </div>\n @if (schedulePreview(); as preview) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n <strong class=\"text-emerald-600\">Valid</strong>\n <span class=\"ms-2\"\n >Next fire: {{ preview.nextFireAtUtc ?? \"-\" }}</span\n >\n </div>\n }\n </section>\n }\n }\n @case (\"SetFields\") {\n @if (sectionInMain(\"setFields\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Set fields</div>\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'fields',\n rows: fieldsRows(),\n keyLabel: 'Field',\n valueLabel: 'Value',\n addLabel: 'Add field',\n required: isConfigFieldRequired('fields'),\n }\n \"\n />\n </section>\n }\n }\n @case (\"If\") {\n @if (sectionInMain(\"condition\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Condition</div>\n <div class=\"grid gap-3 md:grid-cols-[1fr_180px_1fr]\">\n <mt-text-field\n [ngModel]=\"ifConditionFieldText('left')\"\n (focusin)=\"setExpressionTarget('config:left')\"\n (ngModelChange)=\"onIfConditionFieldChange('left', $event)\"\n label=\"Left\"\n hint=\"Left-side value or expression to compare.\"\n [required]=\"isConfigFieldRequired('left')\"\n />\n <mt-select-field\n [ngModel]=\"ifConditionFieldValue('operator') || 'equals'\"\n (ngModelChange)=\"onIfConditionFieldChange('operator', $event)\"\n label=\"Operator\"\n hint=\"Comparison operator used by the backend condition evaluator.\"\n [required]=\"isConfigFieldRequired('operator')\"\n [options]=\"ifOperatorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"ifConditionFieldText('right')\"\n (focusin)=\"setExpressionTarget('config:right')\"\n (ngModelChange)=\"onIfConditionFieldChange('right', $event)\"\n label=\"Right\"\n hint=\"Right-side value or expression to compare against.\"\n [required]=\"isConfigFieldRequired('right')\"\n />\n </div>\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Route outputs</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (key of routeOutputKeys(); track key) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ key }}</span\n >\n }\n </div>\n </div>\n </section>\n }\n }\n @case (\"HTTP\") {\n @if (sectionInMain(\"httpRequest\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">HTTP request</div>\n <div class=\"grid gap-3 md:grid-cols-[150px_1fr]\">\n <mt-select-field\n [ngModel]=\"httpMethod()\"\n (ngModelChange)=\"onHttpMethodChange($event)\"\n label=\"Method\"\n hint=\"HTTP method used for the outbound request.\"\n [required]=\"isConfigFieldRequired('method')\"\n [options]=\"httpMethodOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['url'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:url')\"\n (ngModelChange)=\"onConfigFieldChange('url', $event)\"\n label=\"URL\"\n hint=\"Target URL. Expressions are supported for dynamic hosts, paths, and query values.\"\n [required]=\"isConfigFieldRequired('url')\"\n />\n </div>\n <div class=\"mt-3\">\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'headers',\n rows: headerRows(),\n keyLabel: 'Header',\n valueLabel: 'Value',\n addLabel: 'Add header',\n }\n \"\n />\n </div>\n <div class=\"mt-3\">\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'query',\n rows: queryRows(),\n keyLabel: 'Query param',\n valueLabel: 'Value',\n addLabel: 'Add query param',\n }\n \"\n />\n </div>\n @if (httpMethodAllowsPayload()) {\n @if (httpSupportsBodyMode()) {\n <mt-select-field\n class=\"mt-3\"\n [ngModel]=\"httpBodyMode()\"\n (ngModelChange)=\"onHttpBodyModeChange($event)\"\n label=\"Payload mode\"\n hint=\"How the request payload should be sent by the backend HTTP runtime.\"\n [required]=\"isConfigFieldRequired(httpBodyModeConfigKey())\"\n [options]=\"httpBodyModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n }\n @if (httpBodyEnabled()) {\n <mt-textarea-field\n class=\"mt-3 w-full font-mono\"\n [ngModel]=\"valueText(config()['body'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:body')\"\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\n label=\"Request payload\"\n hint=\"Payload sent by this HTTP method. Use JSON or expressions when the backend schema allows it.\"\n [required]=\"isConfigFieldRequired('body')\"\n rows=\"6\"\n />\n }\n } @else {\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ httpMethod() }} requests do not send a payload. Put request\n data in query parameters or headers.\n </div>\n }\n @if (\n supportsConfigKey(\"timeoutSeconds\") ||\n supportsConfigKey(\"responseHandling\")\n ) {\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n @if (supportsConfigKey(\"timeoutSeconds\")) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('timeoutSeconds', $event)\n \"\n label=\"Timeout seconds\"\n hint=\"Request timeout when exposed by the backend schema.\"\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\n [min]=\"0\"\n />\n }\n @if (supportsConfigKey(\"responseHandling\")) {\n <mt-text-field\n [ngModel]=\"config()['responseHandling'] ?? ''\"\n (ngModelChange)=\"\n onConfigFieldChange('responseHandling', $event)\n \"\n label=\"Response handling\"\n hint=\"Backend-supported response handling mode or expression.\"\n [required]=\"isConfigFieldRequired('responseHandling')\"\n />\n }\n </div>\n }\n @if (sectionInMain(\"credentials\")) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Credential</div>\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\n </div>\n }\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"fp-ae-label\">Configuration check</div>\n <p class=\"fp-ae-copy m-0 mt-1\">\n Runs backend dry-run validation for method, URL,\n expressions, and request policy before execution.\n </p>\n </div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"secondary\"\n label=\"Check request\"\n [disabled]=\"httpCheckState() === 'loading'\"\n (onClick)=\"checkHttpRequest()\"\n />\n </div>\n @if (httpLocalIssues().length > 0) {\n <div\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-warning))]/25 bg-[rgb(var(--fp-warning))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\n >\n {{ httpLocalIssues()[0] }}\n </div>\n } @else if (httpCheckError(); as error) {\n <div\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-error))]/25 bg-[rgb(var(--fp-error))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\n >\n {{ error }}\n </div>\n } @else if (httpCheckResult(); as result) {\n <div\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-commit))]/25 bg-[rgb(var(--fp-commit))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\n >\n Backend check\n {{\n result.success || result.canStart\n ? \"passed\"\n : \"completed with issues\"\n }}.\n </div>\n }\n </div>\n </section>\n }\n }\n @case (\"Wait\") {\n @if (sectionInMain(\"wait\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Wait</div>\n <div class=\"grid gap-3 md:grid-cols-3\">\n <mt-select-field\n [ngModel]=\"waitMode()\"\n (ngModelChange)=\"onWaitModeChange($event)\"\n label=\"Mode\"\n hint=\"Choose how execution should pause.\"\n [required]=\"isConfigFieldRequired('mode')\"\n [options]=\"waitModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (showWaitDuration()) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['durationSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('durationSeconds', $event)\n \"\n label=\"Wait duration\"\n hint=\"Seconds to pause before continuing.\"\n [required]=\"isConfigFieldRequired('durationSeconds')\"\n [min]=\"1\"\n />\n }\n @if (showWaitUntilDate()) {\n <mt-date-field\n [ngModel]=\"waitUntilDateValue()\"\n (ngModelChange)=\"onWaitUntilDateChange($event)\"\n label=\"Resume at\"\n hint=\"Date and time when execution should continue.\"\n [required]=\"isConfigFieldRequired('untilDate')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n </div>\n @if (showWaitResumePayloadSchema()) {\n <mt-textarea-field\n class=\"mt-3 w-full font-mono\"\n [ngModel]=\"waitResumePayloadSchemaText()\"\n (focusin)=\"setExpressionTarget('config:resumePayloadSchema')\"\n (ngModelChange)=\"onWaitResumePayloadSchemaChange($event)\"\n label=\"Resume payload schema\"\n hint=\"Optional JSON schema for data sent back when this wait is resumed.\"\n [required]=\"isConfigFieldRequired('resumePayloadSchema')\"\n rows=\"5\"\n />\n }\n </section>\n }\n }\n @case (\"HumanTask\") {\n @if (sectionInMain(\"humanTask\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Human task</div>\n @if (\n assignmentOptions()?.providerStatus &&\n assignmentOptions()?.providerStatus !== \"Available\"\n ) {\n <p class=\"fp-ae-copy\">\n Assignment provider status:\n {{ assignmentOptions()?.providerStatus }}. The backend provider\n is the source of truth for available assignees.\n </p>\n }\n <div\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n Task details\n </h3>\n <div class=\"grid gap-3 p-4 xl:grid-cols-3\">\n <mt-text-field\n [ngModel]=\"config()['title'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:title')\"\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\n label=\"Task title\"\n hint=\"Title shown to the assigned user while the execution waits.\"\n [required]=\"isConfigFieldRequired('title')\"\n />\n <mt-select-field\n [ngModel]=\"selectedAssignmentKey()\"\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\n label=\"Assignment\"\n hint=\"Backend-provided assignee, role, or group that can submit this task.\"\n [required]=\"isConfigFieldRequired('assignment')\"\n [options]=\"assignmentSelectOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"config()['priority'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('priority', $event)\"\n label=\"Priority\"\n hint=\"Optional task priority passed to backend task orchestration.\"\n [required]=\"isConfigFieldRequired('priority')\"\n />\n <mt-textarea-field\n class=\"xl:col-span-3\"\n [ngModel]=\"config()['description'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:description')\"\n (ngModelChange)=\"onConfigFieldChange('description', $event)\"\n label=\"Task description\"\n hint=\"Instructions shown with the HumanTask form.\"\n [required]=\"isConfigFieldRequired('description')\"\n rows=\"4\"\n />\n </div>\n </div>\n\n <div\n class=\"mt-3 flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n Task behavior\n </h3>\n <div class=\"grid gap-3 p-4 xl:grid-cols-4\">\n <mt-date-field\n [ngModel]=\"\n config()['dueDateUtc'] ?? config()['dueDate'] ?? ''\n \"\n (ngModelChange)=\"onConfigFieldChange('dueDateUtc', $event)\"\n label=\"Due date\"\n hint=\"Optional backend due timestamp for the waiting task.\"\n [required]=\"isConfigFieldRequired('dueDateUtc')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n <mt-number-field\n [ngModel]=\"numberValue(config()['dueInSeconds'])\"\n (ngModelChange)=\"onConfigFieldChange('dueInSeconds', $event)\"\n label=\"Due in seconds\"\n hint=\"Relative due duration when the backend should calculate the timestamp.\"\n [required]=\"isConfigFieldRequired('dueInSeconds')\"\n [min]=\"0\"\n />\n <mt-text-field\n [ngModel]=\"config()['contextOutputPath'] ?? 'task'\"\n (ngModelChange)=\"\n onConfigFieldChange('contextOutputPath', $event)\n \"\n label=\"Context output path\"\n hint=\"Where submitted task values are written in execution context.\"\n [required]=\"isConfigFieldRequired('contextOutputPath')\"\n />\n <mt-select-field\n [ngModel]=\"config()['cancelBehavior'] ?? 'RouteCancelled'\"\n (ngModelChange)=\"\n onConfigFieldChange('cancelBehavior', $event)\n \"\n label=\"Cancel behavior\"\n hint=\"Route cancelled when users cancel, or fail this node.\"\n [required]=\"isConfigFieldRequired('cancelBehavior')\"\n [options]=\"humanTaskCancelBehaviorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <div\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\n >\n <mt-toggle-field\n size=\"small\"\n label=\"Allow draft save\"\n labelPosition=\"end\"\n [ngModel]=\"config()['saveDraft'] !== false\"\n [required]=\"isConfigFieldRequired('saveDraft')\"\n (ngModelChange)=\"\n onConfigFieldChange('saveDraft', $event === true)\n \"\n hint=\"Expose backend draft save for this task when supported.\"\n />\n </div>\n @if (supportsConfigKey(\"submitBehavior\")) {\n <mt-text-field\n [ngModel]=\"config()['submitBehavior'] ?? ''\"\n (ngModelChange)=\"\n onConfigFieldChange('submitBehavior', $event)\n \"\n label=\"Submit behavior\"\n hint=\"Backend submit behavior when exposed by the node schema.\"\n [required]=\"isConfigFieldRequired('submitBehavior')\"\n />\n }\n </div>\n </div>\n\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Validate assignment\"\n (onClick)=\"validateAssignment()\"\n />\n </div>\n @if (assignmentValidation(); as result) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n {{\n result.isValid === false\n ? \"Assignment invalid\"\n : \"Assignment accepted by helper\"\n }}\n @for (issue of resultIssues(result); track $index) {\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\n {{ issue }}\n </div>\n }\n </div>\n }\n </section>\n }\n\n @if (sectionInMain(\"formBinding\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Task input form</div>\n <mt-radio-cards-field\n [ngModel]=\"humanTaskFormSource()\"\n (ngModelChange)=\"onHumanTaskFormSourceChange($event)\"\n label=\"Form source\"\n hint=\"HumanTask requires either its own node form binding or an explicit reused FormSubmitTrigger payload.\"\n [required]=\"isConfigFieldRequired('formSource')\"\n [options]=\"humanTaskFormSourceOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n size=\"small\"\n >\n <ng-template #option let-item>\n <div class=\"w-full min-w-0\">\n <div class=\"text-[13px] font-semibold leading-5\">\n {{ item.label }}\n </div>\n <p\n class=\"mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ item.description }}\n </p>\n </div>\n </ng-template>\n </mt-radio-cards-field>\n\n @if (humanTaskUsesExplicitFormBinding()) {\n <div\n class=\"mt-3 flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n HumanTask input binding\n </h3>\n <div class=\"grid gap-3 p-4 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"selectedFormId() || formBinding()?.formId || ''\"\n (ngModelChange)=\"onFormChange($event)\"\n label=\"Form\"\n hint=\"Choose a backend form for this HumanTask node.\"\n [required]=\"humanTaskUsesExplicitFormBinding()\"\n [options]=\"formOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"\n selectedFormVersionId() ||\n formBinding()?.formVersionId ||\n ''\n \"\n (ngModelChange)=\"onFormVersionChange($event)\"\n label=\"Form version\"\n hint=\"Persist the exact backend formVersionId for the node binding.\"\n [required]=\"humanTaskUsesExplicitFormBinding()\"\n [options]=\"formVersionOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"border-t border-surface-200 px-4 py-3\">\n @if (humanTaskFormSummary(); as summary) {\n <div\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\n >\n @if (summary.formLabel) {\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.formLabel }}\n </span>\n }\n @if (summary.formVersionId) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ summary.formVersionId }}\n </span>\n }\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.fieldCount }} fields\n </span>\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.requiredCount }} required\n </span>\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.writableCount }} writable\n </span>\n </div>\n }\n\n @if (humanTaskHasCustomInputMapping()) {\n <div\n class=\"mt-3 rounded-md border border-[rgb(var(--fp-warning))]/30 bg-[rgb(var(--fp-warning))]/10 px-3 py-2 text-[12px] leading-5 text-(--p-text-color)\"\n >\n Existing custom input mapping will be preserved unless\n field prefill options are changed.\n </div>\n }\n\n @if (humanTaskFormFields().length > 0) {\n <div\n class=\"mt-3 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <div\n class=\"flex flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\n >\n <div class=\"text-[13px] font-semibold text-color\">\n FormBuilder fields\n </div>\n <div class=\"flex flex-wrap gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"secondary\"\n label=\"Select all\"\n (onClick)=\"selectAllHumanTaskPrefillFields()\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"secondary\"\n label=\"Clear\"\n (onClick)=\"clearHumanTaskPrefillFields()\"\n />\n </div>\n </div>\n <div class=\"divide-y divide-surface-200\">\n @for (field of humanTaskFormFields(); track field.key) {\n <div\n class=\"grid gap-3 px-3 py-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-center\"\n >\n <div class=\"min-w-0\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\n >\n {{ field.label }}\n </span>\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 font-mono text-[11px] text-(--p-text-muted-color)\"\n >\n {{ field.key }}\n </span>\n @if (field.required) {\n <span\n class=\"rounded-md bg-[rgb(var(--fp-danger))]/10 px-2 py-0.5 text-[11px] font-semibold text-[rgb(var(--fp-danger))]\"\n >\n Required\n </span>\n }\n @if (field.read === true) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\n >\n Read\n </span>\n }\n @if (field.write === true) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\n >\n Write\n </span>\n }\n </div>\n @if (field.description) {\n <p\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ field.description }}\n </p>\n }\n </div>\n <mt-toggle-field\n size=\"small\"\n label=\"Prefill\"\n labelPosition=\"end\"\n [ngModel]=\"\n humanTaskPrefillFieldKeySet().has(field.key)\n \"\n (ngModelChange)=\"\n onHumanTaskPrefillFieldToggle(\n field.key,\n $event === true\n )\n \"\n />\n </div>\n }\n </div>\n <div\n class=\"border-t border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n Selected fields are saved as\n <span class=\"font-mono\"\n >inputMappingJson.fieldKeys</span\n >\n so backend can prefill matching upstream values.\n </div>\n </div>\n } @else if (formSchema()) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n No FormBuilder fields were returned for this form schema.\n </div>\n }\n\n @if (formSchema()?.requiresSnapshotOnPublish) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n Backend requires this form version snapshot during\n publish.\n </div>\n }\n </div>\n <div class=\"border-t border-surface-200 px-4 py-3\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Save task binding\"\n (onClick)=\"saveFormBinding()\"\n />\n @if (formBinding()) {\n <span\n class=\"rounded-lg bg-(--p-surface-100) px-2 py-1 font-mono text-[11px] text-(--p-text-muted-color)\"\n >\n {{ formBinding()!.formVersionId }}\n </span>\n }\n </div>\n </div>\n </div>\n } @else {\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['triggerKey'] ?? ''\"\n (ngModelChange)=\"onHumanTaskReuseTriggerChange($event)\"\n label=\"Trigger to reuse\"\n hint=\"Explicitly select the FormSubmitTrigger whose submitted form payload is reused.\"\n [options]=\"triggerOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n @if (humanTaskReuseFormBinding()) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Reused trigger form</div>\n @if (humanTaskFormSummary(); as summary) {\n <div\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\n >\n @if (summary.formLabel) {\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.formLabel }}\n </span>\n }\n @if (summary.formVersionId) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ summary.formVersionId }}\n </span>\n }\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.fieldCount }} fields\n </span>\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.requiredCount }} required\n </span>\n </div>\n }\n </div>\n }\n }\n\n @if (formSchema(); as schema) {\n <details\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Form schema preview\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(schema.schema) }}</pre>\n </details>\n }\n\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">HumanTask routes</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (key of routeOutputKeys(); track key) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ key }}</span\n >\n }\n </div>\n </div>\n </section>\n }\n }\n @case (\"HumanApproval\") {\n @if (sectionInMain(\"approvalTask\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Approval task</div>\n @if (\n assignmentOptions()?.providerStatus &&\n assignmentOptions()?.providerStatus !== \"Available\"\n ) {\n <p class=\"fp-ae-copy\">\n Assignment provider status:\n {{ assignmentOptions()?.providerStatus }}. The backend provider\n is the source of truth for available assignees.\n </p>\n }\n <div class=\"grid gap-3 xl:grid-cols-3\">\n <mt-text-field\n [ngModel]=\"config()['title'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:title')\"\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\n label=\"Approval title\"\n hint=\"Approval title shown to the assigned human approver.\"\n [required]=\"isConfigFieldRequired('title')\"\n />\n <mt-select-field\n [ngModel]=\"selectedAssignmentKey()\"\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\n label=\"Assignment\"\n hint=\"Backend-provided assignee, role, or group that can decide this approval.\"\n [required]=\"isConfigFieldRequired('assignment')\"\n [options]=\"assignmentSelectOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (humanApprovalSupportsConfig(\"priority\")) {\n <mt-text-field\n [ngModel]=\"config()['priority'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('priority', $event)\"\n label=\"Priority\"\n hint=\"Approval priority when supported by the backend schema.\"\n [required]=\"isConfigFieldRequired('priority')\"\n />\n }\n </div>\n <mt-textarea-field\n class=\"mt-3 w-full\"\n [ngModel]=\"config()['message'] ?? config()['instructions'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message')\"\n (ngModelChange)=\"onConfigFieldChange('message', $event)\"\n label=\"Approval message\"\n hint=\"Decision instructions shown to the approver.\"\n [required]=\"isConfigFieldRequired('message')\"\n rows=\"4\"\n />\n @if (\n humanApprovalSupportsConfig(\"dueDate\") ||\n humanApprovalSupportsConfig(\"dueInSeconds\") ||\n humanApprovalSupportsConfig(\"timeoutSeconds\") ||\n humanApprovalSupportsConfig(\"expiresAt\") ||\n humanApprovalSupportsConfig(\"commentsRequired\") ||\n humanApprovalSupportsConfig(\"allowReturn\")\n ) {\n <div class=\"mt-3 grid gap-3 xl:grid-cols-4\">\n @if (humanApprovalSupportsConfig(\"dueDate\")) {\n <mt-date-field\n [ngModel]=\"config()['dueDate'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('dueDate', $event)\"\n label=\"Due date\"\n hint=\"Backend-supported approval due date.\"\n [required]=\"isConfigFieldRequired('dueDate')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n @if (humanApprovalSupportsConfig(\"dueInSeconds\")) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['dueInSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('dueInSeconds', $event)\n \"\n label=\"Due in seconds\"\n hint=\"Relative approval due duration when supported by the backend schema.\"\n [required]=\"isConfigFieldRequired('dueInSeconds')\"\n [min]=\"0\"\n />\n }\n @if (humanApprovalSupportsConfig(\"timeoutSeconds\")) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('timeoutSeconds', $event)\n \"\n label=\"Timeout seconds\"\n hint=\"Approval timeout emitted according to backend approval runtime support.\"\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\n [min]=\"0\"\n />\n }\n @if (humanApprovalSupportsConfig(\"expiresAt\")) {\n <mt-date-field\n [ngModel]=\"config()['expiresAt'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('expiresAt', $event)\"\n label=\"Expires at\"\n hint=\"Backend-supported approval expiry timestamp.\"\n [required]=\"isConfigFieldRequired('expiresAt')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n @if (humanApprovalSupportsConfig(\"commentsRequired\")) {\n <div\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\n >\n <mt-toggle-field\n size=\"small\"\n label=\"Comments required\"\n labelPosition=\"end\"\n [ngModel]=\"config()['commentsRequired'] === true\"\n [required]=\"isConfigFieldRequired('commentsRequired')\"\n (ngModelChange)=\"\n onConfigFieldChange('commentsRequired', $event === true)\n \"\n hint=\"Require approver comments when the backend supports this flag.\"\n />\n </div>\n }\n @if (humanApprovalSupportsConfig(\"allowReturn\")) {\n <div\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\n >\n <mt-toggle-field\n size=\"small\"\n label=\"Allow return for changes\"\n labelPosition=\"end\"\n [ngModel]=\"config()['allowReturn'] !== false\"\n [required]=\"isConfigFieldRequired('allowReturn')\"\n (ngModelChange)=\"\n onConfigFieldChange('allowReturn', $event === true)\n \"\n hint=\"Allow ReturnForChanges when the backend schema exposes this option.\"\n />\n </div>\n }\n </div>\n }\n <div class=\"mt-3 space-y-3\">\n <div class=\"fp-ae-section-title\">Decision options</div>\n <div\n class=\"overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n @if (selectedApprovalDecisionRows().length > 0) {\n <div\n class=\"hidden border-b border-surface-200 bg-surface-50 px-3 py-2 text-[12px] font-semibold text-(--p-text-muted-color) xl:grid xl:grid-cols-[1.1fr_1fr_1.2fr_80px_44px] xl:gap-3\"\n >\n <span>Decision label</span>\n <span>Canonical value</span>\n <span>Route output key</span>\n <span>Routes</span>\n <span>Action</span>\n </div>\n @for (\n decision of selectedApprovalDecisionRows();\n track decision.value\n ) {\n <div\n class=\"grid gap-2 border-b border-surface-100 px-3 py-3 last:border-b-0 xl:grid-cols-[1.1fr_1fr_1.2fr_80px_44px] xl:items-center xl:gap-3\"\n >\n <div class=\"min-w-0\">\n <div\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\n >\n Decision label\n </div>\n <div\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\n >\n {{ decision.label }}\n </div>\n </div>\n <div class=\"min-w-0\">\n <div\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\n >\n Canonical value\n </div>\n <div\n class=\"truncate font-mono text-[12px] text-(--p-text-color)\"\n >\n {{ decision.value }}\n </div>\n </div>\n <div class=\"min-w-0\">\n <div\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\n >\n Route output key\n </div>\n <div\n class=\"truncate font-mono text-[12px] text-(--p-text-muted-color)\"\n >\n {{ decision.routeOutputKey }}\n </div>\n </div>\n <div>\n <div\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\n >\n Routes\n </div>\n <div class=\"text-[13px] text-(--p-text-color)\">\n {{ decision.routeCount }}\n </div>\n </div>\n <mt-button\n class=\"justify-self-start xl:justify-self-end\"\n size=\"small\"\n variant=\"outlined\"\n severity=\"danger\"\n icon=\"general.trash-01\"\n tooltip=\"Remove decision option\"\n (onClick)=\"removeApprovalDecision(decision.value)\"\n />\n </div>\n }\n } @else {\n <div\n class=\"px-3 py-4 text-[12px] text-(--p-text-muted-color)\"\n >\n Add at least one backend-supported approval decision.\n </div>\n }\n </div>\n @if (approvalDecisionIssues().length > 0) {\n <div\n class=\"rounded-lg border border-[rgb(var(--fp-error))]/30 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\n >\n @for (issue of approvalDecisionIssues(); track $index) {\n <div>{{ issue }}</div>\n }\n </div>\n }\n <div class=\"grid gap-2 md:grid-cols-[minmax(0,1fr)_auto]\">\n <mt-select-field\n [ngModel]=\"approvalDecisionToAdd()\"\n (ngModelChange)=\"approvalDecisionToAdd.set($event)\"\n label=\"Decision to add\"\n hint=\"Only backend-supported decisions with matching route outputs are available.\"\n [required]=\"isConfigFieldRequired('allowedDecisions')\"\n [options]=\"addableApprovalDecisionOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-button\n class=\"self-end\"\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add decision option\"\n [disabled]=\"!approvalDecisionToAdd()\"\n (onClick)=\"addApprovalDecision()\"\n />\n </div>\n </div>\n @if (\n humanApprovalSupportsConfig(\"payload\") ||\n humanApprovalSupportsConfig(\"context\") ||\n humanApprovalSupportsConfig(\"metadata\")\n ) {\n <div class=\"mt-3 grid gap-3\">\n @if (humanApprovalSupportsConfig(\"payload\")) {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"valueText(config()['payload'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:payload')\"\n (ngModelChange)=\"onConfigFieldChange('payload', $event)\"\n label=\"Payload\"\n hint=\"Approval task payload or context fields supported by backend schema.\"\n [required]=\"isConfigFieldRequired('payload')\"\n rows=\"5\"\n />\n }\n @if (humanApprovalSupportsConfig(\"context\")) {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"valueText(config()['context'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:context')\"\n (ngModelChange)=\"onConfigFieldChange('context', $event)\"\n label=\"Context\"\n hint=\"Additional approval context supported by backend schema.\"\n [required]=\"isConfigFieldRequired('context')\"\n rows=\"5\"\n />\n }\n @if (humanApprovalSupportsConfig(\"metadata\")) {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"valueText(config()['metadata'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:metadata')\"\n (ngModelChange)=\"onConfigFieldChange('metadata', $event)\"\n label=\"Metadata\"\n hint=\"Additional approval metadata supported by the backend schema.\"\n [required]=\"isConfigFieldRequired('metadata')\"\n rows=\"4\"\n />\n }\n </div>\n }\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Validate assignment\"\n (onClick)=\"validateAssignment()\"\n />\n </div>\n @if (assignmentValidation(); as result) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n {{\n result.isValid === false\n ? \"Assignment invalid\"\n : \"Assignment accepted by helper\"\n }}\n @for (issue of resultIssues(result); track $index) {\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\n {{ issue }}\n </div>\n }\n </div>\n }\n </section>\n }\n }\n @case (\"ConnectorAction\") {\n @if (sectionInMain(\"humanReviewRequest\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">\n {{ humanReviewProviderLabel() }} human review\n </div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"config()['channelType'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('channelType', $event)\"\n label=\"Channel\"\n [required]=\"isConfigFieldRequired('channelType')\"\n />\n </div>\n\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\n <mt-text-field\n [ngModel]=\"humanReviewDestination()['recipient'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:destination:recipient')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('destination', 'recipient', $event)\n \"\n label=\"Recipient\"\n [required]=\"isConnectorDestinationRequired()\"\n />\n <mt-text-field\n [ngModel]=\"humanReviewDestination()['channelId'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:destination:channelId')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('destination', 'channelId', $event)\n \"\n label=\"Channel ID\"\n [required]=\"isConnectorDestinationRequired()\"\n />\n <mt-text-field\n [ngModel]=\"humanReviewDestination()['threadKey'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:destination:threadKey')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('destination', 'threadKey', $event)\n \"\n label=\"Thread key\"\n [required]=\"isConnectorDestinationRequired()\"\n />\n </div>\n\n <div class=\"mt-4 grid gap-3\">\n <mt-text-field\n [ngModel]=\"humanReviewMessage()['subject'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message:subject')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('message', 'subject', $event)\n \"\n label=\"Subject\"\n [required]=\"isConnectorMessageRequired()\"\n />\n <mt-textarea-field\n class=\"w-full\"\n [ngModel]=\"humanReviewMessage()['body'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message:body')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('message', 'body', $event)\n \"\n label=\"Message\"\n [required]=\"isConnectorMessageRequired()\"\n rows=\"5\"\n />\n <mt-text-field\n [ngModel]=\"humanReviewMessage()['templateKey'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message:templateKey')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('message', 'templateKey', $event)\n \"\n label=\"Template key\"\n [required]=\"isConnectorMessageRequired()\"\n />\n </div>\n\n <div class=\"mt-4\">\n <div class=\"mb-2 flex items-center justify-between gap-3\">\n <div class=\"fp-ae-label\">Response options</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Add option\"\n (onClick)=\"addHumanReviewResponseOption()\"\n />\n </div>\n <div class=\"grid gap-2\">\n @for (\n option of humanReviewResponseOptions();\n track option.key + \":\" + $index\n ) {\n <div\n class=\"grid gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-2 md:grid-cols-[1fr_1fr_1fr_auto]\"\n >\n <mt-text-field\n [ngModel]=\"option.key\"\n (ngModelChange)=\"\n updateHumanReviewResponseOption($index, 'key', $event)\n \"\n label=\"Key\"\n [required]=\"isConfigFieldRequired('responseOptions')\"\n />\n <mt-text-field\n [ngModel]=\"option.label\"\n (ngModelChange)=\"\n updateHumanReviewResponseOption($index, 'label', $event)\n \"\n label=\"Label\"\n [required]=\"isConfigFieldRequired('responseOptions')\"\n />\n <mt-text-field\n [ngModel]=\"option.routeOutputKey\"\n (ngModelChange)=\"\n updateHumanReviewResponseOption(\n $index,\n 'routeOutputKey',\n $event\n )\n \"\n label=\"Route output\"\n [required]=\"isConfigFieldRequired('responseOptions')\"\n />\n <div class=\"flex items-end justify-end\">\n <mt-button\n size=\"small\"\n variant=\"text\"\n severity=\"secondary\"\n icon=\"general.x-close\"\n [disabled]=\"option.routeCount > 0\"\n (onClick)=\"removeHumanReviewResponseOption($index)\"\n [attr.aria-label]=\"'Remove response option'\"\n />\n </div>\n </div>\n }\n </div>\n </div>\n\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"selectedAssignmentKey()\"\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\n label=\"Assignment\"\n [required]=\"isConfigFieldRequired('assignment')\"\n [options]=\"assignmentSelectOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"config()['credentialRef'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('credentialRef', $event)\"\n label=\"Credential\"\n [required]=\"isConfigFieldRequired('credentialRef')\"\n />\n <mt-number-field\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\n (ngModelChange)=\"onConfigFieldChange('timeoutSeconds', $event)\"\n label=\"Timeout seconds\"\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\n [min]=\"1\"\n />\n <mt-select-field\n [ngModel]=\"config()['failureBehavior'] ?? 'RouteFailure'\"\n (ngModelChange)=\"onConfigFieldChange('failureBehavior', $event)\"\n label=\"Failure behavior\"\n [required]=\"isConfigFieldRequired('failureBehavior')\"\n [options]=\"humanReviewFailureBehaviorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Validate assignment\"\n (onClick)=\"validateAssignment()\"\n />\n </div>\n @if (assignmentValidation(); as result) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n {{\n result.isValid === false\n ? \"Assignment invalid\"\n : \"Assignment accepted by helper\"\n }}\n @for (issue of resultIssues(result); track $index) {\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\n {{ issue }}\n </div>\n }\n </div>\n }\n\n <div\n class=\"mt-4 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Route outputs</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (key of routeOutputKeys(); track key) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ key }}</span\n >\n }\n </div>\n </div>\n </section>\n }\n }\n @case (\"ConvertToFile\") {\n @if (sectionInMain(\"convertToFile\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">\n {{ convertToFileActionLabel() }}\n </div>\n @if (convertToFileUnavailableReason(); as reason) {\n <div\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ reason }}\n </div>\n }\n\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"config()['fileName'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:fileName')\"\n (ngModelChange)=\"onConfigFieldChange('fileName', $event)\"\n label=\"File name\"\n [required]=\"isConfigFieldRequired('fileName')\"\n />\n @if (convertToFileIsBase64()) {\n <mt-text-field\n [ngModel]=\"config()['contentType'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('contentType', $event)\"\n label=\"Content type\"\n [required]=\"isConfigFieldRequired('contentType')\"\n />\n } @else {\n <mt-text-field\n [ngModel]=\"convertToFileContentType()\"\n [readonly]=\"true\"\n label=\"Content type\"\n />\n }\n </div>\n\n @if (\n convertToFileIsCsv() ||\n convertToFileIsJson() ||\n convertToFileIsSpreadsheet()\n ) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"convertToFileSourceMode()\"\n (ngModelChange)=\"onConvertToFileSourceModeChange($event)\"\n label=\"Data source\"\n [required]=\"isConfigFieldRequired('sourceExpression')\"\n [options]=\"convertToFileSourceOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (convertToFileSourceMode() === \"expression\") {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"convertToFileSourceExpression()\"\n (focusin)=\"setExpressionTarget('config:sourceExpression')\"\n (ngModelChange)=\"\n onConvertToFileSourceExpressionChange($event)\n \"\n label=\"Source expression\"\n [required]=\"isConfigFieldRequired('sourceExpression')\"\n />\n }\n @if (convertToFileIsCsv() || convertToFileIsSpreadsheet()) {\n <div\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3 md:col-span-2\"\n >\n <div class=\"mb-2 flex items-center justify-between gap-3\">\n <div class=\"fp-ae-label\">Columns</div>\n @if (convertToFileColumns().length) {\n <mt-button\n size=\"small\"\n variant=\"text\"\n severity=\"secondary\"\n label=\"Clear\"\n (onClick)=\"clearConvertToFileColumns()\"\n />\n }\n </div>\n <div class=\"mb-3 flex min-h-8 flex-wrap gap-1.5\">\n @if (convertToFileColumns().length) {\n @for (column of convertToFileColumns(); track column) {\n <span\n class=\"inline-flex items-center gap-1 rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ column }}\n <button\n type=\"button\"\n class=\"inline-flex h-4 w-4 items-center justify-center rounded-sm text-[10px] hover:bg-(--p-surface-200) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n (click)=\"removeConvertToFileColumn(column)\"\n [attr.aria-label]=\"'Remove column ' + column\"\n >\n x\n </button>\n </span>\n }\n } @else {\n <span\n class=\"rounded-md bg-(--p-content-background) px-2 py-1 text-[12px] text-(--p-text-muted-color)\"\n >\n All fields\n </span>\n }\n </div>\n <div class=\"grid items-end gap-2 md:grid-cols-[1fr_auto]\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"convertToFileColumnDraft()\"\n (ngModelChange)=\"\n onConvertToFileColumnDraftChange($event)\n \"\n (keyup.enter)=\"addConvertToFileColumns()\"\n label=\"Add column\"\n [required]=\"isConfigFieldRequired('columns')\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add\"\n [disabled]=\"!convertToFileColumnDraft().trim()\"\n (onClick)=\"addConvertToFileColumns()\"\n />\n </div>\n </div>\n }\n </div>\n }\n\n @if (convertToFileIsCsv()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['includeHeaders'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('includeHeaders', $event === true)\n \"\n label=\"Include headers\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['delimiter'] ?? ','\"\n (ngModelChange)=\"onConfigFieldChange('delimiter', $event)\"\n label=\"Delimiter\"\n [required]=\"isConfigFieldRequired('delimiter')\"\n />\n <mt-text-field\n [ngModel]=\"config()['encoding'] ?? 'utf-8'\"\n [readonly]=\"true\"\n label=\"Encoding\"\n />\n </div>\n }\n\n @if (convertToFileIsJson()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['pretty'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('pretty', $event === true)\n \"\n label=\"Pretty JSON\"\n />\n </div>\n }\n\n @if (convertToFileIsHtml()) {\n <div class=\"mt-4 grid gap-3\">\n <mt-text-field\n [ngModel]=\"config()['title'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:title')\"\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\n label=\"Title\"\n [required]=\"isConfigFieldRequired('title')\"\n />\n <mt-textarea-field\n class=\"w-full\"\n [ngModel]=\"config()['body'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:body')\"\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\n label=\"Body\"\n [required]=\"isConfigFieldRequired('body')\"\n rows=\"7\"\n />\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['sanitize'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('sanitize', $event === true)\n \"\n label=\"Sanitize HTML\"\n />\n </div>\n }\n\n @if (convertToFileIsIcs()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"config()['summary'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:summary')\"\n (ngModelChange)=\"onConfigFieldChange('summary', $event)\"\n label=\"Event summary\"\n [required]=\"isConfigFieldRequired('summary')\"\n />\n <mt-text-field\n [ngModel]=\"config()['timezone'] ?? 'UTC'\"\n (ngModelChange)=\"onConfigFieldChange('timezone', $event)\"\n label=\"Timezone\"\n [required]=\"isConfigFieldRequired('timezone')\"\n />\n <mt-date-field\n [ngModel]=\"config()['start'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('start', $event)\"\n label=\"Start\"\n [required]=\"isConfigFieldRequired('start')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n <mt-date-field\n [ngModel]=\"config()['end'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('end', $event)\"\n label=\"End\"\n [required]=\"isConfigFieldRequired('end')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n <mt-text-field\n [ngModel]=\"config()['location'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:location')\"\n (ngModelChange)=\"onConfigFieldChange('location', $event)\"\n label=\"Location\"\n [required]=\"isConfigFieldRequired('location')\"\n />\n <mt-textarea-field\n class=\"w-full\"\n [ngModel]=\"config()['description'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:description')\"\n (ngModelChange)=\"onConfigFieldChange('description', $event)\"\n label=\"Description\"\n [required]=\"isConfigFieldRequired('description')\"\n rows=\"4\"\n />\n </div>\n }\n\n @if (convertToFileIsSpreadsheet()) {\n <div\n class=\"mt-4 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"mb-3 flex items-center justify-between gap-3\">\n <div class=\"fp-ae-label\">Sheets</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add sheet\"\n (onClick)=\"addConvertToFileSheet()\"\n />\n </div>\n <div class=\"grid gap-3\">\n @for (sheet of convertToFileSheetRows(); track sheet.index) {\n <div\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\n >\n <div\n class=\"grid gap-3 md:grid-cols-[minmax(140px,0.45fr)_minmax(0,1fr)_minmax(180px,0.6fr)_auto]\"\n >\n <mt-text-field\n [ngModel]=\"sheet.name\"\n (ngModelChange)=\"\n updateConvertToFileSheet(\n sheet.index,\n 'name',\n $event\n )\n \"\n label=\"Sheet name\"\n [required]=\"isConfigFieldRequired('sheets')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"sheet.sourceExpression\"\n (focusin)=\"\n setExpressionTarget(\n 'config:sheets:' + sheet.index + ':source'\n )\n \"\n (ngModelChange)=\"\n updateConvertToFileSheet(\n sheet.index,\n 'sourceExpression',\n $event\n )\n \"\n label=\"Source\"\n [required]=\"isConfigFieldRequired('sheets')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"sheet.columnsText\"\n (ngModelChange)=\"\n updateConvertToFileSheet(\n sheet.index,\n 'columns',\n $event\n )\n \"\n label=\"Columns\"\n [required]=\"isConfigFieldRequired('sheets')\"\n />\n <div class=\"flex items-end justify-end\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"danger\"\n icon=\"general.trash-01\"\n tooltip=\"Remove sheet\"\n (onClick)=\"removeConvertToFileSheet(sheet.index)\"\n />\n </div>\n </div>\n </div>\n } @empty {\n <div\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\n >\n Single sheet uses the selected data source.\n </div>\n }\n </div>\n </div>\n }\n\n @if (convertToFileIsText()) {\n <div class=\"mt-4 grid gap-3\">\n <mt-textarea-field\n class=\"w-full\"\n [ngModel]=\"config()['content'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:content')\"\n (ngModelChange)=\"onConfigFieldChange('content', $event)\"\n label=\"Content\"\n [required]=\"isConfigFieldRequired('content')\"\n rows=\"7\"\n />\n <mt-text-field\n [ngModel]=\"config()['encoding'] ?? 'utf-8'\"\n [readonly]=\"true\"\n label=\"Encoding\"\n />\n </div>\n }\n\n @if (convertToFileIsBase64()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"config()['base64Expression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:base64Expression')\"\n (ngModelChange)=\"\n onConfigFieldChange('base64Expression', $event)\n \"\n label=\"Base64 source\"\n [required]=\"isConfigFieldRequired('base64Expression')\"\n rows=\"5\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['extension'] ?? convertToFileExtension()\"\n (ngModelChange)=\"onConfigFieldChange('extension', $event)\"\n label=\"Extension\"\n [required]=\"isConfigFieldRequired('extension')\"\n />\n </div>\n }\n </section>\n }\n }\n @case (\"DataTransform\") {\n @if (sectionInMain(\"dataTransform\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">\n {{ dataTransformActionLabel() }}\n </div>\n @if (dataTransformUnavailableReason(); as reason) {\n <div\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ reason }}\n </div>\n }\n\n @if (\n !dataTransformIsMerge() && dataTransformUsesKnownActionEditor()\n ) {\n <div class=\"grid gap-3\">\n <div class=\"grid gap-3 md:grid-cols-[minmax(220px,0.45fr)_1fr]\">\n <mt-select-field\n [ngModel]=\"dataTransformSourcePreset()\"\n (ngModelChange)=\"onDataTransformSourcePresetChange($event)\"\n [label]=\"dataTransformSourceLabel()\"\n hint=\"Choose which list this action should transform.\"\n [required]=\"isConfigFieldRequired('sourceExpression')\"\n [options]=\"dataTransformSourceOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (showDataTransformSourceExpression()) {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"dataTransformSourceExpression()\"\n (focusin)=\"setExpressionTarget('config:sourceExpression')\"\n (ngModelChange)=\"\n onDataTransformSourceExpressionChange($event)\n \"\n [label]=\"dataTransformSourceExpressionLabel()\"\n hint=\"Must resolve to an array of items.\"\n [required]=\"isConfigFieldRequired('sourceExpression')\"\n />\n } @else {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2\"\n >\n <div class=\"fp-ae-label mb-1\">Selected input</div>\n <div\n class=\"truncate font-mono text-sm text-(--p-text-color)\"\n >\n {{ dataTransformSourceSummary() }}\n </div>\n </div>\n }\n </div>\n <div class=\"flex flex-wrap gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.search-sm\"\n label=\"Preview source\"\n [disabled]=\"!dataTransformSourceExpression()\"\n (onClick)=\"previewDataTransformSourceExpression()\"\n />\n </div>\n </div>\n }\n\n @if (expressionPreview(); as preview) {\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Source preview</div>\n <pre class=\"fp-ae-code\">{{ schemaText(preview) }}</pre>\n </div>\n }\n\n @if (dataTransformIsFilter()) {\n <div class=\"mt-4 grid gap-3\">\n @if (dataTransformUsesStructuredConditionEditor()) {\n <div class=\"fp-ae-label\">Keep item when</div>\n <div class=\"grid gap-3 md:grid-cols-[1fr_180px_1fr]\">\n <mt-text-field\n [ngModel]=\"dataTransformConditionFieldText('left')\"\n (focusin)=\"setExpressionTarget('config:condition:left')\"\n (ngModelChange)=\"\n onDataTransformConditionFieldChange('left', $event)\n \"\n label=\"Left value\"\n hint=\"Field, value, or expression from each item.\"\n [required]=\"isConfigFieldRequired('condition.left')\"\n />\n <mt-select-field\n [ngModel]=\"\n dataTransformConditionFieldValue('operator') || 'equals'\n \"\n (ngModelChange)=\"\n onDataTransformConditionFieldChange('operator', $event)\n \"\n label=\"Operator\"\n [required]=\"isConfigFieldRequired('condition.operator')\"\n [options]=\"ifOperatorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"dataTransformConditionFieldText('right')\"\n (focusin)=\"setExpressionTarget('config:condition:right')\"\n (ngModelChange)=\"\n onDataTransformConditionFieldChange('right', $event)\n \"\n label=\"Right value\"\n hint=\"Value or expression to compare against.\"\n [required]=\"isConfigFieldRequired('condition.right')\"\n />\n </div>\n } @else {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"dataTransformConditionText()\"\n (focusin)=\"setExpressionTarget('config:condition')\"\n (ngModelChange)=\"onDataTransformConditionChange($event)\"\n label=\"Keep item when\"\n hint=\"Condition expression or JSON supplied by the backend catalog.\"\n [required]=\"isConfigFieldRequired('condition')\"\n rows=\"4\"\n />\n }\n </div>\n }\n\n @if (dataTransformIsSort()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['sortBy'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:sortBy')\"\n (ngModelChange)=\"onConfigFieldChange('sortBy', $event)\"\n label=\"Sort by field\"\n hint=\"Field path inside each item, for example createdAt.\"\n [required]=\"isConfigFieldRequired('sortBy')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['sortExpression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:sortExpression')\"\n (ngModelChange)=\"\n onConfigFieldChange('sortExpression', $event)\n \"\n label=\"Custom sort expression\"\n hint=\"Optional expression when a simple field path is not enough.\"\n [required]=\"isConfigFieldRequired('sortExpression')\"\n />\n <mt-select-field\n [ngModel]=\"config()['direction'] ?? 'asc'\"\n (ngModelChange)=\"onConfigFieldChange('direction', $event)\"\n label=\"Direction\"\n [required]=\"isConfigFieldRequired('direction')\"\n [options]=\"dataTransformDirectionOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['valueType'] ?? 'string'\"\n (ngModelChange)=\"onConfigFieldChange('valueType', $event)\"\n label=\"Value type\"\n [required]=\"isConfigFieldRequired('valueType')\"\n [options]=\"dataTransformValueTypeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n }\n\n @if (dataTransformIsLimit()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-number-field\n [ngModel]=\"numberValue(config()['offset']) ?? 0\"\n (ngModelChange)=\"onConfigFieldChange('offset', $event)\"\n label=\"Skip first\"\n [required]=\"isConfigFieldRequired('offset')\"\n [min]=\"0\"\n />\n <mt-number-field\n [ngModel]=\"numberValue(config()['limit'])\"\n (ngModelChange)=\"onConfigFieldChange('limit', $event)\"\n label=\"Take at most\"\n [required]=\"isConfigFieldRequired('limit')\"\n [min]=\"1\"\n />\n </div>\n }\n\n @if (dataTransformIsMapFields()) {\n <div class=\"mt-4 grid gap-3\">\n <mt-select-field\n [ngModel]=\"config()['mode'] ?? 'merge'\"\n (ngModelChange)=\"onConfigFieldChange('mode', $event)\"\n label=\"Field mode\"\n [required]=\"isConfigFieldRequired('mode')\"\n [options]=\"dataTransformMapModeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'fields',\n rows: fieldsRows(),\n keyLabel: 'Output field',\n valueLabel: 'Value or expression',\n addLabel: 'Add mapped field',\n emptyText:\n 'No fields mapped yet. Add an output field to create or update item data.',\n keyHint: 'Name of the field in the output item.',\n valueHint:\n 'Use a fixed value or an expression from each item.',\n required: isConfigFieldRequired('fields'),\n }\n \"\n />\n </div>\n }\n\n @if (dataTransformIsAggregate()) {\n <div class=\"mt-4 grid gap-3\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['groupBy'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:groupBy')\"\n (ngModelChange)=\"onConfigFieldChange('groupBy', $event)\"\n label=\"Group items by\"\n hint=\"Optional field path or expression used to group results.\"\n [required]=\"isConfigFieldRequired('groupBy')\"\n />\n <div class=\"rounded-md border border-surface-200 p-3\">\n <div\n class=\"mb-3 flex flex-wrap items-center justify-between gap-2\"\n >\n <div class=\"fp-ae-label\">Summary fields</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add summary\"\n (onClick)=\"addDataTransformAggregation()\"\n />\n </div>\n <div class=\"space-y-3\">\n @for (\n item of dataTransformAggregationRows();\n track item.index\n ) {\n <div\n class=\"grid gap-2 rounded-md bg-surface-50 p-3 md:grid-cols-[minmax(160px,0.8fr)_minmax(180px,0.9fr)_1fr_minmax(150px,0.7fr)_auto]\"\n >\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"item.key\"\n (ngModelChange)=\"\n updateDataTransformAggregation(\n item.index,\n 'key',\n $event\n )\n \"\n label=\"Output field\"\n hint=\"Name saved in the summary output.\"\n [required]=\"isConfigFieldRequired('aggregations')\"\n />\n <mt-select-field\n [ngModel]=\"item.operation\"\n (ngModelChange)=\"\n updateDataTransformAggregation(\n item.index,\n 'operation',\n $event\n )\n \"\n label=\"Calculation\"\n [required]=\"isConfigFieldRequired('aggregations')\"\n [options]=\"dataTransformAggregationOperationOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"item.field\"\n [readonly]=\"\n !dataTransformAggregationNeedsField(item.operation)\n \"\n (focusin)=\"\n setExpressionTarget(\n 'config:aggregations:' + item.index + ':field'\n )\n \"\n (ngModelChange)=\"\n updateDataTransformAggregation(\n item.index,\n 'field',\n $event\n )\n \"\n label=\"Field or expression\"\n [hint]=\"\n dataTransformAggregationFieldHint(item.operation)\n \"\n [required]=\"\n isConfigFieldRequired('aggregations') &&\n dataTransformAggregationNeedsField(item.operation)\n \"\n />\n <mt-select-field\n [ngModel]=\"item.valueType\"\n (ngModelChange)=\"\n updateDataTransformAggregation(\n item.index,\n 'valueType',\n $event\n )\n \"\n label=\"Value type\"\n [required]=\"isConfigFieldRequired('aggregations')\"\n [options]=\"dataTransformValueTypeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-button\n class=\"self-end\"\n size=\"small\"\n severity=\"secondary\"\n variant=\"outlined\"\n label=\"Remove\"\n (onClick)=\"removeDataTransformAggregation(item.index)\"\n />\n </div>\n } @empty {\n <div\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\n >\n No summary fields yet. Add a summary such as count,\n total, average, minimum, or maximum.\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n @if (dataTransformIsMerge()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"config()['leftExpression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:leftExpression')\"\n (ngModelChange)=\"\n onConfigFieldChange('leftExpression', $event)\n \"\n label=\"First list\"\n hint=\"Expression that resolves to the first list of items.\"\n [required]=\"isConfigFieldRequired('leftExpression')\"\n rows=\"3\"\n />\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"config()['rightExpression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:rightExpression')\"\n (ngModelChange)=\"\n onConfigFieldChange('rightExpression', $event)\n \"\n label=\"Second list\"\n hint=\"Expression that resolves to the second list of items.\"\n [required]=\"isConfigFieldRequired('rightExpression')\"\n rows=\"3\"\n />\n <mt-select-field\n [ngModel]=\"config()['strategy'] ?? 'append'\"\n (ngModelChange)=\"onConfigFieldChange('strategy', $event)\"\n label=\"Merge strategy\"\n [required]=\"isConfigFieldRequired('strategy')\"\n [options]=\"dataTransformMergeStrategyOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (dataTransformUsesKeyedMerge()) {\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['leftKey'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('leftKey', $event)\"\n label=\"First list key\"\n [required]=\"isConfigFieldRequired('leftKey')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['rightKey'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('rightKey', $event)\"\n label=\"Second list key\"\n [required]=\"isConfigFieldRequired('rightKey')\"\n />\n </div>\n }\n </div>\n }\n\n @if (!dataTransformUsesKnownActionEditor()) {\n <div class=\"mt-4 grid gap-3\">\n @for (field of dataTransformGenericFields(); track field.key) {\n @if (field.enumValues.length) {\n <mt-select-field\n [ngModel]=\"config()[field.key] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n [options]=\"enumOptions(field.enumValues)\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n } @else if (field.type === \"number\") {\n <mt-number-field\n [ngModel]=\"numberValue(config()[field.key])\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n } @else {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"fieldText(field.key)\"\n (focusin)=\"setExpressionTarget('config:' + field.key)\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n }\n } @empty {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"dataTransformActionKey()\"\n [readonly]=\"true\"\n label=\"Action\"\n />\n }\n </div>\n }\n\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\n <mt-number-field\n [ngModel]=\"numberValue(config()['maxOutputItems'])\"\n (ngModelChange)=\"onConfigFieldChange('maxOutputItems', $event)\"\n label=\"Output limit\"\n [required]=\"isConfigFieldRequired('maxOutputItems')\"\n [min]=\"1\"\n />\n <div\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3 md:col-span-2\"\n >\n <div class=\"fp-ae-label mb-2\">Output</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (field of dataTransformOutputFieldLabels; track field) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ field }}</span\n >\n }\n </div>\n </div>\n </div>\n </section>\n }\n }\n @case (\"LoopOverItems\") {\n @if (sectionInMain(\"loopOverItems\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Loop over items</div>\n\n <div class=\"grid gap-3\">\n <div class=\"grid gap-3 md:grid-cols-[minmax(220px,0.45fr)_1fr]\">\n <mt-select-field\n [ngModel]=\"loopSourceMode()\"\n (ngModelChange)=\"onLoopSourceModeChange($event)\"\n label=\"List source\"\n hint=\"Choose an output array or type an array expression.\"\n [required]=\"isConfigFieldRequired('itemsExpression')\"\n [options]=\"loopSourceModeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (showLoopItemsExpression()) {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"loopItemsExpression()\"\n (focusin)=\"setExpressionTarget('config:itemsExpression')\"\n (ngModelChange)=\"onLoopItemsExpressionChange($event)\"\n label=\"Array expression\"\n hint=\"Must resolve to an array.\"\n [required]=\"isConfigFieldRequired('itemsExpression')\"\n />\n } @else {\n <mt-select-field\n [ngModel]=\"loopSelectedOutputArray()\"\n (ngModelChange)=\"onLoopOutputArrayChange($event)\"\n label=\"Output array\"\n hint=\"Arrays exposed by previous connected steps.\"\n [required]=\"isConfigFieldRequired('itemsExpression')\"\n [options]=\"loopOutputArrayOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n }\n </div>\n <div class=\"flex flex-wrap gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.search-sm\"\n label=\"Preview array\"\n [disabled]=\"!loopItemsExpression()\"\n (onClick)=\"previewLoopItemsExpression()\"\n />\n </div>\n </div>\n\n @if (expressionPreview(); as preview) {\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Expression preview</div>\n <pre class=\"fp-ae-code\">{{ schemaText(preview) }}</pre>\n </div>\n }\n\n <div class=\"mt-4 border-t border-surface-100 pt-4\">\n <div class=\"fp-ae-label mb-3\">Run settings</div>\n <div class=\"grid gap-3 md:grid-cols-2 xl:grid-cols-4\">\n <mt-number-field\n [ngModel]=\"\n numberValue(\n config()['maxItems'] ?? config()['maxIterations']\n )\n \"\n (ngModelChange)=\"onConfigFieldChange('maxItems', $event)\"\n label=\"Item limit\"\n hint=\"Optional maximum number of items to process.\"\n [required]=\"isConfigFieldRequired('maxItems')\"\n [min]=\"1\"\n />\n <mt-select-field\n [ngModel]=\"loopFailureBehavior()\"\n (ngModelChange)=\"onLoopFailureBehaviorChange($event)\"\n label=\"When an item fails\"\n [required]=\"isConfigFieldRequired('failureBehavior')\"\n [options]=\"loopFailureBehaviorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['emptyBehavior'] ?? 'done'\"\n (ngModelChange)=\"onConfigFieldChange('emptyBehavior', $event)\"\n label=\"When list is empty\"\n [required]=\"isConfigFieldRequired('emptyBehavior')\"\n [options]=\"loopEmptyBehaviorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-toggle-field\n class=\"self-end pb-1\"\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['collectResults'] === true\"\n (ngModelChange)=\"\n onConfigFieldChange('collectResults', $event === true)\n \"\n label=\"Save item results\"\n hint=\"Stores a bounded summary for the done route.\"\n />\n </div>\n </div>\n\n <details class=\"mt-4 border-t border-surface-100 pt-3\">\n <summary\n class=\"cursor-pointer text-sm font-semibold text-(--p-text-color)\"\n >\n Advanced runtime details\n </summary>\n <div class=\"mt-3 grid gap-3 md:grid-cols-3\">\n <div>\n <div class=\"fp-ae-label mb-1\">Current item token</div>\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2 font-mono text-sm text-(--p-text-color)\"\n >\n $item.json\n </div>\n </div>\n <div>\n <div class=\"fp-ae-label mb-1\">Loop context token</div>\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2 font-mono text-sm text-(--p-text-color)\"\n >\n $loop.current\n </div>\n </div>\n <mt-number-field\n [ngModel]=\"numberValue(config()['concurrency']) ?? 1\"\n (ngModelChange)=\"onLoopConcurrencyChange($event)\"\n label=\"Concurrency\"\n hint=\"This deployment supports 1.\"\n [required]=\"isConfigFieldRequired('concurrency')\"\n [min]=\"1\"\n [max]=\"1\"\n />\n </div>\n </details>\n </section>\n }\n }\n @case (\"FlowPlusCommit\") {\n @if (sectionInMain(\"flowplusCommit\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">FlowPlus commit</div>\n <p class=\"fp-ae-copy\">\n This node is the explicit module-data write boundary. Approval\n does not commit data unless this node is reached.\n </p>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['targetModule'] ?? ''\"\n (ngModelChange)=\"onModuleChange($event)\"\n label=\"Module\"\n hint=\"Module whose records will be written by this explicit FlowPlus commit.\"\n [required]=\"isConfigFieldRequired('targetModule')\"\n [options]=\"moduleOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['operation'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('operation', $event)\"\n label=\"Operation\"\n hint=\"Write operation supported by the selected backend module schema.\"\n [required]=\"isConfigFieldRequired('operation')\"\n [options]=\"operationOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <mt-text-field\n class=\"mt-3 font-mono\"\n [ngModel]=\"config()['idempotencyKey'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:idempotencyKey')\"\n (ngModelChange)=\"onConfigFieldChange('idempotencyKey', $event)\"\n label=\"Idempotency key\"\n hint=\"Optional stable key used by the backend to prevent duplicate writes.\"\n [required]=\"isConfigFieldRequired('idempotencyKey')\"\n />\n @if (selectedModuleFields().length) {\n <div class=\"mt-3 space-y-2\">\n <div class=\"fp-ae-section-title\">Module schema</div>\n <div class=\"grid gap-2 md:grid-cols-2\">\n @for (field of selectedModuleFields(); track field.key) {\n <div class=\"fp-ae-kv\">\n <span>{{ field.displayName ?? field.key }}</span>\n <strong\n >{{ field.viewType ?? \"Value\" }}\n @if (field.required) {\n *\n }\n </strong>\n </div>\n }\n </div>\n </div>\n }\n <div class=\"mt-3\">\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'mapping',\n rows: mappingRows(),\n keyLabel: 'Module field',\n valueLabel: 'Expression / value',\n addLabel: 'Add module field',\n required: isConfigFieldRequired('mapping'),\n }\n \"\n />\n </div>\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Validate mapping\"\n (onClick)=\"validateCommitMapping()\"\n />\n </div>\n @if (commitValidation(); as result) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n {{\n result.isValid === false\n ? \"Mapping invalid\"\n : \"Mapping accepted by helper\"\n }}\n @for (issue of resultIssues(result); track $index) {\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\n {{ issue }}\n </div>\n }\n </div>\n }\n @if (sectionInMain(\"credentials\")) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Credential</div>\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\n </div>\n }\n </section>\n }\n }\n @case (\"WebhookResponse\") {\n @if (sectionInMain(\"webhookResponse\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Webhook response</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-number-field\n [ngModel]=\"numberValue(config()['statusCode']) ?? 200\"\n (ngModelChange)=\"onConfigFieldChange('statusCode', $event)\"\n label=\"Status code\"\n hint=\"HTTP status code sent back by the webhook response node.\"\n [required]=\"isConfigFieldRequired('statusCode')\"\n [min]=\"100\"\n [max]=\"599\"\n />\n <mt-select-field\n [ngModel]=\"config()['responseMode'] ?? 'json'\"\n (ngModelChange)=\"onConfigFieldChange('responseMode', $event)\"\n label=\"Response mode\"\n hint=\"How the webhook response body should be serialized.\"\n [required]=\"isConfigFieldRequired('responseMode')\"\n [options]=\"responseModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"mt-3\">\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'headers',\n rows: headerRows(),\n keyLabel: 'Header',\n valueLabel: 'Value',\n addLabel: 'Add header',\n }\n \"\n />\n </div>\n <mt-textarea-field\n class=\"mt-3 w-full font-mono\"\n [ngModel]=\"valueText(config()['body'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:body')\"\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\n label=\"Body\"\n hint=\"Body returned to the webhook caller. Expressions can use current execution data.\"\n [required]=\"isConfigFieldRequired('body')\"\n rows=\"6\"\n />\n </section>\n }\n }\n @case (\"CallAutomation\") {\n @if (sectionInMain(\"callAutomation\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Subworkflow</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['targetAutomationId'] ?? ''\"\n (ngModelChange)=\"onSubworkflowTargetChange($event)\"\n label=\"Target automation\"\n hint=\"Backend-provided automation candidate. Tenant and recursion validation stay on the backend.\"\n [required]=\"isConfigFieldRequired('targetAutomationId')\"\n [options]=\"subworkflowAutomationOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['revisionMode'] ?? 'Active'\"\n (ngModelChange)=\"onConfigFieldChange('revisionMode', $event)\"\n label=\"Revision mode\"\n hint=\"Choose which published or active child revision to call.\"\n [required]=\"isConfigFieldRequired('revisionMode')\"\n [options]=\"revisionModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (config()[\"revisionMode\"] === \"SpecificRevision\") {\n <mt-text-field\n [ngModel]=\"config()['specificRevisionId'] ?? ''\"\n (ngModelChange)=\"\n onConfigFieldChange('specificRevisionId', $event)\n \"\n label=\"Specific revision id\"\n hint=\"Required only when revision mode is Specific revision.\"\n [required]=\"isConfigFieldRequired('specificRevisionId')\"\n />\n }\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['waitForCompletion'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('waitForCompletion', $event === true)\n \"\n label=\"Wait for completion\"\n hint=\"When disabled, the parent continues after dispatch and backend records the child linkage.\"\n />\n </div>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['inputMappingJson'] ?? '{}')\"\n (focusin)=\"setExpressionTarget('config:inputMappingJson')\"\n (ngModelChange)=\"\n onConfigFieldChange('inputMappingJson', $event)\n \"\n label=\"Input mapping JSON\"\n hint=\"JSON object or expression map sent into the child automation input.\"\n [required]=\"isConfigFieldRequired('inputMappingJson')\"\n rows=\"7\"\n />\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['outputMappingJson'] ?? '{}')\"\n (focusin)=\"setExpressionTarget('config:outputMappingJson')\"\n (ngModelChange)=\"\n onConfigFieldChange('outputMappingJson', $event)\n \"\n label=\"Output mapping JSON\"\n hint=\"JSON object or expression map for child output/status returned to the parent.\"\n [required]=\"isConfigFieldRequired('outputMappingJson')\"\n rows=\"7\"\n />\n </div>\n </section>\n }\n }\n @case (\"Subworkflow\") {\n @if (sectionInMain(\"callAutomation\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Subworkflow</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['targetAutomationId'] ?? ''\"\n (ngModelChange)=\"onSubworkflowTargetChange($event)\"\n label=\"Target automation\"\n hint=\"Backend-provided automation candidate. Tenant and recursion validation stay on the backend.\"\n [required]=\"isConfigFieldRequired('targetAutomationId')\"\n [options]=\"subworkflowAutomationOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['revisionMode'] ?? 'Active'\"\n (ngModelChange)=\"onConfigFieldChange('revisionMode', $event)\"\n label=\"Revision mode\"\n hint=\"Choose which published or active child revision to call.\"\n [required]=\"isConfigFieldRequired('revisionMode')\"\n [options]=\"revisionModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (config()[\"revisionMode\"] === \"SpecificRevision\") {\n <mt-text-field\n [ngModel]=\"config()['specificRevisionId'] ?? ''\"\n (ngModelChange)=\"\n onConfigFieldChange('specificRevisionId', $event)\n \"\n label=\"Specific revision id\"\n hint=\"Required only when revision mode is Specific revision.\"\n [required]=\"isConfigFieldRequired('specificRevisionId')\"\n />\n }\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['waitForCompletion'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('waitForCompletion', $event === true)\n \"\n label=\"Wait for completion\"\n hint=\"When disabled, the parent continues after dispatch and backend records the child linkage.\"\n />\n </div>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['inputMappingJson'] ?? '{}')\"\n (focusin)=\"setExpressionTarget('config:inputMappingJson')\"\n (ngModelChange)=\"\n onConfigFieldChange('inputMappingJson', $event)\n \"\n label=\"Input mapping JSON\"\n hint=\"JSON object or expression map sent into the child automation input.\"\n [required]=\"isConfigFieldRequired('inputMappingJson')\"\n rows=\"7\"\n />\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['outputMappingJson'] ?? '{}')\"\n (focusin)=\"setExpressionTarget('config:outputMappingJson')\"\n (ngModelChange)=\"\n onConfigFieldChange('outputMappingJson', $event)\n \"\n label=\"Output mapping JSON\"\n hint=\"JSON object or expression map for child output/status returned to the parent.\"\n [required]=\"isConfigFieldRequired('outputMappingJson')\"\n rows=\"7\"\n />\n </div>\n </section>\n }\n }\n @case (\"ParallelStart\") {\n @if (sectionInMain(\"parallelStart\")) {\n <section class=\"fp-ae-panel\">\n <div\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-50 px-4 py-3\"\n >\n <div class=\"fp-ae-section-title !m-0\">Parallel start</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add branch\"\n (onClick)=\"addParallelBranch()\"\n />\n </div>\n <div class=\"space-y-3 p-4\">\n @for (\n branch of parallelBranches();\n track branch.key;\n let i = $index\n ) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"grid gap-3 md:grid-cols-[160px_1fr_1fr_auto]\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"branch.key\"\n (ngModelChange)=\"updateParallelBranch(i, 'key', $event)\"\n label=\"Branch key\"\n hint=\"Persisted route key. Do not use array index names.\"\n [required]=\"isConfigFieldRequired('branches')\"\n />\n <mt-text-field\n [ngModel]=\"branch.label\"\n (ngModelChange)=\"updateParallelBranch(i, 'label', $event)\"\n label=\"Label\"\n hint=\"Visual branch label. Changing it does not change existing routes.\"\n [required]=\"isConfigFieldRequired('branches')\"\n />\n <mt-text-field\n [ngModel]=\"branch.description\"\n (ngModelChange)=\"\n updateParallelBranch(i, 'description', $event)\n \"\n label=\"Description\"\n hint=\"Optional branch note for reviewers.\"\n [required]=\"false\"\n />\n <div class=\"flex items-end gap-1\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"arrow.arrow-up\"\n tooltip=\"Move up\"\n (onClick)=\"moveParallelBranch(i, -1)\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"arrow.arrow-down\"\n tooltip=\"Move down\"\n (onClick)=\"moveParallelBranch(i, 1)\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"danger\"\n icon=\"general.trash-01\"\n tooltip=\"Remove branch\"\n (onClick)=\"removeParallelBranch(i)\"\n />\n </div>\n </div>\n <div\n class=\"mt-3 flex flex-wrap items-center gap-1.5 text-[11px]\"\n >\n <span\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-50 px-2 font-mono font-semibold text-(--p-text-color)\"\n >{{ branch.key }}</span\n >\n <span\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 font-medium text-(--p-text-muted-color)\"\n >{{ branch.routeCount }} connected route{{\n branch.routeCount === 1 ? \"\" : \"s\"\n }}</span\n >\n </div>\n </div>\n }\n @if (parallelBranches().length === 0) {\n <p class=\"fp-ae-copy\">\n Add at least two stable branch keys. Backend validation blocks\n publish until branches are valid.\n </p>\n }\n </div>\n </section>\n }\n }\n @case (\"ParallelJoin\") {\n @if (sectionInMain(\"parallelJoin\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Parallel join</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['joinPolicy'] ?? 'All'\"\n (ngModelChange)=\"onConfigFieldChange('joinPolicy', $event)\"\n label=\"Join policy\"\n hint=\"Backend policy used to decide when branch wait is complete.\"\n [required]=\"isConfigFieldRequired('joinPolicy')\"\n [options]=\"joinPolicyOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (config()[\"joinPolicy\"] === \"Threshold\") {\n <mt-number-field\n [ngModel]=\"numberValue(config()['threshold'])\"\n (ngModelChange)=\"onConfigFieldChange('threshold', $event)\"\n label=\"Threshold\"\n hint=\"Minimum completed branch count required for Threshold policy.\"\n [required]=\"isConfigFieldRequired('threshold')\"\n [min]=\"1\"\n />\n }\n <mt-select-field\n [ngModel]=\"config()['aggregationStrategy'] ?? 'MergeObjects'\"\n (ngModelChange)=\"\n onConfigFieldChange('aggregationStrategy', $event)\n \"\n label=\"Aggregation strategy\"\n hint=\"How backend joins branch outputs into the join node output.\"\n [required]=\"isConfigFieldRequired('aggregationStrategy')\"\n [options]=\"aggregationStrategyOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['outputTargetPath'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:outputTargetPath')\"\n (ngModelChange)=\"\n onConfigFieldChange('outputTargetPath', $event)\n \"\n label=\"Output target path\"\n hint=\"Optional context path where joined output should be written.\"\n [required]=\"isConfigFieldRequired('outputTargetPath')\"\n />\n </div>\n </section>\n }\n }\n @case (\"Switch\") {\n @if (sectionInMain(\"switch\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Switch</div>\n <div class=\"grid gap-4\">\n <div\n class=\"grid items-end gap-3 md:grid-cols-[minmax(0,1fr)_minmax(280px,0.7fr)]\"\n >\n <mt-select-field\n [ngModel]=\"config()['mode'] ?? 'value'\"\n (ngModelChange)=\"onConfigFieldChange('mode', $event)\"\n label=\"Mode\"\n hint=\"Backend evaluation mode: expression, rules, or source-value matching.\"\n [required]=\"isConfigFieldRequired('mode')\"\n [options]=\"switchModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-toggle-field\n class=\"self-end pb-1\"\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['firstMatch'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('firstMatch', $event === true)\n \"\n label=\"First match\"\n hint=\"Use the first matching case unless backend explicitly supports multi-match.\"\n />\n </div>\n @if (showSwitchSourceValue() || showSwitchExpression()) {\n <div class=\"grid gap-3 md:grid-cols-2\">\n @if (showSwitchSourceValue()) {\n <mt-text-field\n [class]=\"\n showSwitchExpression()\n ? 'font-mono'\n : 'font-mono md:col-span-2'\n \"\n [ngModel]=\"config()['sourceValue'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:sourceValue')\"\n (ngModelChange)=\"\n onConfigFieldChange('sourceValue', $event)\n \"\n label=\"Source value\"\n hint=\"Value or expression used for value matching.\"\n [required]=\"isConfigFieldRequired('sourceValue')\"\n />\n }\n @if (showSwitchExpression()) {\n <mt-text-field\n [class]=\"\n showSwitchSourceValue()\n ? 'font-mono'\n : 'font-mono md:col-span-2'\n \"\n [ngModel]=\"config()['expression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:expression')\"\n (ngModelChange)=\"\n onConfigFieldChange('expression', $event)\n \"\n label=\"Expression\"\n hint=\"Expression evaluated when mode is expression or rules.\"\n [required]=\"isConfigFieldRequired('expression')\"\n />\n }\n </div>\n }\n <div\n class=\"grid items-end gap-3 md:grid-cols-[minmax(0,1fr)_minmax(280px,0.7fr)]\"\n >\n @if (showSwitchDefaultOutputKey()) {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['defaultOutputKey'] ?? 'default'\"\n (ngModelChange)=\"\n onConfigFieldChange('defaultOutputKey', $event)\n \"\n label=\"Default output key\"\n hint=\"Stable default route key used when no case matches.\"\n [required]=\"isConfigFieldRequired('defaultOutputKey')\"\n />\n }\n <mt-toggle-field\n [class]=\"\n showSwitchDefaultOutputKey()\n ? 'self-end pb-1'\n : 'self-end pb-1 md:col-span-2'\n \"\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['includeDefaultOutput'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('includeDefaultOutput', $event === true)\n \"\n label=\"Include default output\"\n hint=\"Expose the default route output key in the canvas.\"\n />\n </div>\n </div>\n </section>\n <section class=\"fp-ae-panel\">\n <div\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-50 px-4 py-3\"\n >\n <div class=\"fp-ae-section-title !m-0\">Switch cases</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add case\"\n (onClick)=\"addSwitchCase()\"\n />\n </div>\n <div class=\"space-y-3 p-4\">\n @for (item of switchCases(); track item.key; let i = $index) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div\n class=\"flex flex-wrap items-start justify-between gap-3 border-b border-surface-100 pb-3\"\n >\n <div class=\"min-w-0\">\n <div class=\"flex flex-wrap items-center gap-1.5\">\n <span\n class=\"text-sm font-semibold text-(--p-text-color)\"\n >Case {{ i + 1 }}</span\n >\n <span\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-50 px-2 font-mono text-[11px] font-semibold text-(--p-text-color)\"\n >{{ item.routeKey }}</span\n >\n <span\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 text-[11px] font-medium text-(--p-text-muted-color)\"\n >{{ item.routeCount }} connected route{{\n item.routeCount === 1 ? \"\" : \"s\"\n }}</span\n >\n </div>\n <div\n class=\"mt-1 truncate text-xs font-medium text-(--p-text-muted-color)\"\n >\n {{ item.label || item.routeKey }}\n </div>\n </div>\n <div class=\"flex shrink-0 items-center gap-1\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"arrow.arrow-up\"\n tooltip=\"Move up\"\n (onClick)=\"moveSwitchCase(i, -1)\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"arrow.arrow-down\"\n tooltip=\"Move down\"\n (onClick)=\"moveSwitchCase(i, 1)\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"danger\"\n icon=\"general.trash-01\"\n tooltip=\"Remove case\"\n (onClick)=\"removeSwitchCase(i)\"\n />\n </div>\n </div>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"item.label\"\n (ngModelChange)=\"updateSwitchCase(i, 'label', $event)\"\n label=\"Case name\"\n hint=\"Shown in route labels.\"\n [required]=\"isConfigFieldRequired('cases')\"\n />\n @if (showSwitchCaseValue()) {\n <mt-text-field\n [ngModel]=\"item.value\"\n (focusin)=\"\n setExpressionTarget(\n 'config:cases:' + item.key + ':value'\n )\n \"\n (ngModelChange)=\"updateSwitchCase(i, 'value', $event)\"\n label=\"Match value\"\n hint=\"Compared with the source value.\"\n [required]=\"isConfigFieldRequired('cases')\"\n />\n }\n @if (showSwitchCaseCondition()) {\n <mt-text-field\n class=\"font-mono md:col-span-2\"\n [ngModel]=\"item.condition\"\n (focusin)=\"\n setExpressionTarget(\n 'config:cases:' + item.key + ':condition'\n )\n \"\n (ngModelChange)=\"\n updateSwitchCase(i, 'condition', $event)\n \"\n label=\"Rule condition\"\n hint=\"When true, this case is selected.\"\n [required]=\"isConfigFieldRequired('cases')\"\n />\n }\n </div>\n <details\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-xs text-(--p-text-muted-color)\"\n >\n <summary class=\"cursor-pointer font-medium\">\n Advanced route settings\n </summary>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"item.key\"\n (ngModelChange)=\"updateSwitchCase(i, 'key', $event)\"\n label=\"Route key\"\n hint=\"Stable output key for connected routes.\"\n [required]=\"isConfigFieldRequired('cases')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"item.expression\"\n (focusin)=\"\n setExpressionTarget(\n 'config:cases:' + item.key + ':expression'\n )\n \"\n (ngModelChange)=\"\n updateSwitchCase(i, 'expression', $event)\n \"\n label=\"Advanced condition expression\"\n hint=\"Optional true or false expression for this case.\"\n [required]=\"false\"\n />\n </div>\n <div\n class=\"mt-3 inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 font-medium\"\n >\n Visual order {{ i + 1 }}\n </div>\n </details>\n </div>\n }\n @if (switchCases().length === 0) {\n <p class=\"fp-ae-copy\">\n Add a case for each route this switch can select.\n </p>\n }\n </div>\n </section>\n }\n }\n @case (\"Stop\") {\n @if (sectionInMain(\"stop\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">End execution</div>\n @if (\n supportsConfigKey(\"status\") ||\n supportsConfigKey(\"message\") ||\n supportsConfigKey(\"output\")\n ) {\n <div class=\"grid gap-3 md:grid-cols-2\">\n @if (supportsConfigKey(\"status\")) {\n <mt-text-field\n [ngModel]=\"config()['status'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('status', $event)\"\n label=\"Result status\"\n hint=\"Terminal status emitted by the backend stop node.\"\n />\n }\n @if (supportsConfigKey(\"message\")) {\n <mt-text-field\n [ngModel]=\"config()['message'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message')\"\n (ngModelChange)=\"onConfigFieldChange('message', $event)\"\n label=\"Message\"\n hint=\"Optional message or reason saved with the terminal result.\"\n />\n }\n @if (supportsConfigKey(\"output\")) {\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['output'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:output')\"\n (ngModelChange)=\"onConfigFieldChange('output', $event)\"\n label=\"Output payload\"\n hint=\"Terminal output payload when exposed by the backend schema.\"\n rows=\"5\"\n />\n }\n </div>\n } @else {\n <p class=\"fp-ae-copy\">\n Stop nodes use their backend defaults and do not expose extra\n configuration.\n </p>\n }\n </section>\n }\n }\n }\n\n @if (sectionInAdvanced(\"policy\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">\n Retry, timeout, and failure handling\n </div>\n <div class=\"grid gap-3 md:grid-cols-3\">\n <mt-number-field\n [ngModel]=\"policyNumberValue('timeoutPolicyJson', 'timeoutSeconds')\"\n (ngModelChange)=\"\n updatePolicyNumber('timeoutPolicyJson', 'timeoutSeconds', $event)\n \"\n label=\"Step timeout (seconds)\"\n hint=\"How long this step may run before Automation Engine marks it timed out.\"\n [min]=\"1\"\n />\n <mt-number-field\n [ngModel]=\"policyNumberValue('retryPolicyJson', 'maxAttempts')\"\n (ngModelChange)=\"\n updatePolicyNumber('retryPolicyJson', 'maxAttempts', $event)\n \"\n label=\"Max attempts\"\n hint=\"Total tries including the first run. Use 1 for no retry.\"\n [min]=\"1\"\n />\n <mt-select-field\n [ngModel]=\"errorPolicyMode()\"\n (ngModelChange)=\"updateErrorPolicyMode($event)\"\n label=\"When this step fails\"\n hint=\"Choose whether failures pause for recovery, use the failure path, or fail the workflow.\"\n [options]=\"errorPolicyOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n @if (sectionHasAdvancedEmptyState(\"policy\")) {\n <p class=\"fp-ae-copy mt-3\">\n Backend defaults apply until you override a timeout, retry count, or\n failure rule here.\n </p>\n }\n </section>\n }\n\n @if (sectionInAdvanced(\"credentials\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Auth and credentials</div>\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\n </section>\n }\n\n @if (sectionInAdvanced(\"mapping\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Custom data handoff</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <div>\n <div class=\"fp-ae-label mb-2\">Inputs sent to this step</div>\n @for (row of inputMappingRows(); track row.key) {\n <label class=\"mb-2 block space-y-1\">\n <span class=\"text-[11px] text-(--p-text-muted-color)\">{{\n row.key\n }}</span>\n <mt-text-field\n [ngModel]=\"valueText(row.value)\"\n (focusin)=\"\n setExpressionTarget('config:inputMapping:' + row.key)\n \"\n (ngModelChange)=\"\n updateJsonField('inputMappingJson', row.key, $event)\n \"\n hint=\"Value this step receives from the workflow context.\"\n />\n </label>\n }\n </div>\n <div>\n <div class=\"fp-ae-label mb-2\">Outputs saved for next steps</div>\n @for (row of outputMappingRows(); track row.key) {\n <label class=\"mb-2 block space-y-1\">\n <span class=\"text-[11px] text-(--p-text-muted-color)\">{{\n row.key\n }}</span>\n <mt-text-field\n [ngModel]=\"valueText(row.value)\"\n (focusin)=\"\n setExpressionTarget('config:outputMapping:' + row.key)\n \"\n (ngModelChange)=\"\n updateJsonField('outputMappingJson', row.key, $event)\n \"\n hint=\"Value this step exposes to the rest of the workflow.\"\n />\n </label>\n }\n </div>\n </div>\n @if (\n sectionHasAdvancedEmptyState(\"mapping\") &&\n inputMappingRows().length === 0 &&\n outputMappingRows().length === 0\n ) {\n <p class=\"fp-ae-copy mt-3\">\n Default handoff is active. This step receives the current workflow\n item and exposes its normal output to the next connected step. Add a\n custom handoff only when the business data must be reshaped.\n </p>\n }\n </section>\n }\n\n @if (sectionInAdvanced(\"triggerContext\") && trigger()) {\n <details class=\"fp-ae-panel\" open>\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\n Trigger payload and context\n </summary>\n <p class=\"fp-ae-copy mt-3\">\n Trigger payload/context is documentation for expressions only.\n Triggers do not edit node input or output mappings here.\n </p>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\n Config schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(configSchema()) }}</pre>\n </details>\n @if (hasTriggerPayloadSchema() && triggerPayloadSchema(); as schema) {\n <details\n class=\"rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Payload schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(schema) }}</pre>\n </details>\n }\n @if (authPolicySchema()) {\n <details\n class=\"rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Auth policy schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(authPolicySchema()) }}</pre>\n </details>\n }\n @if (triggerPayloadSample(); as sample) {\n <details\n class=\"rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Payload or request sample\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(sample) }}</pre>\n </details>\n }\n </div>\n </details>\n }\n\n @if (sectionInAdvanced(\"backendSchema\") && step()) {\n <details class=\"fp-ae-panel\" open>\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\n Backend schemas and outputs\n </summary>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\n Config schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(configSchema()) }}</pre>\n </details>\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\n Input schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(inputSchema()) }}</pre>\n </details>\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\n Output schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(outputSchema()) }}</pre>\n </details>\n <div class=\"rounded-lg border border-(--p-content-border-color) p-3\">\n <div class=\"fp-ae-label mb-2\">Route output keys</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (key of routeOutputKeys(); track key) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ key }}</span\n >\n }\n @if (routeOutputKeys().length === 0) {\n <span class=\"text-[12px] text-(--p-text-muted-color)\"\n >No outgoing route keys</span\n >\n }\n </div>\n </div>\n </div>\n </details>\n }\n\n @if (sectionInAdvanced(\"schemaFields\") && configRows().length > 0) {\n <details class=\"fp-ae-panel\" open>\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\n Backend schema fields\n </summary>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n @for (field of configRows(); track field.key) {\n <div\n class=\"space-y-1.5\"\n [class.md:col-span-2]=\"\n field.type === 'object' || field.type === 'array'\n \"\n >\n @if (field.enumValues.length) {\n <mt-select-field\n [ngModel]=\"fieldValue(field.key)\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n [options]=\"enumOptions(field.enumValues)\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n } @else if (field.type === \"boolean\") {\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"!!fieldValue(field.key)\"\n (ngModelChange)=\"\n onConfigFieldChange(field.key, $event === true)\n \"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n } @else if (field.type === \"number\") {\n <mt-number-field\n [ngModel]=\"numberValue(fieldValue(field.key))\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n } @else if (field.type === \"date\") {\n <mt-date-field\n [ngModel]=\"fieldValue(field.key)\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n [showTime]=\"field.format !== 'date'\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n } @else if (field.type === \"object\" || field.type === \"array\") {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"valueText(fieldValue(field.key))\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n rows=\"5\"\n />\n } @else {\n <mt-text-field\n [ngModel]=\"fieldText(field.key)\"\n (focusin)=\"\n field.expressionEnabled &&\n setExpressionTarget('config:' + field.key)\n \"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n }\n </div>\n }\n </div>\n </details>\n }\n </div>\n</div>\n\n<ng-template #credentialSelector>\n @if (activeCredentialProvider(); as provider) {\n <div\n class=\"mb-4 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-4 shadow-[0_1px_2px_rgba(15,23,42,0.05)]\"\n >\n <div class=\"mb-2 text-[12px] font-bold text-(--p-text-color)\">\n Credential\n </div>\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (provider.connectAvailable) {\n <button\n type=\"button\"\n class=\"inline-flex min-h-10 max-w-full items-center gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2 text-[13px] font-bold text-(--p-text-color) shadow-sm transition-[border-color,box-shadow,background] hover:border-(--p-primary-color) hover:shadow-[0_2px_7px_rgba(15,23,42,0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n [disabled]=\"credentialOauthState() === 'loading'\"\n [attr.aria-busy]=\"\n credentialOauthState() === 'loading' ? 'true' : null\n \"\n (click)=\"connectCredentialProvider()\"\n >\n <fp-automation-node-icon\n class=\"text-[22px]\"\n [itemKey]=\"provider.providerKey\"\n [displayName]=\"provider.displayName\"\n [metadata]=\"credentialProviderIconMetadata(provider)\"\n />\n <span class=\"truncate\">{{ credentialConnectLabel() }}</span>\n </button>\n @if (provider.manualSetupAvailable) {\n <span class=\"text-[12px] text-(--p-text-muted-color)\">or</span>\n <button\n type=\"button\"\n class=\"rounded-md px-1 py-1 text-[12px] font-semibold text-(--p-text-color) underline decoration-(--p-text-muted-color) underline-offset-4 transition-colors hover:text-(--p-primary-color) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n (click)=\"showManualCredentialSetup()\"\n >\n setup manually\n </button>\n }\n } @else if (provider.manualSetupAvailable) {\n <button\n type=\"button\"\n class=\"inline-flex min-h-10 max-w-full items-center gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2 text-[13px] font-bold text-(--p-text-color) shadow-sm transition-[border-color,box-shadow,background] hover:border-(--p-primary-color) hover:shadow-[0_2px_7px_rgba(15,23,42,0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n (click)=\"showManualCredentialSetup()\"\n >\n <fp-automation-node-icon\n class=\"text-[22px]\"\n [itemKey]=\"provider.providerKey\"\n [displayName]=\"provider.displayName\"\n [metadata]=\"credentialProviderIconMetadata(provider)\"\n />\n <span class=\"truncate\"\n >Set up\n {{ provider.displayName || humanReviewProviderLabel() }}\n credential</span\n >\n </button>\n }\n </div>\n <div\n class=\"mt-2 flex flex-wrap items-center gap-1.5 text-[11px] text-(--p-text-muted-color)\"\n >\n @if (provider.authModes?.length) {\n @for (mode of provider.authModes; track mode) {\n <span\n class=\"rounded-full bg-(--p-surface-100) px-2 py-0.5 font-semibold\"\n >\n {{ mode }}\n </span>\n }\n }\n @if (provider.requiredScopes?.length) {\n <span class=\"truncate\">\n Scopes: {{ provider.requiredScopes.join(\", \") }}\n </span>\n }\n </div>\n @if (provider.setupInstructions) {\n <p class=\"m-0 mt-2 text-[11.5px] leading-5 text-(--p-text-muted-color)\">\n {{ provider.setupInstructions }}\n </p>\n }\n </div>\n }\n\n @if (manualCredentialOpen()) {\n <div\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\n >\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"manualCredentialDisplayName()\"\n (ngModelChange)=\"onManualCredentialDisplayNameChange($event)\"\n label=\"Credential name\"\n />\n <mt-select-field\n [ngModel]=\"manualCredentialType()\"\n (ngModelChange)=\"onManualCredentialTypeChange($event)\"\n label=\"Credential type\"\n [options]=\"credentialTypeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n @for (field of manualCredentialFields(); track field.key) {\n @if (manualCredentialFieldIsToggle(field)) {\n <mt-toggle-field\n [ngModel]=\"manualCredentialFieldValue(field.key) === 'true'\"\n (ngModelChange)=\"onManualCredentialFieldChange(field.key, $event)\"\n [label]=\"\n manualCredentialFieldLabel(field) + (field.required ? ' *' : '')\n \"\n size=\"small\"\n />\n } @else {\n <mt-text-field\n [ngModel]=\"manualCredentialFieldValue(field.key)\"\n (ngModelChange)=\"onManualCredentialFieldChange(field.key, $event)\"\n [label]=\"\n manualCredentialFieldLabel(field) + (field.required ? ' *' : '')\n \"\n [placeholder]=\"field.placeholder ?? ''\"\n [type]=\"manualCredentialFieldInputType(field)\"\n />\n }\n }\n </div>\n @if (credentialManualError(); as error) {\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ error }}\n </div>\n }\n <div class=\"mt-3 flex justify-end gap-2\">\n <mt-button\n size=\"small\"\n variant=\"text\"\n label=\"Cancel\"\n (onClick)=\"cancelManualCredentialSetup()\"\n />\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Save credential\"\n [loading]=\"credentialManualState() === 'loading'\"\n [disabled]=\"credentialManualState() === 'loading'\"\n (onClick)=\"createManualCredential()\"\n />\n </div>\n </div>\n }\n\n @if (credentialOptions().length) {\n <div class=\"space-y-2\">\n <mt-select-field\n [ngModel]=\"selectedCredentialRef()\"\n (ngModelChange)=\"selectCredential($event)\"\n label=\"Saved credential\"\n [options]=\"credentialOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n [showClear]=\"true\"\n />\n @for (credential of credentials(); track credential.credentialRef) {\n @if (credential.credentialRef === selectedCredentialRef()) {\n <div\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2\"\n >\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"truncate text-[12px] font-semibold\">\n {{ credential.displayName ?? credential.credentialRef }}\n </div>\n <div\n class=\"truncate font-mono text-[11px] text-(--p-text-muted-color)\"\n >\n {{ credential.credentialRef }} /\n {{\n credential.status ??\n (credential.resolved ? \"Resolved\" : \"Unresolved\")\n }}\n </div>\n </div>\n <div class=\"flex flex-none items-center gap-2\">\n <mt-button\n size=\"small\"\n variant=\"text\"\n label=\"Test\"\n [loading]=\"\n credentialTestingRef() === credential.credentialRef\n \"\n [disabled]=\"\n credentialTestingRef() === credential.credentialRef\n \"\n (onClick)=\"testCredential(credential.credentialRef)\"\n />\n @if (activeCredentialProvider()?.connectAvailable) {\n <mt-button\n size=\"small\"\n variant=\"text\"\n label=\"Reconnect\"\n [disabled]=\"credentialOauthState() === 'loading'\"\n (onClick)=\"connectCredentialProvider()\"\n />\n }\n <mt-button\n size=\"small\"\n variant=\"text\"\n severity=\"danger\"\n label=\"Revoke\"\n [loading]=\"credentialRevokeRef() === credential.credentialRef\"\n [disabled]=\"\n credentialRevokeRef() === credential.credentialRef\n \"\n (onClick)=\"revokeCredential(credential.credentialRef)\"\n />\n </div>\n </div>\n @if (credential.maskedFields; as maskedFields) {\n <div\n class=\"mt-2 flex flex-wrap gap-1.5 text-[11px] text-(--p-text-muted-color)\"\n >\n @for (field of maskedFields | keyvalue; track field.key) {\n <span\n class=\"rounded-md bg-(--p-content-background) px-2 py-1 font-mono\"\n >\n {{ field.key }}: {{ field.value }}\n </span>\n }\n </div>\n }\n </div>\n }\n }\n </div>\n } @else if (sectionHasAdvancedEmptyState(\"credentials\")) {\n <p class=\"fp-ae-copy\">\n No saved credential exists for this provider. Connect the provider or set\n it up manually before publishing.\n </p>\n }\n @if (credentialTest(); as test) {\n <div class=\"mt-3 rounded-md bg-(--p-surface-100) px-3 py-2 text-[12px]\">\n {{ test.status ?? (test.succeeded ? \"Succeeded\" : \"Failed\") }}\n @if (test.message) {\n <span>- {{ test.message }}</span>\n }\n </div>\n }\n</ng-template>\n\n<ng-template\n #mapEditor\n let-objectKey=\"objectKey\"\n let-rows=\"rows\"\n let-keyLabel=\"keyLabel\"\n let-valueLabel=\"valueLabel\"\n let-addLabel=\"addLabel\"\n let-emptyText=\"emptyText\"\n let-keyHint=\"keyHint\"\n let-valueHint=\"valueHint\"\n let-required=\"required\"\n>\n <div class=\"space-y-2\">\n @for (row of rows; track row.key) {\n <div class=\"grid gap-2 md:grid-cols-[180px_1fr_auto]\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"row.key\"\n (ngModelChange)=\"\n updateObjectRow(objectKey, row.key, $event, row.value)\n \"\n [label]=\"keyLabel\"\n [hint]=\"\n keyHint || 'Configuration key saved to the backend JSON object.'\n \"\n [required]=\"required === true\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"valueText(row.value)\"\n (focusin)=\"setExpressionTarget('config:' + objectKey + ':' + row.key)\"\n (ngModelChange)=\"updateObjectRow(objectKey, row.key, row.key, $event)\"\n [label]=\"valueLabel\"\n [hint]=\"\n valueHint ||\n 'Configuration value. Use expressions when this field should resolve at runtime.'\n \"\n [required]=\"required === true\"\n />\n <mt-button\n class=\"self-end\"\n size=\"small\"\n severity=\"secondary\"\n variant=\"outlined\"\n label=\"Remove\"\n (onClick)=\"removeObjectRow(objectKey, row.key)\"\n />\n </div>\n } @empty {\n @if (emptyText) {\n <div\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\n >\n {{ emptyText }}\n </div>\n }\n }\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n [label]=\"addLabel || 'Add ' + keyLabel\"\n (onClick)=\"addObjectRow(objectKey)\"\n />\n </div>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DateField, selector: "mt-date-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "showIcon", "showClear", "showTime", "pInputs", "required"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "component", type: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { kind: "component", type: NumberField, selector: "mt-number-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "pInputs", "format", "useGrouping", "maxFractionDigits", "min", "max", "required"] }, { kind: "component", type: RadioCardsField, selector: "mt-radio-cards-field", inputs: ["circle", "label", "hint", "readonly", "required", "color", "size", "optionLabel", "optionValue", "options"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "directive", type: DropDataDirective, selector: "[fpDropData]", inputs: ["fpDropAutoInsert"], outputs: ["dataDrop", "dataDropEvent"] }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }] });
|
|
20289
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: AutomationSmartEditorComponent, isStandalone: true, selector: "fp-automation-smart-editor", inputs: { step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block h-full min-h-0" }, ngImport: i0, template: "<div\r\n class=\"fp-scroll flex h-full min-h-0 flex-col overflow-y-auto\"\r\n fpDropData\r\n [fpDropAutoInsert]=\"true\"\r\n (dataDropEvent)=\"insertExpressionDrop($event)\"\r\n>\r\n <div class=\"space-y-4 px-5 py-5\">\r\n @if (helperError()) {\r\n <div\r\n class=\"rounded-lg border border-[rgb(var(--fp-warning))]/30 bg-[rgb(var(--fp-warning))]/10 px-3 py-2 text-[12px] leading-5 text-(--p-text-color)\"\r\n >\r\n {{ helperError() }}\r\n </div>\r\n }\r\n\r\n @if (sectionInMain(\"startConnection\") && trigger()) {\r\n <section\r\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Start connection\r\n </h3>\r\n <div class=\"space-y-3 p-4\">\r\n @if (startConnection().key) {\r\n @if (startConnection().step) {\r\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\r\n <div class=\"min-w-0 space-y-1\">\r\n <div\r\n class=\"text-[12px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n First node connected\r\n </div>\r\n <div\r\n class=\"truncate text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ startConnection().label }}\r\n </div>\r\n <div\r\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n <span>Managed on canvas</span>\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ startConnection().key }}\r\n </span>\r\n </div>\r\n </div>\r\n <div class=\"flex shrink-0 flex-wrap gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n label=\"Focus connected node\"\r\n (onClick)=\"focusStartConnection()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"space-y-2\">\r\n <div class=\"text-[14px] font-semibold text-(--p-text-color)\">\r\n No first node connected\r\n </div>\r\n <p\r\n class=\"m-0 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n The saved start connection points to a node key that is not on\r\n the canvas. Connect this trigger to the first node on the\r\n canvas.\r\n </p>\r\n <div\r\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n <span>Technical key</span>\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ startConnection().key }}\r\n </span>\r\n <span>Managed on canvas</span>\r\n </div>\r\n </div>\r\n }\r\n } @else {\r\n <div\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"text-[14px] font-semibold text-(--p-text-color)\">\r\n No first node connected\r\n </div>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Connect this trigger to the first node on the canvas.\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n\r\n @switch (editorType()) {\r\n @case (\"ManualTrigger\") {\r\n @if (sectionInMain(\"manualTrigger\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Manual run input</div>\r\n @if (\r\n hasTriggerPayloadSchema() && triggerPayloadSchema();\r\n as schema\r\n ) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Payload schema</div>\r\n <pre class=\"fp-ae-code\">{{ schemaText(schema) }}</pre>\r\n </div>\r\n }\r\n @if (triggerPayloadSample(); as sample) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Sample payload</div>\r\n <pre class=\"fp-ae-code\">{{ schemaText(sample) }}</pre>\r\n </div>\r\n }\r\n @if (!hasTriggerPayloadSchema() && !triggerPayloadSample()) {\r\n <p class=\"fp-ae-copy\">\r\n This manual trigger has no backend-provided input schema. It can\r\n still be connected to the first step on the canvas.\r\n </p>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"WebhookTrigger\") {\r\n @if (sectionInMain(\"webhookSetup\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Webhook setup</div>\r\n @if (webhookSetup(); as setup) {\r\n <div class=\"flex gap-2\">\r\n <mt-text-field\r\n class=\"flex-1 font-mono\"\r\n [ngModel]=\"setup.webhookUrl ?? ''\"\r\n [readonly]=\"true\"\r\n label=\"Webhook URL\"\r\n hint=\"Backend-generated endpoint clients should call to start this trigger.\"\r\n />\r\n <mt-button\r\n class=\"self-end\"\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Copy\"\r\n (onClick)=\"copyWebhookUrl()\"\r\n />\r\n </div>\r\n <div class=\"grid gap-2 md:grid-cols-2\">\r\n <div class=\"fp-ae-kv\">\r\n <span>Auth mode</span>\r\n <strong>{{ setup.authMode ?? \"Backend default\" }}</strong>\r\n </div>\r\n <div class=\"fp-ae-kv\">\r\n <span>Required headers</span>\r\n <strong>{{\r\n (setup.requiredHeaders ?? []).join(\", \") || \"-\"\r\n }}</strong>\r\n </div>\r\n </div>\r\n @if (setup.hmacSigning) {\r\n <p class=\"fp-ae-copy\">{{ setup.hmacSigning }}</p>\r\n }\r\n @if (setup.sampleRequest) {\r\n <details\r\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Sample request\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{\r\n schemaText(setup.sampleRequest)\r\n }}</pre>\r\n </details>\r\n }\r\n } @else {\r\n <p class=\"fp-ae-copy\">\r\n Webhook setup is not available for this draft yet.\r\n </p>\r\n }\r\n </section>\r\n }\r\n @if (sectionInMain(\"authPolicy\") && hasAuthPolicy()) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Authentication policy</div>\r\n <div class=\"grid gap-3 md:grid-cols-3\">\r\n <mt-select-field\r\n [ngModel]=\"authPolicy()['mode'] ?? ''\"\r\n (ngModelChange)=\"onAuthFieldChange('mode', $event)\"\r\n label=\"Mode\"\r\n hint=\"Authentication mode required by the backend webhook policy.\"\r\n [required]=\"isAuthFieldRequired('mode')\"\r\n [options]=\"authModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"authPolicy()['signatureHeaderName'] ?? ''\"\r\n (ngModelChange)=\"\r\n onAuthFieldChange('signatureHeaderName', $event)\r\n \"\r\n label=\"Signature header\"\r\n hint=\"Header name that carries the webhook signature when the policy requires signed requests.\"\r\n [required]=\"isAuthFieldRequired('signatureHeaderName')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"authPolicy()['timestampHeaderName'] ?? ''\"\r\n (ngModelChange)=\"\r\n onAuthFieldChange('timestampHeaderName', $event)\r\n \"\r\n label=\"Timestamp header\"\r\n hint=\"Header name that carries the request timestamp for replay protection.\"\r\n [required]=\"isAuthFieldRequired('timestampHeaderName')\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"FormSubmitTrigger\") {\r\n @if (sectionInMain(\"formBinding\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Form binding</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"selectedFormId() || formBinding()?.formId || ''\"\r\n (ngModelChange)=\"onFormChange($event)\"\r\n label=\"Form\"\r\n hint=\"Choose a backend form that will submit data into this trigger.\"\r\n [required]=\"true\"\r\n [options]=\"formOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"\r\n selectedFormVersionId() || formBinding()?.formVersionId || ''\r\n \"\r\n (ngModelChange)=\"onFormVersionChange($event)\"\r\n label=\"Form version\"\r\n hint=\"Persist the exact backend formVersionId. Do not type or generate IDs manually.\"\r\n [required]=\"true\"\r\n [options]=\"formVersionOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Save binding\"\r\n (onClick)=\"saveFormBinding()\"\r\n />\r\n @if (formBinding()) {\r\n <span\r\n class=\"rounded-lg bg-(--p-surface-100) px-2 py-1 font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ formBinding()!.formVersionId }}\r\n </span>\r\n }\r\n </div>\r\n @if (formSchema(); as schema) {\r\n <details\r\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Form schema preview\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(schema.schema) }}</pre>\r\n </details>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"ScheduleTrigger\") {\r\n @if (sectionInMain(\"schedule\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Schedule</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"scheduleMode()\"\r\n (ngModelChange)=\"onScheduleModeChange($event)\"\r\n label=\"Mode\"\r\n hint=\"Pick one schedule mode: cron, interval, or once. Backend validation requires exactly one mode.\"\r\n [required]=\"true\"\r\n [options]=\"scheduleModeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['timezone'] ?? 'UTC'\"\r\n (ngModelChange)=\"onConfigFieldChange('timezone', $event)\"\r\n label=\"Timezone\"\r\n hint=\"Timezone used to calculate the next fire time.\"\r\n [required]=\"isConfigFieldRequired('timezone')\"\r\n [options]=\"timeZoneOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (showScheduleCron()) {\r\n <mt-text-field\r\n class=\"font-mono md:col-span-2\"\r\n [ngModel]=\"config()['cron'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('cron', $event)\"\r\n label=\"Cron\"\r\n hint=\"Cron expression, for example 0 9 * * *.\"\r\n [required]=\"showScheduleCron()\"\r\n />\r\n }\r\n @if (showScheduleInterval()) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['intervalSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('intervalSeconds', $event)\r\n \"\r\n label=\"Interval seconds\"\r\n hint=\"Repeat interval in seconds.\"\r\n [required]=\"showScheduleInterval()\"\r\n [min]=\"0\"\r\n />\r\n }\r\n @if (showScheduleOnce()) {\r\n <mt-date-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"\r\n config()['runAt'] ??\r\n config()['runAtUtc'] ??\r\n config()['oneTimeAt'] ??\r\n config()['at'] ??\r\n ''\r\n \"\r\n (ngModelChange)=\"onConfigFieldChange('runAt', $event)\"\r\n label=\"Run at UTC\"\r\n hint=\"UTC date/time for the one-time schedule.\"\r\n [required]=\"showScheduleOnce()\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n @if (showScheduleStartDate()) {\r\n <mt-date-field\r\n [ngModel]=\"config()['startDate'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('startDate', $event)\"\r\n label=\"Start date UTC\"\r\n hint=\"Optional first allowed run time for this schedule.\"\r\n [required]=\"isConfigFieldRequired('startDate')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n <mt-select-field\r\n [ngModel]=\"config()['misfirePolicy'] ?? 'SkipMissed'\"\r\n (ngModelChange)=\"onConfigFieldChange('misfirePolicy', $event)\"\r\n label=\"Misfire policy\"\r\n hint=\"Backend behavior when a scheduled run is missed while the automation is unavailable.\"\r\n [required]=\"isConfigFieldRequired('misfirePolicy')\"\r\n [options]=\"misfirePolicyOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Validate\"\r\n (onClick)=\"validateSchedule()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Preview next fire\"\r\n (onClick)=\"previewSchedule()\"\r\n />\r\n </div>\r\n @if (schedulePreview(); as preview) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n <strong class=\"text-emerald-600\">Valid</strong>\r\n <span class=\"ms-2\"\r\n >Next fire: {{ preview.nextFireAtUtc ?? \"-\" }}</span\r\n >\r\n </div>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"SetFields\") {\r\n @if (sectionInMain(\"setFields\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Set fields</div>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'fields',\r\n rows: fieldsRows(),\r\n keyLabel: 'Field',\r\n valueLabel: 'Value',\r\n addLabel: 'Add field',\r\n required: isConfigFieldRequired('fields'),\r\n }\r\n \"\r\n />\r\n </section>\r\n }\r\n }\r\n @case (\"If\") {\r\n @if (sectionInMain(\"condition\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Condition</div>\r\n <div class=\"grid gap-3 md:grid-cols-[1fr_180px_1fr]\">\r\n <mt-text-field\r\n [ngModel]=\"ifConditionFieldText('left')\"\r\n (focusin)=\"setExpressionTarget('config:left')\"\r\n (ngModelChange)=\"onIfConditionFieldChange('left', $event)\"\r\n label=\"Left\"\r\n hint=\"Left-side value or expression to compare.\"\r\n [required]=\"isConfigFieldRequired('left')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"ifConditionFieldValue('operator') || 'equals'\"\r\n (ngModelChange)=\"onIfConditionFieldChange('operator', $event)\"\r\n label=\"Operator\"\r\n hint=\"Comparison operator used by the backend condition evaluator.\"\r\n [required]=\"isConfigFieldRequired('operator')\"\r\n [options]=\"ifOperatorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"ifConditionFieldText('right')\"\r\n (focusin)=\"setExpressionTarget('config:right')\"\r\n (ngModelChange)=\"onIfConditionFieldChange('right', $event)\"\r\n label=\"Right\"\r\n hint=\"Right-side value or expression to compare against.\"\r\n [required]=\"isConfigFieldRequired('right')\"\r\n />\r\n </div>\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Route outputs</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (key of routeOutputKeys(); track key) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ key }}</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"HTTP\") {\r\n @if (sectionInMain(\"httpRequest\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">HTTP request</div>\r\n <div class=\"grid gap-3 md:grid-cols-[150px_1fr]\">\r\n <mt-select-field\r\n [ngModel]=\"httpMethod()\"\r\n (ngModelChange)=\"onHttpMethodChange($event)\"\r\n label=\"Method\"\r\n hint=\"HTTP method used for the outbound request.\"\r\n [required]=\"isConfigFieldRequired('method')\"\r\n [options]=\"httpMethodOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['url'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:url')\"\r\n (ngModelChange)=\"onConfigFieldChange('url', $event)\"\r\n label=\"URL\"\r\n hint=\"Target URL. Expressions are supported for dynamic hosts, paths, and query values.\"\r\n [required]=\"isConfigFieldRequired('url')\"\r\n />\r\n </div>\r\n <div class=\"mt-3\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'headers',\r\n rows: headerRows(),\r\n keyLabel: 'Header',\r\n valueLabel: 'Value',\r\n addLabel: 'Add header',\r\n }\r\n \"\r\n />\r\n </div>\r\n <div class=\"mt-3\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'query',\r\n rows: queryRows(),\r\n keyLabel: 'Query param',\r\n valueLabel: 'Value',\r\n addLabel: 'Add query param',\r\n }\r\n \"\r\n />\r\n </div>\r\n @if (httpMethodAllowsPayload()) {\r\n @if (httpSupportsBodyMode()) {\r\n <mt-select-field\r\n class=\"mt-3\"\r\n [ngModel]=\"httpBodyMode()\"\r\n (ngModelChange)=\"onHttpBodyModeChange($event)\"\r\n label=\"Payload mode\"\r\n hint=\"How the request payload should be sent by the backend HTTP runtime.\"\r\n [required]=\"isConfigFieldRequired(httpBodyModeConfigKey())\"\r\n [options]=\"httpBodyModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n }\r\n @if (httpBodyEnabled()) {\r\n <mt-textarea-field\r\n class=\"mt-3 w-full font-mono\"\r\n [ngModel]=\"valueText(config()['body'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:body')\"\r\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\r\n label=\"Request payload\"\r\n hint=\"Payload sent by this HTTP method. Use JSON or expressions when the backend schema allows it.\"\r\n [required]=\"isConfigFieldRequired('body')\"\r\n rows=\"6\"\r\n />\r\n }\r\n } @else {\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ httpMethod() }} requests do not send a payload. Put request\r\n data in query parameters or headers.\r\n </div>\r\n }\r\n @if (\r\n supportsConfigKey(\"timeoutSeconds\") ||\r\n supportsConfigKey(\"responseHandling\")\r\n ) {\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n @if (supportsConfigKey(\"timeoutSeconds\")) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('timeoutSeconds', $event)\r\n \"\r\n label=\"Timeout seconds\"\r\n hint=\"Request timeout when exposed by the backend schema.\"\r\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\r\n [min]=\"0\"\r\n />\r\n }\r\n @if (supportsConfigKey(\"responseHandling\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['responseHandling'] ?? ''\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('responseHandling', $event)\r\n \"\r\n label=\"Response handling\"\r\n hint=\"Backend-supported response handling mode or expression.\"\r\n [required]=\"isConfigFieldRequired('responseHandling')\"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (sectionInMain(\"credentials\")) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Credential</div>\r\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\r\n </div>\r\n }\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"min-w-0\">\r\n <div class=\"fp-ae-label\">Configuration check</div>\r\n <p class=\"fp-ae-copy m-0 mt-1\">\r\n Runs backend dry-run validation for method, URL,\r\n expressions, and request policy before execution.\r\n </p>\r\n </div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n label=\"Check request\"\r\n [disabled]=\"httpCheckState() === 'loading'\"\r\n (onClick)=\"checkHttpRequest()\"\r\n />\r\n </div>\r\n @if (httpLocalIssues().length > 0) {\r\n <div\r\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-warning))]/25 bg-[rgb(var(--fp-warning))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\r\n >\r\n {{ httpLocalIssues()[0] }}\r\n </div>\r\n } @else if (httpCheckError(); as error) {\r\n <div\r\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-error))]/25 bg-[rgb(var(--fp-error))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\r\n >\r\n {{ error }}\r\n </div>\r\n } @else if (httpCheckResult(); as result) {\r\n <div\r\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-commit))]/25 bg-[rgb(var(--fp-commit))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\r\n >\r\n Backend check\r\n {{\r\n result.success || result.canStart\r\n ? \"passed\"\r\n : \"completed with issues\"\r\n }}.\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"Wait\") {\r\n @if (sectionInMain(\"wait\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Wait</div>\r\n <div class=\"grid gap-3 md:grid-cols-3\">\r\n <mt-select-field\r\n [ngModel]=\"waitMode()\"\r\n (ngModelChange)=\"onWaitModeChange($event)\"\r\n label=\"Mode\"\r\n hint=\"Choose how execution should pause.\"\r\n [required]=\"isConfigFieldRequired('mode')\"\r\n [options]=\"waitModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (showWaitDuration()) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['durationSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('durationSeconds', $event)\r\n \"\r\n label=\"Wait duration\"\r\n hint=\"Seconds to pause before continuing.\"\r\n [required]=\"isConfigFieldRequired('durationSeconds')\"\r\n [min]=\"1\"\r\n />\r\n }\r\n @if (showWaitUntilDate()) {\r\n <mt-date-field\r\n [ngModel]=\"waitUntilDateValue()\"\r\n (ngModelChange)=\"onWaitUntilDateChange($event)\"\r\n label=\"Resume at\"\r\n hint=\"Date and time when execution should continue.\"\r\n [required]=\"isConfigFieldRequired('untilDate')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n </div>\r\n @if (showWaitResumePayloadSchema()) {\r\n <mt-textarea-field\r\n class=\"mt-3 w-full font-mono\"\r\n [ngModel]=\"waitResumePayloadSchemaText()\"\r\n (focusin)=\"setExpressionTarget('config:resumePayloadSchema')\"\r\n (ngModelChange)=\"onWaitResumePayloadSchemaChange($event)\"\r\n label=\"Resume payload schema\"\r\n hint=\"Optional JSON schema for data sent back when this wait is resumed.\"\r\n [required]=\"isConfigFieldRequired('resumePayloadSchema')\"\r\n rows=\"5\"\r\n />\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"HumanTask\") {\r\n @if (sectionInMain(\"humanTask\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Human task</div>\r\n @if (\r\n assignmentOptions()?.providerStatus &&\r\n assignmentOptions()?.providerStatus !== \"Available\"\r\n ) {\r\n <p class=\"fp-ae-copy\">\r\n Assignment provider status:\r\n {{ assignmentOptions()?.providerStatus }}. The backend provider\r\n is the source of truth for available assignees.\r\n </p>\r\n }\r\n <div\r\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Task details\r\n </h3>\r\n <div class=\"grid gap-3 p-4 xl:grid-cols-3\">\r\n <mt-text-field\r\n [ngModel]=\"config()['title'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:title')\"\r\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\r\n label=\"Task title\"\r\n hint=\"Title shown to the assigned user while the execution waits.\"\r\n [required]=\"isConfigFieldRequired('title')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"selectedAssignmentKey()\"\r\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\r\n label=\"Assignment\"\r\n hint=\"Backend-provided assignee, role, or group that can submit this task.\"\r\n [required]=\"isConfigFieldRequired('assignment')\"\r\n [options]=\"assignmentSelectOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['priority'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('priority', $event)\"\r\n label=\"Priority\"\r\n hint=\"Optional task priority passed to backend task orchestration.\"\r\n [required]=\"isConfigFieldRequired('priority')\"\r\n />\r\n <mt-textarea-field\r\n class=\"xl:col-span-3\"\r\n [ngModel]=\"config()['description'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:description')\"\r\n (ngModelChange)=\"onConfigFieldChange('description', $event)\"\r\n label=\"Task description\"\r\n hint=\"Instructions shown with the HumanTask form.\"\r\n [required]=\"isConfigFieldRequired('description')\"\r\n rows=\"4\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div\r\n class=\"mt-3 flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Task behavior\r\n </h3>\r\n <div class=\"grid gap-3 p-4 xl:grid-cols-4\">\r\n <mt-date-field\r\n [ngModel]=\"\r\n config()['dueDateUtc'] ?? config()['dueDate'] ?? ''\r\n \"\r\n (ngModelChange)=\"onConfigFieldChange('dueDateUtc', $event)\"\r\n label=\"Due date\"\r\n hint=\"Optional backend due timestamp for the waiting task.\"\r\n [required]=\"isConfigFieldRequired('dueDateUtc')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['dueInSeconds'])\"\r\n (ngModelChange)=\"onConfigFieldChange('dueInSeconds', $event)\"\r\n label=\"Due in seconds\"\r\n hint=\"Relative due duration when the backend should calculate the timestamp.\"\r\n [required]=\"isConfigFieldRequired('dueInSeconds')\"\r\n [min]=\"0\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['contextOutputPath'] ?? 'task'\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('contextOutputPath', $event)\r\n \"\r\n label=\"Context output path\"\r\n hint=\"Where submitted task values are written in execution context.\"\r\n [required]=\"isConfigFieldRequired('contextOutputPath')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['cancelBehavior'] ?? 'RouteCancelled'\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('cancelBehavior', $event)\r\n \"\r\n label=\"Cancel behavior\"\r\n hint=\"Route cancelled when users cancel, or fail this node.\"\r\n [required]=\"isConfigFieldRequired('cancelBehavior')\"\r\n [options]=\"humanTaskCancelBehaviorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <div\r\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <mt-toggle-field\r\n size=\"small\"\r\n label=\"Allow draft save\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['saveDraft'] !== false\"\r\n [required]=\"isConfigFieldRequired('saveDraft')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('saveDraft', $event === true)\r\n \"\r\n hint=\"Expose backend draft save for this task when supported.\"\r\n />\r\n </div>\r\n @if (supportsConfigKey(\"submitBehavior\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['submitBehavior'] ?? ''\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('submitBehavior', $event)\r\n \"\r\n label=\"Submit behavior\"\r\n hint=\"Backend submit behavior when exposed by the node schema.\"\r\n [required]=\"isConfigFieldRequired('submitBehavior')\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Validate assignment\"\r\n (onClick)=\"validateAssignment()\"\r\n />\r\n </div>\r\n @if (assignmentValidation(); as result) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n {{\r\n result.isValid === false\r\n ? \"Assignment invalid\"\r\n : \"Assignment accepted by helper\"\r\n }}\r\n @for (issue of resultIssues(result); track $index) {\r\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\r\n {{ issue }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n\r\n @if (sectionInMain(\"formBinding\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Task input form</div>\r\n <mt-radio-cards-field\r\n [ngModel]=\"humanTaskFormSource()\"\r\n (ngModelChange)=\"onHumanTaskFormSourceChange($event)\"\r\n label=\"Form source\"\r\n hint=\"HumanTask requires either its own node form binding or an explicit reused FormSubmitTrigger payload.\"\r\n [required]=\"isConfigFieldRequired('formSource')\"\r\n [options]=\"humanTaskFormSourceOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n size=\"small\"\r\n >\r\n <ng-template #option let-item>\r\n <div class=\"w-full min-w-0\">\r\n <div class=\"text-[13px] font-semibold leading-5\">\r\n {{ item.label }}\r\n </div>\r\n <p\r\n class=\"mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ item.description }}\r\n </p>\r\n </div>\r\n </ng-template>\r\n </mt-radio-cards-field>\r\n\r\n @if (humanTaskUsesExplicitFormBinding()) {\r\n <div\r\n class=\"mt-3 flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n HumanTask input binding\r\n </h3>\r\n <div class=\"grid gap-3 p-4 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"selectedFormId() || formBinding()?.formId || ''\"\r\n (ngModelChange)=\"onFormChange($event)\"\r\n label=\"Form\"\r\n hint=\"Choose a backend form for this HumanTask node.\"\r\n [required]=\"humanTaskUsesExplicitFormBinding()\"\r\n [options]=\"formOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"\r\n selectedFormVersionId() ||\r\n formBinding()?.formVersionId ||\r\n ''\r\n \"\r\n (ngModelChange)=\"onFormVersionChange($event)\"\r\n label=\"Form version\"\r\n hint=\"Persist the exact backend formVersionId for the node binding.\"\r\n [required]=\"humanTaskUsesExplicitFormBinding()\"\r\n [options]=\"formVersionOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"border-t border-surface-200 px-4 py-3\">\r\n @if (humanTaskFormSummary(); as summary) {\r\n <div\r\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n @if (summary.formLabel) {\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.formLabel }}\r\n </span>\r\n }\r\n @if (summary.formVersionId) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ summary.formVersionId }}\r\n </span>\r\n }\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.fieldCount }} fields\r\n </span>\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.requiredCount }} required\r\n </span>\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.writableCount }} writable\r\n </span>\r\n </div>\r\n }\r\n\r\n @if (humanTaskHasCustomInputMapping()) {\r\n <div\r\n class=\"mt-3 rounded-md border border-[rgb(var(--fp-warning))]/30 bg-[rgb(var(--fp-warning))]/10 px-3 py-2 text-[12px] leading-5 text-(--p-text-color)\"\r\n >\r\n Existing custom input mapping will be preserved unless\r\n field prefill options are changed.\r\n </div>\r\n }\r\n\r\n @if (humanTaskFormFields().length > 0) {\r\n <div\r\n class=\"mt-3 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"text-[13px] font-semibold text-color\">\r\n FormBuilder fields\r\n </div>\r\n <div class=\"flex flex-wrap gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n label=\"Select all\"\r\n (onClick)=\"selectAllHumanTaskPrefillFields()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n label=\"Clear\"\r\n (onClick)=\"clearHumanTaskPrefillFields()\"\r\n />\r\n </div>\r\n </div>\r\n <div class=\"divide-y divide-surface-200\">\r\n @for (field of humanTaskFormFields(); track field.key) {\r\n <div\r\n class=\"grid gap-3 px-3 py-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-center\"\r\n >\r\n <div class=\"min-w-0\">\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n <span\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ field.label }}\r\n </span>\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ field.key }}\r\n </span>\r\n @if (field.required) {\r\n <span\r\n class=\"rounded-md bg-[rgb(var(--fp-danger))]/10 px-2 py-0.5 text-[11px] font-semibold text-[rgb(var(--fp-danger))]\"\r\n >\r\n Required\r\n </span>\r\n }\r\n @if (field.read === true) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n Read\r\n </span>\r\n }\r\n @if (field.write === true) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n Write\r\n </span>\r\n }\r\n </div>\r\n @if (field.description) {\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ field.description }}\r\n </p>\r\n }\r\n </div>\r\n <mt-toggle-field\r\n size=\"small\"\r\n label=\"Prefill\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"\r\n humanTaskPrefillFieldKeySet().has(field.key)\r\n \"\r\n (ngModelChange)=\"\r\n onHumanTaskPrefillFieldToggle(\r\n field.key,\r\n $event === true\r\n )\r\n \"\r\n />\r\n </div>\r\n }\r\n </div>\r\n <div\r\n class=\"border-t border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Selected fields are saved as\r\n <span class=\"font-mono\"\r\n >inputMappingJson.fieldKeys</span\r\n >\r\n so backend can prefill matching upstream values.\r\n </div>\r\n </div>\r\n } @else if (formSchema()) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No FormBuilder fields were returned for this form schema.\r\n </div>\r\n }\r\n\r\n @if (formSchema()?.requiresSnapshotOnPublish) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Backend requires this form version snapshot during\r\n publish.\r\n </div>\r\n }\r\n </div>\r\n <div class=\"border-t border-surface-200 px-4 py-3\">\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Save task binding\"\r\n (onClick)=\"saveFormBinding()\"\r\n />\r\n @if (formBinding()) {\r\n <span\r\n class=\"rounded-lg bg-(--p-surface-100) px-2 py-1 font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ formBinding()!.formVersionId }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['triggerKey'] ?? ''\"\r\n (ngModelChange)=\"onHumanTaskReuseTriggerChange($event)\"\r\n label=\"Trigger to reuse\"\r\n hint=\"Explicitly select the FormSubmitTrigger whose submitted form payload is reused.\"\r\n [options]=\"triggerOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n @if (humanTaskReuseFormBinding()) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Reused trigger form</div>\r\n @if (humanTaskFormSummary(); as summary) {\r\n <div\r\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n @if (summary.formLabel) {\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.formLabel }}\r\n </span>\r\n }\r\n @if (summary.formVersionId) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ summary.formVersionId }}\r\n </span>\r\n }\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.fieldCount }} fields\r\n </span>\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.requiredCount }} required\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n @if (formSchema(); as schema) {\r\n <details\r\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Form schema preview\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(schema.schema) }}</pre>\r\n </details>\r\n }\r\n\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">HumanTask routes</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (key of routeOutputKeys(); track key) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ key }}</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"HumanApproval\") {\r\n @if (sectionInMain(\"approvalTask\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Approval task</div>\r\n @if (\r\n assignmentOptions()?.providerStatus &&\r\n assignmentOptions()?.providerStatus !== \"Available\"\r\n ) {\r\n <p class=\"fp-ae-copy\">\r\n Assignment provider status:\r\n {{ assignmentOptions()?.providerStatus }}. The backend provider\r\n is the source of truth for available assignees.\r\n </p>\r\n }\r\n <div class=\"grid gap-3 xl:grid-cols-3\">\r\n <mt-text-field\r\n [ngModel]=\"config()['title'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:title')\"\r\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\r\n label=\"Approval title\"\r\n hint=\"Approval title shown to the assigned human approver.\"\r\n [required]=\"isConfigFieldRequired('title')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"selectedAssignmentKey()\"\r\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\r\n label=\"Assignment\"\r\n hint=\"Backend-provided assignee, role, or group that can decide this approval.\"\r\n [required]=\"isConfigFieldRequired('assignment')\"\r\n [options]=\"assignmentSelectOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (humanApprovalSupportsConfig(\"priority\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['priority'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('priority', $event)\"\r\n label=\"Priority\"\r\n hint=\"Approval priority when supported by the backend schema.\"\r\n [required]=\"isConfigFieldRequired('priority')\"\r\n />\r\n }\r\n </div>\r\n <mt-textarea-field\r\n class=\"mt-3 w-full\"\r\n [ngModel]=\"config()['message'] ?? config()['instructions'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message')\"\r\n (ngModelChange)=\"onConfigFieldChange('message', $event)\"\r\n label=\"Approval message\"\r\n hint=\"Decision instructions shown to the approver.\"\r\n [required]=\"isConfigFieldRequired('message')\"\r\n rows=\"4\"\r\n />\r\n @if (\r\n humanApprovalSupportsConfig(\"dueDate\") ||\r\n humanApprovalSupportsConfig(\"dueInSeconds\") ||\r\n humanApprovalSupportsConfig(\"timeoutSeconds\") ||\r\n humanApprovalSupportsConfig(\"expiresAt\") ||\r\n humanApprovalSupportsConfig(\"commentsRequired\") ||\r\n humanApprovalSupportsConfig(\"allowReturn\")\r\n ) {\r\n <div class=\"mt-3 grid gap-3 xl:grid-cols-4\">\r\n @if (humanApprovalSupportsConfig(\"dueDate\")) {\r\n <mt-date-field\r\n [ngModel]=\"config()['dueDate'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('dueDate', $event)\"\r\n label=\"Due date\"\r\n hint=\"Backend-supported approval due date.\"\r\n [required]=\"isConfigFieldRequired('dueDate')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"dueInSeconds\")) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['dueInSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('dueInSeconds', $event)\r\n \"\r\n label=\"Due in seconds\"\r\n hint=\"Relative approval due duration when supported by the backend schema.\"\r\n [required]=\"isConfigFieldRequired('dueInSeconds')\"\r\n [min]=\"0\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"timeoutSeconds\")) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('timeoutSeconds', $event)\r\n \"\r\n label=\"Timeout seconds\"\r\n hint=\"Approval timeout emitted according to backend approval runtime support.\"\r\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\r\n [min]=\"0\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"expiresAt\")) {\r\n <mt-date-field\r\n [ngModel]=\"config()['expiresAt'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('expiresAt', $event)\"\r\n label=\"Expires at\"\r\n hint=\"Backend-supported approval expiry timestamp.\"\r\n [required]=\"isConfigFieldRequired('expiresAt')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"commentsRequired\")) {\r\n <div\r\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <mt-toggle-field\r\n size=\"small\"\r\n label=\"Comments required\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['commentsRequired'] === true\"\r\n [required]=\"isConfigFieldRequired('commentsRequired')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('commentsRequired', $event === true)\r\n \"\r\n hint=\"Require approver comments when the backend supports this flag.\"\r\n />\r\n </div>\r\n }\r\n @if (humanApprovalSupportsConfig(\"allowReturn\")) {\r\n <div\r\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <mt-toggle-field\r\n size=\"small\"\r\n label=\"Allow return for changes\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['allowReturn'] !== false\"\r\n [required]=\"isConfigFieldRequired('allowReturn')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('allowReturn', $event === true)\r\n \"\r\n hint=\"Allow ReturnForChanges when the backend schema exposes this option.\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n <div class=\"mt-3 space-y-3\">\r\n <div class=\"fp-ae-section-title\">Decision options</div>\r\n <div\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n @if (selectedApprovalDecisionRows().length > 0) {\r\n <div\r\n class=\"hidden border-b border-surface-200 bg-surface-50 px-3 py-2 text-[12px] font-semibold text-(--p-text-muted-color) xl:grid xl:grid-cols-[1.1fr_1fr_1.2fr_80px_44px] xl:gap-3\"\r\n >\r\n <span>Decision label</span>\r\n <span>Canonical value</span>\r\n <span>Route output key</span>\r\n <span>Routes</span>\r\n <span>Action</span>\r\n </div>\r\n @for (\r\n decision of selectedApprovalDecisionRows();\r\n track decision.value\r\n ) {\r\n <div\r\n class=\"grid gap-2 border-b border-surface-100 px-3 py-3 last:border-b-0 xl:grid-cols-[1.1fr_1fr_1.2fr_80px_44px] xl:items-center xl:gap-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <div\r\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\r\n >\r\n Decision label\r\n </div>\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ decision.label }}\r\n </div>\r\n </div>\r\n <div class=\"min-w-0\">\r\n <div\r\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\r\n >\r\n Canonical value\r\n </div>\r\n <div\r\n class=\"truncate font-mono text-[12px] text-(--p-text-color)\"\r\n >\r\n {{ decision.value }}\r\n </div>\r\n </div>\r\n <div class=\"min-w-0\">\r\n <div\r\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\r\n >\r\n Route output key\r\n </div>\r\n <div\r\n class=\"truncate font-mono text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n {{ decision.routeOutputKey }}\r\n </div>\r\n </div>\r\n <div>\r\n <div\r\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\r\n >\r\n Routes\r\n </div>\r\n <div class=\"text-[13px] text-(--p-text-color)\">\r\n {{ decision.routeCount }}\r\n </div>\r\n </div>\r\n <mt-button\r\n class=\"justify-self-start xl:justify-self-end\"\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n icon=\"general.trash-01\"\r\n tooltip=\"Remove decision option\"\r\n (onClick)=\"removeApprovalDecision(decision.value)\"\r\n />\r\n </div>\r\n }\r\n } @else {\r\n <div\r\n class=\"px-3 py-4 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n Add at least one backend-supported approval decision.\r\n </div>\r\n }\r\n </div>\r\n @if (approvalDecisionIssues().length > 0) {\r\n <div\r\n class=\"rounded-lg border border-[rgb(var(--fp-error))]/30 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n @for (issue of approvalDecisionIssues(); track $index) {\r\n <div>{{ issue }}</div>\r\n }\r\n </div>\r\n }\r\n <div class=\"grid gap-2 md:grid-cols-[minmax(0,1fr)_auto]\">\r\n <mt-select-field\r\n [ngModel]=\"approvalDecisionToAdd()\"\r\n (ngModelChange)=\"approvalDecisionToAdd.set($event)\"\r\n label=\"Decision to add\"\r\n hint=\"Only backend-supported decisions with matching route outputs are available.\"\r\n [required]=\"isConfigFieldRequired('allowedDecisions')\"\r\n [options]=\"addableApprovalDecisionOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-button\r\n class=\"self-end\"\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add decision option\"\r\n [disabled]=\"!approvalDecisionToAdd()\"\r\n (onClick)=\"addApprovalDecision()\"\r\n />\r\n </div>\r\n </div>\r\n @if (\r\n humanApprovalSupportsConfig(\"payload\") ||\r\n humanApprovalSupportsConfig(\"context\") ||\r\n humanApprovalSupportsConfig(\"metadata\")\r\n ) {\r\n <div class=\"mt-3 grid gap-3\">\r\n @if (humanApprovalSupportsConfig(\"payload\")) {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"valueText(config()['payload'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:payload')\"\r\n (ngModelChange)=\"onConfigFieldChange('payload', $event)\"\r\n label=\"Payload\"\r\n hint=\"Approval task payload or context fields supported by backend schema.\"\r\n [required]=\"isConfigFieldRequired('payload')\"\r\n rows=\"5\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"context\")) {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"valueText(config()['context'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:context')\"\r\n (ngModelChange)=\"onConfigFieldChange('context', $event)\"\r\n label=\"Context\"\r\n hint=\"Additional approval context supported by backend schema.\"\r\n [required]=\"isConfigFieldRequired('context')\"\r\n rows=\"5\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"metadata\")) {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"valueText(config()['metadata'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:metadata')\"\r\n (ngModelChange)=\"onConfigFieldChange('metadata', $event)\"\r\n label=\"Metadata\"\r\n hint=\"Additional approval metadata supported by the backend schema.\"\r\n [required]=\"isConfigFieldRequired('metadata')\"\r\n rows=\"4\"\r\n />\r\n }\r\n </div>\r\n }\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Validate assignment\"\r\n (onClick)=\"validateAssignment()\"\r\n />\r\n </div>\r\n @if (assignmentValidation(); as result) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n {{\r\n result.isValid === false\r\n ? \"Assignment invalid\"\r\n : \"Assignment accepted by helper\"\r\n }}\r\n @for (issue of resultIssues(result); track $index) {\r\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\r\n {{ issue }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"ConnectorAction\") {\r\n @if (sectionInMain(\"humanReviewRequest\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">\r\n {{ humanReviewProviderLabel() }} human review\r\n </div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"config()['channelType'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('channelType', $event)\"\r\n label=\"Channel\"\r\n [required]=\"isConfigFieldRequired('channelType')\"\r\n />\r\n </div>\r\n\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\r\n <mt-text-field\r\n [ngModel]=\"humanReviewDestination()['recipient'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:destination:recipient')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('destination', 'recipient', $event)\r\n \"\r\n label=\"Recipient\"\r\n [required]=\"isConnectorDestinationRequired()\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"humanReviewDestination()['channelId'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:destination:channelId')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('destination', 'channelId', $event)\r\n \"\r\n label=\"Channel ID\"\r\n [required]=\"isConnectorDestinationRequired()\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"humanReviewDestination()['threadKey'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:destination:threadKey')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('destination', 'threadKey', $event)\r\n \"\r\n label=\"Thread key\"\r\n [required]=\"isConnectorDestinationRequired()\"\r\n />\r\n </div>\r\n\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-text-field\r\n [ngModel]=\"humanReviewMessage()['subject'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message:subject')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('message', 'subject', $event)\r\n \"\r\n label=\"Subject\"\r\n [required]=\"isConnectorMessageRequired()\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full\"\r\n [ngModel]=\"humanReviewMessage()['body'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message:body')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('message', 'body', $event)\r\n \"\r\n label=\"Message\"\r\n [required]=\"isConnectorMessageRequired()\"\r\n rows=\"5\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"humanReviewMessage()['templateKey'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message:templateKey')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('message', 'templateKey', $event)\r\n \"\r\n label=\"Template key\"\r\n [required]=\"isConnectorMessageRequired()\"\r\n />\r\n </div>\r\n\r\n <div class=\"mt-4\">\r\n <div class=\"mb-2 flex items-center justify-between gap-3\">\r\n <div class=\"fp-ae-label\">Response options</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Add option\"\r\n (onClick)=\"addHumanReviewResponseOption()\"\r\n />\r\n </div>\r\n <div class=\"grid gap-2\">\r\n @for (\r\n option of humanReviewResponseOptions();\r\n track option.key + \":\" + $index\r\n ) {\r\n <div\r\n class=\"grid gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-2 md:grid-cols-[1fr_1fr_1fr_auto]\"\r\n >\r\n <mt-text-field\r\n [ngModel]=\"option.key\"\r\n (ngModelChange)=\"\r\n updateHumanReviewResponseOption($index, 'key', $event)\r\n \"\r\n label=\"Key\"\r\n [required]=\"isConfigFieldRequired('responseOptions')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"option.label\"\r\n (ngModelChange)=\"\r\n updateHumanReviewResponseOption($index, 'label', $event)\r\n \"\r\n label=\"Label\"\r\n [required]=\"isConfigFieldRequired('responseOptions')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"option.routeOutputKey\"\r\n (ngModelChange)=\"\r\n updateHumanReviewResponseOption(\r\n $index,\r\n 'routeOutputKey',\r\n $event\r\n )\r\n \"\r\n label=\"Route output\"\r\n [required]=\"isConfigFieldRequired('responseOptions')\"\r\n />\r\n <div class=\"flex items-end justify-end\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"general.x-close\"\r\n [disabled]=\"option.routeCount > 0\"\r\n (onClick)=\"removeHumanReviewResponseOption($index)\"\r\n [attr.aria-label]=\"'Remove response option'\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"selectedAssignmentKey()\"\r\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\r\n label=\"Assignment\"\r\n [required]=\"isConfigFieldRequired('assignment')\"\r\n [options]=\"assignmentSelectOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['credentialRef'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('credentialRef', $event)\"\r\n label=\"Credential\"\r\n [required]=\"isConfigFieldRequired('credentialRef')\"\r\n />\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\r\n (ngModelChange)=\"onConfigFieldChange('timeoutSeconds', $event)\"\r\n label=\"Timeout seconds\"\r\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\r\n [min]=\"1\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['failureBehavior'] ?? 'RouteFailure'\"\r\n (ngModelChange)=\"onConfigFieldChange('failureBehavior', $event)\"\r\n label=\"Failure behavior\"\r\n [required]=\"isConfigFieldRequired('failureBehavior')\"\r\n [options]=\"humanReviewFailureBehaviorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Validate assignment\"\r\n (onClick)=\"validateAssignment()\"\r\n />\r\n </div>\r\n @if (assignmentValidation(); as result) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n {{\r\n result.isValid === false\r\n ? \"Assignment invalid\"\r\n : \"Assignment accepted by helper\"\r\n }}\r\n @for (issue of resultIssues(result); track $index) {\r\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\r\n {{ issue }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"mt-4 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Route outputs</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (key of routeOutputKeys(); track key) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ key }}</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"ConvertToFile\") {\r\n @if (sectionInMain(\"convertToFile\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">\r\n {{ convertToFileActionLabel() }}\r\n </div>\r\n @if (convertToFileUnavailableReason(); as reason) {\r\n <div\r\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ reason }}\r\n </div>\r\n }\r\n\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"config()['fileName'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:fileName')\"\r\n (ngModelChange)=\"onConfigFieldChange('fileName', $event)\"\r\n label=\"File name\"\r\n [required]=\"isConfigFieldRequired('fileName')\"\r\n />\r\n @if (convertToFileIsBase64()) {\r\n <mt-text-field\r\n [ngModel]=\"config()['contentType'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('contentType', $event)\"\r\n label=\"Content type\"\r\n [required]=\"isConfigFieldRequired('contentType')\"\r\n />\r\n } @else {\r\n <mt-text-field\r\n [ngModel]=\"convertToFileContentType()\"\r\n [readonly]=\"true\"\r\n label=\"Content type\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (\r\n convertToFileIsCsv() ||\r\n convertToFileIsJson() ||\r\n convertToFileIsSpreadsheet()\r\n ) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"convertToFileSourceMode()\"\r\n (ngModelChange)=\"onConvertToFileSourceModeChange($event)\"\r\n label=\"Data source\"\r\n [required]=\"isConfigFieldRequired('sourceExpression')\"\r\n [options]=\"convertToFileSourceOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (convertToFileSourceMode() === \"expression\") {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"convertToFileSourceExpression()\"\r\n (focusin)=\"setExpressionTarget('config:sourceExpression')\"\r\n (ngModelChange)=\"\r\n onConvertToFileSourceExpressionChange($event)\r\n \"\r\n label=\"Source expression\"\r\n [required]=\"isConfigFieldRequired('sourceExpression')\"\r\n />\r\n }\r\n @if (convertToFileIsCsv() || convertToFileIsSpreadsheet()) {\r\n <div\r\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3 md:col-span-2\"\r\n >\r\n <div class=\"mb-2 flex items-center justify-between gap-3\">\r\n <div class=\"fp-ae-label\">Columns</div>\r\n @if (convertToFileColumns().length) {\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n label=\"Clear\"\r\n (onClick)=\"clearConvertToFileColumns()\"\r\n />\r\n }\r\n </div>\r\n <div class=\"mb-3 flex min-h-8 flex-wrap gap-1.5\">\r\n @if (convertToFileColumns().length) {\r\n @for (column of convertToFileColumns(); track column) {\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ column }}\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex h-4 w-4 items-center justify-center rounded-sm text-[10px] hover:bg-(--p-surface-200) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n (click)=\"removeConvertToFileColumn(column)\"\r\n [attr.aria-label]=\"'Remove column ' + column\"\r\n >\r\n x\r\n </button>\r\n </span>\r\n }\r\n } @else {\r\n <span\r\n class=\"rounded-md bg-(--p-content-background) px-2 py-1 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n All fields\r\n </span>\r\n }\r\n </div>\r\n <div class=\"grid items-end gap-2 md:grid-cols-[1fr_auto]\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"convertToFileColumnDraft()\"\r\n (ngModelChange)=\"\r\n onConvertToFileColumnDraftChange($event)\r\n \"\r\n (keyup.enter)=\"addConvertToFileColumns()\"\r\n label=\"Add column\"\r\n [required]=\"isConfigFieldRequired('columns')\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add\"\r\n [disabled]=\"!convertToFileColumnDraft().trim()\"\r\n (onClick)=\"addConvertToFileColumns()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsCsv()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['includeHeaders'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('includeHeaders', $event === true)\r\n \"\r\n label=\"Include headers\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['delimiter'] ?? ','\"\r\n (ngModelChange)=\"onConfigFieldChange('delimiter', $event)\"\r\n label=\"Delimiter\"\r\n [required]=\"isConfigFieldRequired('delimiter')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['encoding'] ?? 'utf-8'\"\r\n [readonly]=\"true\"\r\n label=\"Encoding\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsJson()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['pretty'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('pretty', $event === true)\r\n \"\r\n label=\"Pretty JSON\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsHtml()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-text-field\r\n [ngModel]=\"config()['title'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:title')\"\r\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\r\n label=\"Title\"\r\n [required]=\"isConfigFieldRequired('title')\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full\"\r\n [ngModel]=\"config()['body'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:body')\"\r\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\r\n label=\"Body\"\r\n [required]=\"isConfigFieldRequired('body')\"\r\n rows=\"7\"\r\n />\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['sanitize'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('sanitize', $event === true)\r\n \"\r\n label=\"Sanitize HTML\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsIcs()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"config()['summary'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:summary')\"\r\n (ngModelChange)=\"onConfigFieldChange('summary', $event)\"\r\n label=\"Event summary\"\r\n [required]=\"isConfigFieldRequired('summary')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['timezone'] ?? 'UTC'\"\r\n (ngModelChange)=\"onConfigFieldChange('timezone', $event)\"\r\n label=\"Timezone\"\r\n [required]=\"isConfigFieldRequired('timezone')\"\r\n />\r\n <mt-date-field\r\n [ngModel]=\"config()['start'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('start', $event)\"\r\n label=\"Start\"\r\n [required]=\"isConfigFieldRequired('start')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n <mt-date-field\r\n [ngModel]=\"config()['end'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('end', $event)\"\r\n label=\"End\"\r\n [required]=\"isConfigFieldRequired('end')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['location'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:location')\"\r\n (ngModelChange)=\"onConfigFieldChange('location', $event)\"\r\n label=\"Location\"\r\n [required]=\"isConfigFieldRequired('location')\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full\"\r\n [ngModel]=\"config()['description'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:description')\"\r\n (ngModelChange)=\"onConfigFieldChange('description', $event)\"\r\n label=\"Description\"\r\n [required]=\"isConfigFieldRequired('description')\"\r\n rows=\"4\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsSpreadsheet()) {\r\n <div\r\n class=\"mt-4 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"mb-3 flex items-center justify-between gap-3\">\r\n <div class=\"fp-ae-label\">Sheets</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add sheet\"\r\n (onClick)=\"addConvertToFileSheet()\"\r\n />\r\n </div>\r\n <div class=\"grid gap-3\">\r\n @for (sheet of convertToFileSheetRows(); track sheet.index) {\r\n <div\r\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n <div\r\n class=\"grid gap-3 md:grid-cols-[minmax(140px,0.45fr)_minmax(0,1fr)_minmax(180px,0.6fr)_auto]\"\r\n >\r\n <mt-text-field\r\n [ngModel]=\"sheet.name\"\r\n (ngModelChange)=\"\r\n updateConvertToFileSheet(\r\n sheet.index,\r\n 'name',\r\n $event\r\n )\r\n \"\r\n label=\"Sheet name\"\r\n [required]=\"isConfigFieldRequired('sheets')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"sheet.sourceExpression\"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:sheets:' + sheet.index + ':source'\r\n )\r\n \"\r\n (ngModelChange)=\"\r\n updateConvertToFileSheet(\r\n sheet.index,\r\n 'sourceExpression',\r\n $event\r\n )\r\n \"\r\n label=\"Source\"\r\n [required]=\"isConfigFieldRequired('sheets')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"sheet.columnsText\"\r\n (ngModelChange)=\"\r\n updateConvertToFileSheet(\r\n sheet.index,\r\n 'columns',\r\n $event\r\n )\r\n \"\r\n label=\"Columns\"\r\n [required]=\"isConfigFieldRequired('sheets')\"\r\n />\r\n <div class=\"flex items-end justify-end\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n icon=\"general.trash-01\"\r\n tooltip=\"Remove sheet\"\r\n (onClick)=\"removeConvertToFileSheet(sheet.index)\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n } @empty {\r\n <div\r\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n Single sheet uses the selected data source.\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsText()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-textarea-field\r\n class=\"w-full\"\r\n [ngModel]=\"config()['content'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:content')\"\r\n (ngModelChange)=\"onConfigFieldChange('content', $event)\"\r\n label=\"Content\"\r\n [required]=\"isConfigFieldRequired('content')\"\r\n rows=\"7\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['encoding'] ?? 'utf-8'\"\r\n [readonly]=\"true\"\r\n label=\"Encoding\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsBase64()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"config()['base64Expression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:base64Expression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('base64Expression', $event)\r\n \"\r\n label=\"Base64 source\"\r\n [required]=\"isConfigFieldRequired('base64Expression')\"\r\n rows=\"5\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['extension'] ?? convertToFileExtension()\"\r\n (ngModelChange)=\"onConfigFieldChange('extension', $event)\"\r\n label=\"Extension\"\r\n [required]=\"isConfigFieldRequired('extension')\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"DataTransform\") {\r\n @if (sectionInMain(\"dataTransform\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">\r\n {{ dataTransformActionLabel() }}\r\n </div>\r\n @if (dataTransformUnavailableReason(); as reason) {\r\n <div\r\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ reason }}\r\n </div>\r\n }\r\n\r\n @if (\r\n !dataTransformIsMerge() && dataTransformUsesKnownActionEditor()\r\n ) {\r\n <div class=\"grid gap-3\">\r\n <div class=\"grid gap-3 md:grid-cols-[minmax(220px,0.45fr)_1fr]\">\r\n <mt-select-field\r\n [ngModel]=\"dataTransformSourcePreset()\"\r\n (ngModelChange)=\"onDataTransformSourcePresetChange($event)\"\r\n [label]=\"dataTransformSourceLabel()\"\r\n hint=\"Choose which list this action should transform.\"\r\n [required]=\"isConfigFieldRequired('sourceExpression')\"\r\n [options]=\"dataTransformSourceOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (showDataTransformSourceExpression()) {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"dataTransformSourceExpression()\"\r\n (focusin)=\"setExpressionTarget('config:sourceExpression')\"\r\n (ngModelChange)=\"\r\n onDataTransformSourceExpressionChange($event)\r\n \"\r\n [label]=\"dataTransformSourceExpressionLabel()\"\r\n hint=\"Must resolve to an array of items.\"\r\n [required]=\"isConfigFieldRequired('sourceExpression')\"\r\n />\r\n } @else {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"fp-ae-label mb-1\">Selected input</div>\r\n <div\r\n class=\"truncate font-mono text-sm text-(--p-text-color)\"\r\n >\r\n {{ dataTransformSourceSummary() }}\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <div class=\"flex flex-wrap gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.search-sm\"\r\n label=\"Preview source\"\r\n [disabled]=\"!dataTransformSourceExpression()\"\r\n (onClick)=\"previewDataTransformSourceExpression()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (expressionPreview(); as preview) {\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Source preview</div>\r\n <pre class=\"fp-ae-code\">{{ schemaText(preview) }}</pre>\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsFilter()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n @if (dataTransformUsesStructuredConditionEditor()) {\r\n <div class=\"fp-ae-label\">Keep item when</div>\r\n <div class=\"grid gap-3 md:grid-cols-[1fr_180px_1fr]\">\r\n <mt-text-field\r\n [ngModel]=\"dataTransformConditionFieldText('left')\"\r\n (focusin)=\"setExpressionTarget('config:condition:left')\"\r\n (ngModelChange)=\"\r\n onDataTransformConditionFieldChange('left', $event)\r\n \"\r\n label=\"Left value\"\r\n hint=\"Field, value, or expression from each item.\"\r\n [required]=\"isConfigFieldRequired('condition.left')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"\r\n dataTransformConditionFieldValue('operator') || 'equals'\r\n \"\r\n (ngModelChange)=\"\r\n onDataTransformConditionFieldChange('operator', $event)\r\n \"\r\n label=\"Operator\"\r\n [required]=\"isConfigFieldRequired('condition.operator')\"\r\n [options]=\"ifOperatorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"dataTransformConditionFieldText('right')\"\r\n (focusin)=\"setExpressionTarget('config:condition:right')\"\r\n (ngModelChange)=\"\r\n onDataTransformConditionFieldChange('right', $event)\r\n \"\r\n label=\"Right value\"\r\n hint=\"Value or expression to compare against.\"\r\n [required]=\"isConfigFieldRequired('condition.right')\"\r\n />\r\n </div>\r\n } @else {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"dataTransformConditionText()\"\r\n (focusin)=\"setExpressionTarget('config:condition')\"\r\n (ngModelChange)=\"onDataTransformConditionChange($event)\"\r\n label=\"Keep item when\"\r\n hint=\"Condition expression or JSON supplied by the backend catalog.\"\r\n [required]=\"isConfigFieldRequired('condition')\"\r\n rows=\"4\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsSort()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['sortBy'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:sortBy')\"\r\n (ngModelChange)=\"onConfigFieldChange('sortBy', $event)\"\r\n label=\"Sort by field\"\r\n hint=\"Field path inside each item, for example createdAt.\"\r\n [required]=\"isConfigFieldRequired('sortBy')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['sortExpression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:sortExpression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('sortExpression', $event)\r\n \"\r\n label=\"Custom sort expression\"\r\n hint=\"Optional expression when a simple field path is not enough.\"\r\n [required]=\"isConfigFieldRequired('sortExpression')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['direction'] ?? 'asc'\"\r\n (ngModelChange)=\"onConfigFieldChange('direction', $event)\"\r\n label=\"Direction\"\r\n [required]=\"isConfigFieldRequired('direction')\"\r\n [options]=\"dataTransformDirectionOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['valueType'] ?? 'string'\"\r\n (ngModelChange)=\"onConfigFieldChange('valueType', $event)\"\r\n label=\"Value type\"\r\n [required]=\"isConfigFieldRequired('valueType')\"\r\n [options]=\"dataTransformValueTypeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsLimit()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['offset']) ?? 0\"\r\n (ngModelChange)=\"onConfigFieldChange('offset', $event)\"\r\n label=\"Skip first\"\r\n [required]=\"isConfigFieldRequired('offset')\"\r\n [min]=\"0\"\r\n />\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['limit'])\"\r\n (ngModelChange)=\"onConfigFieldChange('limit', $event)\"\r\n label=\"Take at most\"\r\n [required]=\"isConfigFieldRequired('limit')\"\r\n [min]=\"1\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsMapFields()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-select-field\r\n [ngModel]=\"config()['mode'] ?? 'merge'\"\r\n (ngModelChange)=\"onConfigFieldChange('mode', $event)\"\r\n label=\"Field mode\"\r\n [required]=\"isConfigFieldRequired('mode')\"\r\n [options]=\"dataTransformMapModeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'fields',\r\n rows: fieldsRows(),\r\n keyLabel: 'Output field',\r\n valueLabel: 'Value or expression',\r\n addLabel: 'Add mapped field',\r\n emptyText:\r\n 'No fields mapped yet. Add an output field to create or update item data.',\r\n keyHint: 'Name of the field in the output item.',\r\n valueHint:\r\n 'Use a fixed value or an expression from each item.',\r\n required: isConfigFieldRequired('fields'),\r\n }\r\n \"\r\n />\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsAggregate()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['groupBy'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:groupBy')\"\r\n (ngModelChange)=\"onConfigFieldChange('groupBy', $event)\"\r\n label=\"Group items by\"\r\n hint=\"Optional field path or expression used to group results.\"\r\n [required]=\"isConfigFieldRequired('groupBy')\"\r\n />\r\n <div class=\"rounded-md border border-surface-200 p-3\">\r\n <div\r\n class=\"mb-3 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <div class=\"fp-ae-label\">Summary fields</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add summary\"\r\n (onClick)=\"addDataTransformAggregation()\"\r\n />\r\n </div>\r\n <div class=\"space-y-3\">\r\n @for (\r\n item of dataTransformAggregationRows();\r\n track item.index\r\n ) {\r\n <div\r\n class=\"grid gap-2 rounded-md bg-surface-50 p-3 md:grid-cols-[minmax(160px,0.8fr)_minmax(180px,0.9fr)_1fr_minmax(150px,0.7fr)_auto]\"\r\n >\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"item.key\"\r\n (ngModelChange)=\"\r\n updateDataTransformAggregation(\r\n item.index,\r\n 'key',\r\n $event\r\n )\r\n \"\r\n label=\"Output field\"\r\n hint=\"Name saved in the summary output.\"\r\n [required]=\"isConfigFieldRequired('aggregations')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"item.operation\"\r\n (ngModelChange)=\"\r\n updateDataTransformAggregation(\r\n item.index,\r\n 'operation',\r\n $event\r\n )\r\n \"\r\n label=\"Calculation\"\r\n [required]=\"isConfigFieldRequired('aggregations')\"\r\n [options]=\"dataTransformAggregationOperationOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"item.field\"\r\n [readonly]=\"\r\n !dataTransformAggregationNeedsField(item.operation)\r\n \"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:aggregations:' + item.index + ':field'\r\n )\r\n \"\r\n (ngModelChange)=\"\r\n updateDataTransformAggregation(\r\n item.index,\r\n 'field',\r\n $event\r\n )\r\n \"\r\n label=\"Field or expression\"\r\n [hint]=\"\r\n dataTransformAggregationFieldHint(item.operation)\r\n \"\r\n [required]=\"\r\n isConfigFieldRequired('aggregations') &&\r\n dataTransformAggregationNeedsField(item.operation)\r\n \"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"item.valueType\"\r\n (ngModelChange)=\"\r\n updateDataTransformAggregation(\r\n item.index,\r\n 'valueType',\r\n $event\r\n )\r\n \"\r\n label=\"Value type\"\r\n [required]=\"isConfigFieldRequired('aggregations')\"\r\n [options]=\"dataTransformValueTypeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-button\r\n class=\"self-end\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n label=\"Remove\"\r\n (onClick)=\"removeDataTransformAggregation(item.index)\"\r\n />\r\n </div>\r\n } @empty {\r\n <div\r\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n No summary fields yet. Add a summary such as count,\r\n total, average, minimum, or maximum.\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsMerge()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"config()['leftExpression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:leftExpression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('leftExpression', $event)\r\n \"\r\n label=\"First list\"\r\n hint=\"Expression that resolves to the first list of items.\"\r\n [required]=\"isConfigFieldRequired('leftExpression')\"\r\n rows=\"3\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"config()['rightExpression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:rightExpression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('rightExpression', $event)\r\n \"\r\n label=\"Second list\"\r\n hint=\"Expression that resolves to the second list of items.\"\r\n [required]=\"isConfigFieldRequired('rightExpression')\"\r\n rows=\"3\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['strategy'] ?? 'append'\"\r\n (ngModelChange)=\"onConfigFieldChange('strategy', $event)\"\r\n label=\"Merge strategy\"\r\n [required]=\"isConfigFieldRequired('strategy')\"\r\n [options]=\"dataTransformMergeStrategyOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (dataTransformUsesKeyedMerge()) {\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['leftKey'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('leftKey', $event)\"\r\n label=\"First list key\"\r\n [required]=\"isConfigFieldRequired('leftKey')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['rightKey'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('rightKey', $event)\"\r\n label=\"Second list key\"\r\n [required]=\"isConfigFieldRequired('rightKey')\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (!dataTransformUsesKnownActionEditor()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n @for (field of dataTransformGenericFields(); track field.key) {\r\n @if (field.enumValues.length) {\r\n <mt-select-field\r\n [ngModel]=\"config()[field.key] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n [options]=\"enumOptions(field.enumValues)\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n } @else if (field.type === \"number\") {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()[field.key])\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n } @else {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"fieldText(field.key)\"\r\n (focusin)=\"setExpressionTarget('config:' + field.key)\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n }\r\n } @empty {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"dataTransformActionKey()\"\r\n [readonly]=\"true\"\r\n label=\"Action\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['maxOutputItems'])\"\r\n (ngModelChange)=\"onConfigFieldChange('maxOutputItems', $event)\"\r\n label=\"Output limit\"\r\n [required]=\"isConfigFieldRequired('maxOutputItems')\"\r\n [min]=\"1\"\r\n />\r\n <div\r\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3 md:col-span-2\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Output</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (field of dataTransformOutputFieldLabels; track field) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ field }}</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"LoopOverItems\") {\r\n @if (sectionInMain(\"loopOverItems\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Loop over items</div>\r\n\r\n <div class=\"grid gap-3\">\r\n <div class=\"grid gap-3 md:grid-cols-[minmax(220px,0.45fr)_1fr]\">\r\n <mt-select-field\r\n [ngModel]=\"loopSourceMode()\"\r\n (ngModelChange)=\"onLoopSourceModeChange($event)\"\r\n label=\"List source\"\r\n hint=\"Choose an output array or type an array expression.\"\r\n [required]=\"isConfigFieldRequired('itemsExpression')\"\r\n [options]=\"loopSourceModeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (showLoopItemsExpression()) {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"loopItemsExpression()\"\r\n (focusin)=\"setExpressionTarget('config:itemsExpression')\"\r\n (ngModelChange)=\"onLoopItemsExpressionChange($event)\"\r\n label=\"Array expression\"\r\n hint=\"Must resolve to an array.\"\r\n [required]=\"isConfigFieldRequired('itemsExpression')\"\r\n />\r\n } @else {\r\n <mt-select-field\r\n [ngModel]=\"loopSelectedOutputArray()\"\r\n (ngModelChange)=\"onLoopOutputArrayChange($event)\"\r\n label=\"Output array\"\r\n hint=\"Arrays exposed by previous connected steps.\"\r\n [required]=\"isConfigFieldRequired('itemsExpression')\"\r\n [options]=\"loopOutputArrayOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n }\r\n </div>\r\n <div class=\"flex flex-wrap gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.search-sm\"\r\n label=\"Preview array\"\r\n [disabled]=\"!loopItemsExpression()\"\r\n (onClick)=\"previewLoopItemsExpression()\"\r\n />\r\n </div>\r\n </div>\r\n\r\n @if (expressionPreview(); as preview) {\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Expression preview</div>\r\n <pre class=\"fp-ae-code\">{{ schemaText(preview) }}</pre>\r\n </div>\r\n }\r\n\r\n <div class=\"mt-4 border-t border-surface-100 pt-4\">\r\n <div class=\"fp-ae-label mb-3\">Run settings</div>\r\n <div class=\"grid gap-3 md:grid-cols-2 xl:grid-cols-4\">\r\n <mt-number-field\r\n [ngModel]=\"\r\n numberValue(\r\n config()['maxItems'] ?? config()['maxIterations']\r\n )\r\n \"\r\n (ngModelChange)=\"onConfigFieldChange('maxItems', $event)\"\r\n label=\"Item limit\"\r\n hint=\"Optional maximum number of items to process.\"\r\n [required]=\"isConfigFieldRequired('maxItems')\"\r\n [min]=\"1\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"loopFailureBehavior()\"\r\n (ngModelChange)=\"onLoopFailureBehaviorChange($event)\"\r\n label=\"When an item fails\"\r\n [required]=\"isConfigFieldRequired('failureBehavior')\"\r\n [options]=\"loopFailureBehaviorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['emptyBehavior'] ?? 'done'\"\r\n (ngModelChange)=\"onConfigFieldChange('emptyBehavior', $event)\"\r\n label=\"When list is empty\"\r\n [required]=\"isConfigFieldRequired('emptyBehavior')\"\r\n [options]=\"loopEmptyBehaviorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-toggle-field\r\n class=\"self-end pb-1\"\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['collectResults'] === true\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('collectResults', $event === true)\r\n \"\r\n label=\"Save item results\"\r\n hint=\"Stores a bounded summary for the done route.\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <details class=\"mt-4 border-t border-surface-100 pt-3\">\r\n <summary\r\n class=\"cursor-pointer text-sm font-semibold text-(--p-text-color)\"\r\n >\r\n Advanced runtime details\r\n </summary>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-3\">\r\n <div>\r\n <div class=\"fp-ae-label mb-1\">Current item token</div>\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2 font-mono text-sm text-(--p-text-color)\"\r\n >\r\n $item.json\r\n </div>\r\n </div>\r\n <div>\r\n <div class=\"fp-ae-label mb-1\">Loop context token</div>\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2 font-mono text-sm text-(--p-text-color)\"\r\n >\r\n $loop.current\r\n </div>\r\n </div>\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['concurrency']) ?? 1\"\r\n (ngModelChange)=\"onLoopConcurrencyChange($event)\"\r\n label=\"Concurrency\"\r\n hint=\"This deployment supports 1.\"\r\n [required]=\"isConfigFieldRequired('concurrency')\"\r\n [min]=\"1\"\r\n [max]=\"1\"\r\n />\r\n </div>\r\n </details>\r\n </section>\r\n }\r\n }\r\n @case (\"FlowPlusCommit\") {\r\n @if (sectionInMain(\"flowplusCommit\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">FlowPlus commit</div>\r\n <p class=\"fp-ae-copy\">\r\n This node is the explicit module-data write boundary. Approval\r\n does not commit data unless this node is reached.\r\n </p>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['targetModule'] ?? ''\"\r\n (ngModelChange)=\"onModuleChange($event)\"\r\n label=\"Module\"\r\n hint=\"Module whose records will be written by this explicit FlowPlus commit.\"\r\n [required]=\"isConfigFieldRequired('targetModule')\"\r\n [options]=\"moduleOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['operation'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('operation', $event)\"\r\n label=\"Operation\"\r\n hint=\"Write operation supported by the selected backend module schema.\"\r\n [required]=\"isConfigFieldRequired('operation')\"\r\n [options]=\"operationOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <mt-text-field\r\n class=\"mt-3 font-mono\"\r\n [ngModel]=\"config()['idempotencyKey'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:idempotencyKey')\"\r\n (ngModelChange)=\"onConfigFieldChange('idempotencyKey', $event)\"\r\n label=\"Idempotency key\"\r\n hint=\"Optional stable key used by the backend to prevent duplicate writes.\"\r\n [required]=\"isConfigFieldRequired('idempotencyKey')\"\r\n />\r\n @if (selectedModuleFields().length) {\r\n <div class=\"mt-3 space-y-2\">\r\n <div class=\"fp-ae-section-title\">Module schema</div>\r\n <div class=\"grid gap-2 md:grid-cols-2\">\r\n @for (field of selectedModuleFields(); track field.key) {\r\n <div class=\"fp-ae-kv\">\r\n <span>{{ field.displayName ?? field.key }}</span>\r\n <strong\r\n >{{ field.viewType ?? \"Value\" }}\r\n @if (field.required) {\r\n *\r\n }\r\n </strong>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n <div class=\"mt-3\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'mapping',\r\n rows: mappingRows(),\r\n keyLabel: 'Module field',\r\n valueLabel: 'Expression / value',\r\n addLabel: 'Add module field',\r\n required: isConfigFieldRequired('mapping'),\r\n }\r\n \"\r\n />\r\n </div>\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Validate mapping\"\r\n (onClick)=\"validateCommitMapping()\"\r\n />\r\n </div>\r\n @if (commitValidation(); as result) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n {{\r\n result.isValid === false\r\n ? \"Mapping invalid\"\r\n : \"Mapping accepted by helper\"\r\n }}\r\n @for (issue of resultIssues(result); track $index) {\r\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\r\n {{ issue }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n @if (sectionInMain(\"credentials\")) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Credential</div>\r\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\r\n </div>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"WebhookResponse\") {\r\n @if (sectionInMain(\"webhookResponse\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Webhook response</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['statusCode']) ?? 200\"\r\n (ngModelChange)=\"onConfigFieldChange('statusCode', $event)\"\r\n label=\"Status code\"\r\n hint=\"HTTP status code sent back by the webhook response node.\"\r\n [required]=\"isConfigFieldRequired('statusCode')\"\r\n [min]=\"100\"\r\n [max]=\"599\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['responseMode'] ?? 'json'\"\r\n (ngModelChange)=\"onConfigFieldChange('responseMode', $event)\"\r\n label=\"Response mode\"\r\n hint=\"How the webhook response body should be serialized.\"\r\n [required]=\"isConfigFieldRequired('responseMode')\"\r\n [options]=\"responseModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"mt-3\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'headers',\r\n rows: headerRows(),\r\n keyLabel: 'Header',\r\n valueLabel: 'Value',\r\n addLabel: 'Add header',\r\n }\r\n \"\r\n />\r\n </div>\r\n <mt-textarea-field\r\n class=\"mt-3 w-full font-mono\"\r\n [ngModel]=\"valueText(config()['body'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:body')\"\r\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\r\n label=\"Body\"\r\n hint=\"Body returned to the webhook caller. Expressions can use current execution data.\"\r\n [required]=\"isConfigFieldRequired('body')\"\r\n rows=\"6\"\r\n />\r\n </section>\r\n }\r\n }\r\n @case (\"CallAutomation\") {\r\n @if (sectionInMain(\"callAutomation\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Subworkflow</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['targetAutomationId'] ?? ''\"\r\n (ngModelChange)=\"onSubworkflowTargetChange($event)\"\r\n label=\"Target automation\"\r\n hint=\"Backend-provided automation candidate. Tenant and recursion validation stay on the backend.\"\r\n [required]=\"isConfigFieldRequired('targetAutomationId')\"\r\n [options]=\"subworkflowAutomationOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['revisionMode'] ?? 'Active'\"\r\n (ngModelChange)=\"onConfigFieldChange('revisionMode', $event)\"\r\n label=\"Revision mode\"\r\n hint=\"Choose which published or active child revision to call.\"\r\n [required]=\"isConfigFieldRequired('revisionMode')\"\r\n [options]=\"revisionModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (config()[\"revisionMode\"] === \"SpecificRevision\") {\r\n <mt-text-field\r\n [ngModel]=\"config()['specificRevisionId'] ?? ''\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('specificRevisionId', $event)\r\n \"\r\n label=\"Specific revision id\"\r\n hint=\"Required only when revision mode is Specific revision.\"\r\n [required]=\"isConfigFieldRequired('specificRevisionId')\"\r\n />\r\n }\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['waitForCompletion'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('waitForCompletion', $event === true)\r\n \"\r\n label=\"Wait for completion\"\r\n hint=\"When disabled, the parent continues after dispatch and backend records the child linkage.\"\r\n />\r\n </div>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['inputMappingJson'] ?? '{}')\"\r\n (focusin)=\"setExpressionTarget('config:inputMappingJson')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('inputMappingJson', $event)\r\n \"\r\n label=\"Input mapping JSON\"\r\n hint=\"JSON object or expression map sent into the child automation input.\"\r\n [required]=\"isConfigFieldRequired('inputMappingJson')\"\r\n rows=\"7\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['outputMappingJson'] ?? '{}')\"\r\n (focusin)=\"setExpressionTarget('config:outputMappingJson')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('outputMappingJson', $event)\r\n \"\r\n label=\"Output mapping JSON\"\r\n hint=\"JSON object or expression map for child output/status returned to the parent.\"\r\n [required]=\"isConfigFieldRequired('outputMappingJson')\"\r\n rows=\"7\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"Subworkflow\") {\r\n @if (sectionInMain(\"callAutomation\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Subworkflow</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['targetAutomationId'] ?? ''\"\r\n (ngModelChange)=\"onSubworkflowTargetChange($event)\"\r\n label=\"Target automation\"\r\n hint=\"Backend-provided automation candidate. Tenant and recursion validation stay on the backend.\"\r\n [required]=\"isConfigFieldRequired('targetAutomationId')\"\r\n [options]=\"subworkflowAutomationOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['revisionMode'] ?? 'Active'\"\r\n (ngModelChange)=\"onConfigFieldChange('revisionMode', $event)\"\r\n label=\"Revision mode\"\r\n hint=\"Choose which published or active child revision to call.\"\r\n [required]=\"isConfigFieldRequired('revisionMode')\"\r\n [options]=\"revisionModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (config()[\"revisionMode\"] === \"SpecificRevision\") {\r\n <mt-text-field\r\n [ngModel]=\"config()['specificRevisionId'] ?? ''\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('specificRevisionId', $event)\r\n \"\r\n label=\"Specific revision id\"\r\n hint=\"Required only when revision mode is Specific revision.\"\r\n [required]=\"isConfigFieldRequired('specificRevisionId')\"\r\n />\r\n }\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['waitForCompletion'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('waitForCompletion', $event === true)\r\n \"\r\n label=\"Wait for completion\"\r\n hint=\"When disabled, the parent continues after dispatch and backend records the child linkage.\"\r\n />\r\n </div>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['inputMappingJson'] ?? '{}')\"\r\n (focusin)=\"setExpressionTarget('config:inputMappingJson')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('inputMappingJson', $event)\r\n \"\r\n label=\"Input mapping JSON\"\r\n hint=\"JSON object or expression map sent into the child automation input.\"\r\n [required]=\"isConfigFieldRequired('inputMappingJson')\"\r\n rows=\"7\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['outputMappingJson'] ?? '{}')\"\r\n (focusin)=\"setExpressionTarget('config:outputMappingJson')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('outputMappingJson', $event)\r\n \"\r\n label=\"Output mapping JSON\"\r\n hint=\"JSON object or expression map for child output/status returned to the parent.\"\r\n [required]=\"isConfigFieldRequired('outputMappingJson')\"\r\n rows=\"7\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"ParallelStart\") {\r\n @if (sectionInMain(\"parallelStart\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div\r\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-50 px-4 py-3\"\r\n >\r\n <div class=\"fp-ae-section-title !m-0\">Parallel start</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add branch\"\r\n (onClick)=\"addParallelBranch()\"\r\n />\r\n </div>\r\n <div class=\"space-y-3 p-4\">\r\n @for (\r\n branch of parallelBranches();\r\n track branch.key;\r\n let i = $index\r\n ) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"grid gap-3 md:grid-cols-[160px_1fr_1fr_auto]\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"branch.key\"\r\n (ngModelChange)=\"updateParallelBranch(i, 'key', $event)\"\r\n label=\"Branch key\"\r\n hint=\"Persisted route key. Do not use array index names.\"\r\n [required]=\"isConfigFieldRequired('branches')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"branch.label\"\r\n (ngModelChange)=\"updateParallelBranch(i, 'label', $event)\"\r\n label=\"Label\"\r\n hint=\"Visual branch label. Changing it does not change existing routes.\"\r\n [required]=\"isConfigFieldRequired('branches')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"branch.description\"\r\n (ngModelChange)=\"\r\n updateParallelBranch(i, 'description', $event)\r\n \"\r\n label=\"Description\"\r\n hint=\"Optional branch note for reviewers.\"\r\n [required]=\"false\"\r\n />\r\n <div class=\"flex items-end gap-1\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"arrow.arrow-up\"\r\n tooltip=\"Move up\"\r\n (onClick)=\"moveParallelBranch(i, -1)\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"arrow.arrow-down\"\r\n tooltip=\"Move down\"\r\n (onClick)=\"moveParallelBranch(i, 1)\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n icon=\"general.trash-01\"\r\n tooltip=\"Remove branch\"\r\n (onClick)=\"removeParallelBranch(i)\"\r\n />\r\n </div>\r\n </div>\r\n <div\r\n class=\"mt-3 flex flex-wrap items-center gap-1.5 text-[11px]\"\r\n >\r\n <span\r\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-50 px-2 font-mono font-semibold text-(--p-text-color)\"\r\n >{{ branch.key }}</span\r\n >\r\n <span\r\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 font-medium text-(--p-text-muted-color)\"\r\n >{{ branch.routeCount }} connected route{{\r\n branch.routeCount === 1 ? \"\" : \"s\"\r\n }}</span\r\n >\r\n </div>\r\n </div>\r\n }\r\n @if (parallelBranches().length === 0) {\r\n <p class=\"fp-ae-copy\">\r\n Add at least two stable branch keys. Backend validation blocks\r\n publish until branches are valid.\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"ParallelJoin\") {\r\n @if (sectionInMain(\"parallelJoin\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Parallel join</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['joinPolicy'] ?? 'All'\"\r\n (ngModelChange)=\"onConfigFieldChange('joinPolicy', $event)\"\r\n label=\"Join policy\"\r\n hint=\"Backend policy used to decide when branch wait is complete.\"\r\n [required]=\"isConfigFieldRequired('joinPolicy')\"\r\n [options]=\"joinPolicyOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (config()[\"joinPolicy\"] === \"Threshold\") {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['threshold'])\"\r\n (ngModelChange)=\"onConfigFieldChange('threshold', $event)\"\r\n label=\"Threshold\"\r\n hint=\"Minimum completed branch count required for Threshold policy.\"\r\n [required]=\"isConfigFieldRequired('threshold')\"\r\n [min]=\"1\"\r\n />\r\n }\r\n <mt-select-field\r\n [ngModel]=\"config()['aggregationStrategy'] ?? 'MergeObjects'\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('aggregationStrategy', $event)\r\n \"\r\n label=\"Aggregation strategy\"\r\n hint=\"How backend joins branch outputs into the join node output.\"\r\n [required]=\"isConfigFieldRequired('aggregationStrategy')\"\r\n [options]=\"aggregationStrategyOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['outputTargetPath'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:outputTargetPath')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('outputTargetPath', $event)\r\n \"\r\n label=\"Output target path\"\r\n hint=\"Optional context path where joined output should be written.\"\r\n [required]=\"isConfigFieldRequired('outputTargetPath')\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"Switch\") {\r\n @if (sectionInMain(\"switch\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Switch</div>\r\n <div class=\"grid gap-4\">\r\n <div\r\n class=\"grid items-end gap-3 md:grid-cols-[minmax(0,1fr)_minmax(280px,0.7fr)]\"\r\n >\r\n <mt-select-field\r\n [ngModel]=\"config()['mode'] ?? 'value'\"\r\n (ngModelChange)=\"onConfigFieldChange('mode', $event)\"\r\n label=\"Mode\"\r\n hint=\"Backend evaluation mode: expression, rules, or source-value matching.\"\r\n [required]=\"isConfigFieldRequired('mode')\"\r\n [options]=\"switchModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-toggle-field\r\n class=\"self-end pb-1\"\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['firstMatch'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('firstMatch', $event === true)\r\n \"\r\n label=\"First match\"\r\n hint=\"Use the first matching case unless backend explicitly supports multi-match.\"\r\n />\r\n </div>\r\n @if (showSwitchSourceValue() || showSwitchExpression()) {\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n @if (showSwitchSourceValue()) {\r\n <mt-text-field\r\n [class]=\"\r\n showSwitchExpression()\r\n ? 'font-mono'\r\n : 'font-mono md:col-span-2'\r\n \"\r\n [ngModel]=\"config()['sourceValue'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:sourceValue')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('sourceValue', $event)\r\n \"\r\n label=\"Source value\"\r\n hint=\"Value or expression used for value matching.\"\r\n [required]=\"isConfigFieldRequired('sourceValue')\"\r\n />\r\n }\r\n @if (showSwitchExpression()) {\r\n <mt-text-field\r\n [class]=\"\r\n showSwitchSourceValue()\r\n ? 'font-mono'\r\n : 'font-mono md:col-span-2'\r\n \"\r\n [ngModel]=\"config()['expression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:expression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('expression', $event)\r\n \"\r\n label=\"Expression\"\r\n hint=\"Expression evaluated when mode is expression or rules.\"\r\n [required]=\"isConfigFieldRequired('expression')\"\r\n />\r\n }\r\n </div>\r\n }\r\n <div\r\n class=\"grid items-end gap-3 md:grid-cols-[minmax(0,1fr)_minmax(280px,0.7fr)]\"\r\n >\r\n @if (showSwitchDefaultOutputKey()) {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['defaultOutputKey'] ?? 'default'\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('defaultOutputKey', $event)\r\n \"\r\n label=\"Default output key\"\r\n hint=\"Stable default route key used when no case matches.\"\r\n [required]=\"isConfigFieldRequired('defaultOutputKey')\"\r\n />\r\n }\r\n <mt-toggle-field\r\n [class]=\"\r\n showSwitchDefaultOutputKey()\r\n ? 'self-end pb-1'\r\n : 'self-end pb-1 md:col-span-2'\r\n \"\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['includeDefaultOutput'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('includeDefaultOutput', $event === true)\r\n \"\r\n label=\"Include default output\"\r\n hint=\"Expose the default route output key in the canvas.\"\r\n />\r\n </div>\r\n </div>\r\n </section>\r\n <section class=\"fp-ae-panel\">\r\n <div\r\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-50 px-4 py-3\"\r\n >\r\n <div class=\"fp-ae-section-title !m-0\">Switch cases</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add case\"\r\n (onClick)=\"addSwitchCase()\"\r\n />\r\n </div>\r\n <div class=\"space-y-3 p-4\">\r\n @for (item of switchCases(); track item.key; let i = $index) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div\r\n class=\"flex flex-wrap items-start justify-between gap-3 border-b border-surface-100 pb-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <div class=\"flex flex-wrap items-center gap-1.5\">\r\n <span\r\n class=\"text-sm font-semibold text-(--p-text-color)\"\r\n >Case {{ i + 1 }}</span\r\n >\r\n <span\r\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-50 px-2 font-mono text-[11px] font-semibold text-(--p-text-color)\"\r\n >{{ item.routeKey }}</span\r\n >\r\n <span\r\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >{{ item.routeCount }} connected route{{\r\n item.routeCount === 1 ? \"\" : \"s\"\r\n }}</span\r\n >\r\n </div>\r\n <div\r\n class=\"mt-1 truncate text-xs font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ item.label || item.routeKey }}\r\n </div>\r\n </div>\r\n <div class=\"flex shrink-0 items-center gap-1\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"arrow.arrow-up\"\r\n tooltip=\"Move up\"\r\n (onClick)=\"moveSwitchCase(i, -1)\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"arrow.arrow-down\"\r\n tooltip=\"Move down\"\r\n (onClick)=\"moveSwitchCase(i, 1)\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n icon=\"general.trash-01\"\r\n tooltip=\"Remove case\"\r\n (onClick)=\"removeSwitchCase(i)\"\r\n />\r\n </div>\r\n </div>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"item.label\"\r\n (ngModelChange)=\"updateSwitchCase(i, 'label', $event)\"\r\n label=\"Case name\"\r\n hint=\"Shown in route labels.\"\r\n [required]=\"isConfigFieldRequired('cases')\"\r\n />\r\n @if (showSwitchCaseValue()) {\r\n <mt-text-field\r\n [ngModel]=\"item.value\"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:cases:' + item.key + ':value'\r\n )\r\n \"\r\n (ngModelChange)=\"updateSwitchCase(i, 'value', $event)\"\r\n label=\"Match value\"\r\n hint=\"Compared with the source value.\"\r\n [required]=\"isConfigFieldRequired('cases')\"\r\n />\r\n }\r\n @if (showSwitchCaseCondition()) {\r\n <mt-text-field\r\n class=\"font-mono md:col-span-2\"\r\n [ngModel]=\"item.condition\"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:cases:' + item.key + ':condition'\r\n )\r\n \"\r\n (ngModelChange)=\"\r\n updateSwitchCase(i, 'condition', $event)\r\n \"\r\n label=\"Rule condition\"\r\n hint=\"When true, this case is selected.\"\r\n [required]=\"isConfigFieldRequired('cases')\"\r\n />\r\n }\r\n </div>\r\n <details\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-xs text-(--p-text-muted-color)\"\r\n >\r\n <summary class=\"cursor-pointer font-medium\">\r\n Advanced route settings\r\n </summary>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"item.key\"\r\n (ngModelChange)=\"updateSwitchCase(i, 'key', $event)\"\r\n label=\"Route key\"\r\n hint=\"Stable output key for connected routes.\"\r\n [required]=\"isConfigFieldRequired('cases')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"item.expression\"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:cases:' + item.key + ':expression'\r\n )\r\n \"\r\n (ngModelChange)=\"\r\n updateSwitchCase(i, 'expression', $event)\r\n \"\r\n label=\"Advanced condition expression\"\r\n hint=\"Optional true or false expression for this case.\"\r\n [required]=\"false\"\r\n />\r\n </div>\r\n <div\r\n class=\"mt-3 inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 font-medium\"\r\n >\r\n Visual order {{ i + 1 }}\r\n </div>\r\n </details>\r\n </div>\r\n }\r\n @if (switchCases().length === 0) {\r\n <p class=\"fp-ae-copy\">\r\n Add a case for each route this switch can select.\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"Stop\") {\r\n @if (sectionInMain(\"stop\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">End execution</div>\r\n @if (\r\n supportsConfigKey(\"status\") ||\r\n supportsConfigKey(\"message\") ||\r\n supportsConfigKey(\"output\")\r\n ) {\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n @if (supportsConfigKey(\"status\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['status'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('status', $event)\"\r\n label=\"Result status\"\r\n hint=\"Terminal status emitted by the backend stop node.\"\r\n />\r\n }\r\n @if (supportsConfigKey(\"message\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['message'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message')\"\r\n (ngModelChange)=\"onConfigFieldChange('message', $event)\"\r\n label=\"Message\"\r\n hint=\"Optional message or reason saved with the terminal result.\"\r\n />\r\n }\r\n @if (supportsConfigKey(\"output\")) {\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['output'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:output')\"\r\n (ngModelChange)=\"onConfigFieldChange('output', $event)\"\r\n label=\"Output payload\"\r\n hint=\"Terminal output payload when exposed by the backend schema.\"\r\n rows=\"5\"\r\n />\r\n }\r\n </div>\r\n } @else {\r\n <p class=\"fp-ae-copy\">\r\n Stop nodes use their backend defaults and do not expose extra\r\n configuration.\r\n </p>\r\n }\r\n </section>\r\n }\r\n }\r\n }\r\n\r\n @if (sectionInAdvanced(\"policy\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">\r\n Retry, timeout, and failure handling\r\n </div>\r\n <div class=\"grid gap-3 md:grid-cols-3\">\r\n <mt-number-field\r\n [ngModel]=\"policyNumberValue('timeoutPolicyJson', 'timeoutSeconds')\"\r\n (ngModelChange)=\"\r\n updatePolicyNumber('timeoutPolicyJson', 'timeoutSeconds', $event)\r\n \"\r\n label=\"Step timeout (seconds)\"\r\n hint=\"How long this step may run before Automation Engine marks it timed out.\"\r\n [min]=\"1\"\r\n />\r\n <mt-number-field\r\n [ngModel]=\"policyNumberValue('retryPolicyJson', 'maxAttempts')\"\r\n (ngModelChange)=\"\r\n updatePolicyNumber('retryPolicyJson', 'maxAttempts', $event)\r\n \"\r\n label=\"Max attempts\"\r\n hint=\"Total tries including the first run. Use 1 for no retry.\"\r\n [min]=\"1\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"errorPolicyMode()\"\r\n (ngModelChange)=\"updateErrorPolicyMode($event)\"\r\n label=\"When this step fails\"\r\n hint=\"Choose whether failures pause for recovery, use the failure path, or fail the workflow.\"\r\n [options]=\"errorPolicyOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n @if (sectionHasAdvancedEmptyState(\"policy\")) {\r\n <p class=\"fp-ae-copy mt-3\">\r\n Backend defaults apply until you override a timeout, retry count, or\r\n failure rule here.\r\n </p>\r\n }\r\n </section>\r\n }\r\n\r\n @if (sectionInAdvanced(\"credentials\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Auth and credentials</div>\r\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\r\n </section>\r\n }\r\n\r\n @if (sectionInAdvanced(\"mapping\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Custom data handoff</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <div>\r\n <div class=\"fp-ae-label mb-2\">Inputs sent to this step</div>\r\n @for (row of inputMappingRows(); track row.key) {\r\n <label class=\"mb-2 block space-y-1\">\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">{{\r\n row.key\r\n }}</span>\r\n <mt-text-field\r\n [ngModel]=\"valueText(row.value)\"\r\n (focusin)=\"\r\n setExpressionTarget('config:inputMapping:' + row.key)\r\n \"\r\n (ngModelChange)=\"\r\n updateJsonField('inputMappingJson', row.key, $event)\r\n \"\r\n hint=\"Value this step receives from the workflow context.\"\r\n />\r\n </label>\r\n }\r\n </div>\r\n <div>\r\n <div class=\"fp-ae-label mb-2\">Outputs saved for next steps</div>\r\n @for (row of outputMappingRows(); track row.key) {\r\n <label class=\"mb-2 block space-y-1\">\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">{{\r\n row.key\r\n }}</span>\r\n <mt-text-field\r\n [ngModel]=\"valueText(row.value)\"\r\n (focusin)=\"\r\n setExpressionTarget('config:outputMapping:' + row.key)\r\n \"\r\n (ngModelChange)=\"\r\n updateJsonField('outputMappingJson', row.key, $event)\r\n \"\r\n hint=\"Value this step exposes to the rest of the workflow.\"\r\n />\r\n </label>\r\n }\r\n </div>\r\n </div>\r\n @if (\r\n sectionHasAdvancedEmptyState(\"mapping\") &&\r\n inputMappingRows().length === 0 &&\r\n outputMappingRows().length === 0\r\n ) {\r\n <p class=\"fp-ae-copy mt-3\">\r\n Default handoff is active. This step receives the current workflow\r\n item and exposes its normal output to the next connected step. Add a\r\n custom handoff only when the business data must be reshaped.\r\n </p>\r\n }\r\n </section>\r\n }\r\n\r\n @if (sectionInAdvanced(\"triggerContext\") && trigger()) {\r\n <details class=\"fp-ae-panel\" open>\r\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\r\n Trigger payload and context\r\n </summary>\r\n <p class=\"fp-ae-copy mt-3\">\r\n Trigger payload/context is documentation for expressions only.\r\n Triggers do not edit node input or output mappings here.\r\n </p>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\r\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\r\n Config schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(configSchema()) }}</pre>\r\n </details>\r\n @if (hasTriggerPayloadSchema() && triggerPayloadSchema(); as schema) {\r\n <details\r\n class=\"rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Payload schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(schema) }}</pre>\r\n </details>\r\n }\r\n @if (authPolicySchema()) {\r\n <details\r\n class=\"rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Auth policy schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(authPolicySchema()) }}</pre>\r\n </details>\r\n }\r\n @if (triggerPayloadSample(); as sample) {\r\n <details\r\n class=\"rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Payload or request sample\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(sample) }}</pre>\r\n </details>\r\n }\r\n </div>\r\n </details>\r\n }\r\n\r\n @if (sectionInAdvanced(\"backendSchema\") && step()) {\r\n <details class=\"fp-ae-panel\" open>\r\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\r\n Backend schemas and outputs\r\n </summary>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\r\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\r\n Config schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(configSchema()) }}</pre>\r\n </details>\r\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\r\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\r\n Input schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(inputSchema()) }}</pre>\r\n </details>\r\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\r\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\r\n Output schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(outputSchema()) }}</pre>\r\n </details>\r\n <div class=\"rounded-lg border border-(--p-content-border-color) p-3\">\r\n <div class=\"fp-ae-label mb-2\">Route output keys</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (key of routeOutputKeys(); track key) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ key }}</span\r\n >\r\n }\r\n @if (routeOutputKeys().length === 0) {\r\n <span class=\"text-[12px] text-(--p-text-muted-color)\"\r\n >No outgoing route keys</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </details>\r\n }\r\n\r\n @if (sectionInAdvanced(\"schemaFields\") && configRows().length > 0) {\r\n <details class=\"fp-ae-panel\" open>\r\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\r\n Backend schema fields\r\n </summary>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n @for (field of configRows(); track field.key) {\r\n <div\r\n class=\"space-y-1.5\"\r\n [class.md:col-span-2]=\"\r\n field.type === 'object' || field.type === 'array'\r\n \"\r\n >\r\n @if (field.enumValues.length) {\r\n <mt-select-field\r\n [ngModel]=\"fieldValue(field.key)\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n [options]=\"enumOptions(field.enumValues)\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n } @else if (field.type === \"boolean\") {\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"!!fieldValue(field.key)\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange(field.key, $event === true)\r\n \"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n } @else if (field.type === \"number\") {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(fieldValue(field.key))\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n } @else if (field.type === \"date\") {\r\n <mt-date-field\r\n [ngModel]=\"fieldValue(field.key)\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n [showTime]=\"field.format !== 'date'\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n } @else if (field.type === \"object\" || field.type === \"array\") {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"valueText(fieldValue(field.key))\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n rows=\"5\"\r\n />\r\n } @else {\r\n <mt-text-field\r\n [ngModel]=\"fieldText(field.key)\"\r\n (focusin)=\"\r\n field.expressionEnabled &&\r\n setExpressionTarget('config:' + field.key)\r\n \"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n }\r\n </div>\r\n }\r\n </div>\r\n </details>\r\n }\r\n </div>\r\n</div>\r\n\r\n<ng-template #credentialSelector>\r\n @if (activeCredentialProvider(); as provider) {\r\n <div\r\n class=\"mb-4 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-4 shadow-[0_1px_2px_rgba(15,23,42,0.05)]\"\r\n >\r\n <div class=\"mb-2 text-[12px] font-bold text-(--p-text-color)\">\r\n Credential\r\n </div>\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n @if (provider.connectAvailable) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex min-h-10 max-w-full items-center gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2 text-[13px] font-bold text-(--p-text-color) shadow-sm transition-[border-color,box-shadow,background] hover:border-(--p-primary-color) hover:shadow-[0_2px_7px_rgba(15,23,42,0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n [disabled]=\"credentialOauthState() === 'loading'\"\r\n [attr.aria-busy]=\"\r\n credentialOauthState() === 'loading' ? 'true' : null\r\n \"\r\n (click)=\"connectCredentialProvider()\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[22px]\"\r\n [itemKey]=\"provider.providerKey\"\r\n [displayName]=\"provider.displayName\"\r\n [metadata]=\"credentialProviderIconMetadata(provider)\"\r\n />\r\n <span class=\"truncate\">{{ credentialConnectLabel() }}</span>\r\n </button>\r\n @if (provider.manualSetupAvailable) {\r\n <span class=\"text-[12px] text-(--p-text-muted-color)\">or</span>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-md px-1 py-1 text-[12px] font-semibold text-(--p-text-color) underline decoration-(--p-text-muted-color) underline-offset-4 transition-colors hover:text-(--p-primary-color) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n (click)=\"showManualCredentialSetup()\"\r\n >\r\n setup manually\r\n </button>\r\n }\r\n } @else if (provider.manualSetupAvailable) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex min-h-10 max-w-full items-center gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2 text-[13px] font-bold text-(--p-text-color) shadow-sm transition-[border-color,box-shadow,background] hover:border-(--p-primary-color) hover:shadow-[0_2px_7px_rgba(15,23,42,0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n (click)=\"showManualCredentialSetup()\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[22px]\"\r\n [itemKey]=\"provider.providerKey\"\r\n [displayName]=\"provider.displayName\"\r\n [metadata]=\"credentialProviderIconMetadata(provider)\"\r\n />\r\n <span class=\"truncate\"\r\n >Set up\r\n {{ provider.displayName || humanReviewProviderLabel() }}\r\n credential</span\r\n >\r\n </button>\r\n }\r\n </div>\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center gap-1.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n @if (provider.authModes?.length) {\r\n @for (mode of provider.authModes; track mode) {\r\n <span\r\n class=\"rounded-full bg-(--p-surface-100) px-2 py-0.5 font-semibold\"\r\n >\r\n {{ mode }}\r\n </span>\r\n }\r\n }\r\n @if (provider.requiredScopes?.length) {\r\n <span class=\"truncate\">\r\n Scopes: {{ provider.requiredScopes.join(\", \") }}\r\n </span>\r\n }\r\n </div>\r\n @if (provider.setupInstructions) {\r\n <p class=\"m-0 mt-2 text-[11.5px] leading-5 text-(--p-text-muted-color)\">\r\n {{ provider.setupInstructions }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (manualCredentialOpen()) {\r\n <div\r\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"manualCredentialDisplayName()\"\r\n (ngModelChange)=\"onManualCredentialDisplayNameChange($event)\"\r\n label=\"Credential name\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"manualCredentialType()\"\r\n (ngModelChange)=\"onManualCredentialTypeChange($event)\"\r\n label=\"Credential type\"\r\n [options]=\"credentialTypeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n @for (field of manualCredentialFields(); track field.key) {\r\n @if (manualCredentialFieldIsToggle(field)) {\r\n <mt-toggle-field\r\n [ngModel]=\"manualCredentialFieldValue(field.key) === 'true'\"\r\n (ngModelChange)=\"onManualCredentialFieldChange(field.key, $event)\"\r\n [label]=\"\r\n manualCredentialFieldLabel(field) + (field.required ? ' *' : '')\r\n \"\r\n size=\"small\"\r\n />\r\n } @else {\r\n <mt-text-field\r\n [ngModel]=\"manualCredentialFieldValue(field.key)\"\r\n (ngModelChange)=\"onManualCredentialFieldChange(field.key, $event)\"\r\n [label]=\"\r\n manualCredentialFieldLabel(field) + (field.required ? ' *' : '')\r\n \"\r\n [placeholder]=\"field.placeholder ?? ''\"\r\n [type]=\"manualCredentialFieldInputType(field)\"\r\n />\r\n }\r\n }\r\n </div>\r\n @if (credentialManualError(); as error) {\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ error }}\r\n </div>\r\n }\r\n <div class=\"mt-3 flex justify-end gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n label=\"Cancel\"\r\n (onClick)=\"cancelManualCredentialSetup()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Save credential\"\r\n [loading]=\"credentialManualState() === 'loading'\"\r\n [disabled]=\"credentialManualState() === 'loading'\"\r\n (onClick)=\"createManualCredential()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (credentialOptions().length) {\r\n <div class=\"space-y-2\">\r\n <mt-select-field\r\n [ngModel]=\"selectedCredentialRef()\"\r\n (ngModelChange)=\"selectCredential($event)\"\r\n label=\"Saved credential\"\r\n [options]=\"credentialOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n [showClear]=\"true\"\r\n />\r\n @for (credential of credentials(); track credential.credentialRef) {\r\n @if (credential.credentialRef === selectedCredentialRef()) {\r\n <div\r\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2\"\r\n >\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"min-w-0\">\r\n <div class=\"truncate text-[12px] font-semibold\">\r\n {{ credential.displayName ?? credential.credentialRef }}\r\n </div>\r\n <div\r\n class=\"truncate font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ credential.credentialRef }} /\r\n {{\r\n credential.status ??\r\n (credential.resolved ? \"Resolved\" : \"Unresolved\")\r\n }}\r\n </div>\r\n </div>\r\n <div class=\"flex flex-none items-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n label=\"Test\"\r\n [loading]=\"\r\n credentialTestingRef() === credential.credentialRef\r\n \"\r\n [disabled]=\"\r\n credentialTestingRef() === credential.credentialRef\r\n \"\r\n (onClick)=\"testCredential(credential.credentialRef)\"\r\n />\r\n @if (activeCredentialProvider()?.connectAvailable) {\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n label=\"Reconnect\"\r\n [disabled]=\"credentialOauthState() === 'loading'\"\r\n (onClick)=\"connectCredentialProvider()\"\r\n />\r\n }\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n severity=\"danger\"\r\n label=\"Revoke\"\r\n [loading]=\"credentialRevokeRef() === credential.credentialRef\"\r\n [disabled]=\"\r\n credentialRevokeRef() === credential.credentialRef\r\n \"\r\n (onClick)=\"revokeCredential(credential.credentialRef)\"\r\n />\r\n </div>\r\n </div>\r\n @if (credential.maskedFields; as maskedFields) {\r\n <div\r\n class=\"mt-2 flex flex-wrap gap-1.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n @for (field of maskedFields | keyvalue; track field.key) {\r\n <span\r\n class=\"rounded-md bg-(--p-content-background) px-2 py-1 font-mono\"\r\n >\r\n {{ field.key }}: {{ field.value }}\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n } @else if (sectionHasAdvancedEmptyState(\"credentials\")) {\r\n <p class=\"fp-ae-copy\">\r\n No saved credential exists for this provider. Connect the provider or set\r\n it up manually before publishing.\r\n </p>\r\n }\r\n @if (credentialTest(); as test) {\r\n <div class=\"mt-3 rounded-md bg-(--p-surface-100) px-3 py-2 text-[12px]\">\r\n {{ test.status ?? (test.succeeded ? \"Succeeded\" : \"Failed\") }}\r\n @if (test.message) {\r\n <span>- {{ test.message }}</span>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n<ng-template\r\n #mapEditor\r\n let-objectKey=\"objectKey\"\r\n let-rows=\"rows\"\r\n let-keyLabel=\"keyLabel\"\r\n let-valueLabel=\"valueLabel\"\r\n let-addLabel=\"addLabel\"\r\n let-emptyText=\"emptyText\"\r\n let-keyHint=\"keyHint\"\r\n let-valueHint=\"valueHint\"\r\n let-required=\"required\"\r\n>\r\n <div class=\"space-y-2\">\r\n @for (row of rows; track row.key) {\r\n <div class=\"grid gap-2 md:grid-cols-[180px_1fr_auto]\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"row.key\"\r\n (ngModelChange)=\"\r\n updateObjectRow(objectKey, row.key, $event, row.value)\r\n \"\r\n [label]=\"keyLabel\"\r\n [hint]=\"\r\n keyHint || 'Configuration key saved to the backend JSON object.'\r\n \"\r\n [required]=\"required === true\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"valueText(row.value)\"\r\n (focusin)=\"setExpressionTarget('config:' + objectKey + ':' + row.key)\"\r\n (ngModelChange)=\"updateObjectRow(objectKey, row.key, row.key, $event)\"\r\n [label]=\"valueLabel\"\r\n [hint]=\"\r\n valueHint ||\r\n 'Configuration value. Use expressions when this field should resolve at runtime.'\r\n \"\r\n [required]=\"required === true\"\r\n />\r\n <mt-button\r\n class=\"self-end\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n label=\"Remove\"\r\n (onClick)=\"removeObjectRow(objectKey, row.key)\"\r\n />\r\n </div>\r\n } @empty {\r\n @if (emptyText) {\r\n <div\r\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyText }}\r\n </div>\r\n }\r\n }\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n [label]=\"addLabel || 'Add ' + keyLabel\"\r\n (onClick)=\"addObjectRow(objectKey)\"\r\n />\r\n </div>\r\n</ng-template>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DateField, selector: "mt-date-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "showIcon", "showClear", "showTime", "pInputs", "required"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "component", type: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { kind: "component", type: NumberField, selector: "mt-number-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "pInputs", "format", "useGrouping", "maxFractionDigits", "min", "max", "required"] }, { kind: "component", type: RadioCardsField, selector: "mt-radio-cards-field", inputs: ["circle", "label", "hint", "readonly", "required", "color", "size", "optionLabel", "optionValue", "options"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "directive", type: DropDataDirective, selector: "[fpDropData]", inputs: ["fpDropAutoInsert"], outputs: ["dataDrop", "dataDropEvent"] }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }] });
|
|
20287
20290
|
}
|
|
20288
20291
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AutomationSmartEditorComponent, decorators: [{
|
|
20289
20292
|
type: Component,
|
|
@@ -20301,7 +20304,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
20301
20304
|
ToggleField,
|
|
20302
20305
|
AutomationNodeIconComponent,
|
|
20303
20306
|
...DATA_PILL_DRAG,
|
|
20304
|
-
], host: { class: 'block h-full min-h-0' }, template: "<div\n class=\"fp-scroll flex h-full min-h-0 flex-col overflow-y-auto\"\n fpDropData\n [fpDropAutoInsert]=\"true\"\n (dataDropEvent)=\"insertExpressionDrop($event)\"\n>\n <div class=\"space-y-4 px-5 py-5\">\n @if (helperError()) {\n <div\n class=\"rounded-lg border border-[rgb(var(--fp-warning))]/30 bg-[rgb(var(--fp-warning))]/10 px-3 py-2 text-[12px] leading-5 text-(--p-text-color)\"\n >\n {{ helperError() }}\n </div>\n }\n\n @if (sectionInMain(\"startConnection\") && trigger()) {\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n Start connection\n </h3>\n <div class=\"space-y-3 p-4\">\n @if (startConnection().key) {\n @if (startConnection().step) {\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\n <div class=\"min-w-0 space-y-1\">\n <div\n class=\"text-[12px] font-semibold text-(--p-text-muted-color)\"\n >\n First node connected\n </div>\n <div\n class=\"truncate text-[14px] font-semibold text-(--p-text-color)\"\n >\n {{ startConnection().label }}\n </div>\n <div\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\n >\n <span>Managed on canvas</span>\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ startConnection().key }}\n </span>\n </div>\n </div>\n <div class=\"flex shrink-0 flex-wrap gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"secondary\"\n label=\"Focus connected node\"\n (onClick)=\"focusStartConnection()\"\n />\n </div>\n </div>\n } @else {\n <div class=\"space-y-2\">\n <div class=\"text-[14px] font-semibold text-(--p-text-color)\">\n No first node connected\n </div>\n <p\n class=\"m-0 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n The saved start connection points to a node key that is not on\n the canvas. Connect this trigger to the first node on the\n canvas.\n </p>\n <div\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\n >\n <span>Technical key</span>\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ startConnection().key }}\n </span>\n <span>Managed on canvas</span>\n </div>\n </div>\n }\n } @else {\n <div\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"text-[14px] font-semibold text-(--p-text-color)\">\n No first node connected\n </div>\n <p\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n Connect this trigger to the first node on the canvas.\n </p>\n </div>\n }\n </div>\n </section>\n }\n\n @switch (editorType()) {\n @case (\"ManualTrigger\") {\n @if (sectionInMain(\"manualTrigger\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Manual run input</div>\n @if (\n hasTriggerPayloadSchema() && triggerPayloadSchema();\n as schema\n ) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Payload schema</div>\n <pre class=\"fp-ae-code\">{{ schemaText(schema) }}</pre>\n </div>\n }\n @if (triggerPayloadSample(); as sample) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Sample payload</div>\n <pre class=\"fp-ae-code\">{{ schemaText(sample) }}</pre>\n </div>\n }\n @if (!hasTriggerPayloadSchema() && !triggerPayloadSample()) {\n <p class=\"fp-ae-copy\">\n This manual trigger has no backend-provided input schema. It can\n still be connected to the first step on the canvas.\n </p>\n }\n </section>\n }\n }\n @case (\"WebhookTrigger\") {\n @if (sectionInMain(\"webhookSetup\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Webhook setup</div>\n @if (webhookSetup(); as setup) {\n <div class=\"flex gap-2\">\n <mt-text-field\n class=\"flex-1 font-mono\"\n [ngModel]=\"setup.webhookUrl ?? ''\"\n [readonly]=\"true\"\n label=\"Webhook URL\"\n hint=\"Backend-generated endpoint clients should call to start this trigger.\"\n />\n <mt-button\n class=\"self-end\"\n size=\"small\"\n variant=\"outlined\"\n label=\"Copy\"\n (onClick)=\"copyWebhookUrl()\"\n />\n </div>\n <div class=\"grid gap-2 md:grid-cols-2\">\n <div class=\"fp-ae-kv\">\n <span>Auth mode</span>\n <strong>{{ setup.authMode ?? \"Backend default\" }}</strong>\n </div>\n <div class=\"fp-ae-kv\">\n <span>Required headers</span>\n <strong>{{\n (setup.requiredHeaders ?? []).join(\", \") || \"-\"\n }}</strong>\n </div>\n </div>\n @if (setup.hmacSigning) {\n <p class=\"fp-ae-copy\">{{ setup.hmacSigning }}</p>\n }\n @if (setup.sampleRequest) {\n <details\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Sample request\n </summary>\n <pre class=\"fp-ae-code\">{{\n schemaText(setup.sampleRequest)\n }}</pre>\n </details>\n }\n } @else {\n <p class=\"fp-ae-copy\">\n Webhook setup is not available for this draft yet.\n </p>\n }\n </section>\n }\n @if (sectionInMain(\"authPolicy\") && hasAuthPolicy()) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Authentication policy</div>\n <div class=\"grid gap-3 md:grid-cols-3\">\n <mt-select-field\n [ngModel]=\"authPolicy()['mode'] ?? ''\"\n (ngModelChange)=\"onAuthFieldChange('mode', $event)\"\n label=\"Mode\"\n hint=\"Authentication mode required by the backend webhook policy.\"\n [required]=\"isAuthFieldRequired('mode')\"\n [options]=\"authModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"authPolicy()['signatureHeaderName'] ?? ''\"\n (ngModelChange)=\"\n onAuthFieldChange('signatureHeaderName', $event)\n \"\n label=\"Signature header\"\n hint=\"Header name that carries the webhook signature when the policy requires signed requests.\"\n [required]=\"isAuthFieldRequired('signatureHeaderName')\"\n />\n <mt-text-field\n [ngModel]=\"authPolicy()['timestampHeaderName'] ?? ''\"\n (ngModelChange)=\"\n onAuthFieldChange('timestampHeaderName', $event)\n \"\n label=\"Timestamp header\"\n hint=\"Header name that carries the request timestamp for replay protection.\"\n [required]=\"isAuthFieldRequired('timestampHeaderName')\"\n />\n </div>\n </section>\n }\n }\n @case (\"FormSubmitTrigger\") {\n @if (sectionInMain(\"formBinding\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Form binding</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"selectedFormId() || formBinding()?.formId || ''\"\n (ngModelChange)=\"onFormChange($event)\"\n label=\"Form\"\n hint=\"Choose a backend form that will submit data into this trigger.\"\n [required]=\"true\"\n [options]=\"formOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"\n selectedFormVersionId() || formBinding()?.formVersionId || ''\n \"\n (ngModelChange)=\"onFormVersionChange($event)\"\n label=\"Form version\"\n hint=\"Persist the exact backend formVersionId. Do not type or generate IDs manually.\"\n [required]=\"true\"\n [options]=\"formVersionOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Save binding\"\n (onClick)=\"saveFormBinding()\"\n />\n @if (formBinding()) {\n <span\n class=\"rounded-lg bg-(--p-surface-100) px-2 py-1 font-mono text-[11px] text-(--p-text-muted-color)\"\n >\n {{ formBinding()!.formVersionId }}\n </span>\n }\n </div>\n @if (formSchema(); as schema) {\n <details\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Form schema preview\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(schema.schema) }}</pre>\n </details>\n }\n </section>\n }\n }\n @case (\"ScheduleTrigger\") {\n @if (sectionInMain(\"schedule\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Schedule</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"scheduleMode()\"\n (ngModelChange)=\"onScheduleModeChange($event)\"\n label=\"Mode\"\n hint=\"Pick one schedule mode: cron, interval, or once. Backend validation requires exactly one mode.\"\n [required]=\"true\"\n [options]=\"scheduleModeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['timezone'] ?? 'UTC'\"\n (ngModelChange)=\"onConfigFieldChange('timezone', $event)\"\n label=\"Timezone\"\n hint=\"Timezone used to calculate the next fire time.\"\n [required]=\"isConfigFieldRequired('timezone')\"\n [options]=\"timeZoneOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (showScheduleCron()) {\n <mt-text-field\n class=\"font-mono md:col-span-2\"\n [ngModel]=\"config()['cron'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('cron', $event)\"\n label=\"Cron\"\n hint=\"Cron expression, for example 0 9 * * *.\"\n [required]=\"showScheduleCron()\"\n />\n }\n @if (showScheduleInterval()) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['intervalSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('intervalSeconds', $event)\n \"\n label=\"Interval seconds\"\n hint=\"Repeat interval in seconds.\"\n [required]=\"showScheduleInterval()\"\n [min]=\"0\"\n />\n }\n @if (showScheduleOnce()) {\n <mt-date-field\n class=\"md:col-span-2\"\n [ngModel]=\"\n config()['runAt'] ??\n config()['runAtUtc'] ??\n config()['oneTimeAt'] ??\n config()['at'] ??\n ''\n \"\n (ngModelChange)=\"onConfigFieldChange('runAt', $event)\"\n label=\"Run at UTC\"\n hint=\"UTC date/time for the one-time schedule.\"\n [required]=\"showScheduleOnce()\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n @if (showScheduleStartDate()) {\n <mt-date-field\n [ngModel]=\"config()['startDate'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('startDate', $event)\"\n label=\"Start date UTC\"\n hint=\"Optional first allowed run time for this schedule.\"\n [required]=\"isConfigFieldRequired('startDate')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n <mt-select-field\n [ngModel]=\"config()['misfirePolicy'] ?? 'SkipMissed'\"\n (ngModelChange)=\"onConfigFieldChange('misfirePolicy', $event)\"\n label=\"Misfire policy\"\n hint=\"Backend behavior when a scheduled run is missed while the automation is unavailable.\"\n [required]=\"isConfigFieldRequired('misfirePolicy')\"\n [options]=\"misfirePolicyOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Validate\"\n (onClick)=\"validateSchedule()\"\n />\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Preview next fire\"\n (onClick)=\"previewSchedule()\"\n />\n </div>\n @if (schedulePreview(); as preview) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n <strong class=\"text-emerald-600\">Valid</strong>\n <span class=\"ms-2\"\n >Next fire: {{ preview.nextFireAtUtc ?? \"-\" }}</span\n >\n </div>\n }\n </section>\n }\n }\n @case (\"SetFields\") {\n @if (sectionInMain(\"setFields\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Set fields</div>\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'fields',\n rows: fieldsRows(),\n keyLabel: 'Field',\n valueLabel: 'Value',\n addLabel: 'Add field',\n required: isConfigFieldRequired('fields'),\n }\n \"\n />\n </section>\n }\n }\n @case (\"If\") {\n @if (sectionInMain(\"condition\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Condition</div>\n <div class=\"grid gap-3 md:grid-cols-[1fr_180px_1fr]\">\n <mt-text-field\n [ngModel]=\"ifConditionFieldText('left')\"\n (focusin)=\"setExpressionTarget('config:left')\"\n (ngModelChange)=\"onIfConditionFieldChange('left', $event)\"\n label=\"Left\"\n hint=\"Left-side value or expression to compare.\"\n [required]=\"isConfigFieldRequired('left')\"\n />\n <mt-select-field\n [ngModel]=\"ifConditionFieldValue('operator') || 'equals'\"\n (ngModelChange)=\"onIfConditionFieldChange('operator', $event)\"\n label=\"Operator\"\n hint=\"Comparison operator used by the backend condition evaluator.\"\n [required]=\"isConfigFieldRequired('operator')\"\n [options]=\"ifOperatorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"ifConditionFieldText('right')\"\n (focusin)=\"setExpressionTarget('config:right')\"\n (ngModelChange)=\"onIfConditionFieldChange('right', $event)\"\n label=\"Right\"\n hint=\"Right-side value or expression to compare against.\"\n [required]=\"isConfigFieldRequired('right')\"\n />\n </div>\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Route outputs</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (key of routeOutputKeys(); track key) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ key }}</span\n >\n }\n </div>\n </div>\n </section>\n }\n }\n @case (\"HTTP\") {\n @if (sectionInMain(\"httpRequest\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">HTTP request</div>\n <div class=\"grid gap-3 md:grid-cols-[150px_1fr]\">\n <mt-select-field\n [ngModel]=\"httpMethod()\"\n (ngModelChange)=\"onHttpMethodChange($event)\"\n label=\"Method\"\n hint=\"HTTP method used for the outbound request.\"\n [required]=\"isConfigFieldRequired('method')\"\n [options]=\"httpMethodOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['url'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:url')\"\n (ngModelChange)=\"onConfigFieldChange('url', $event)\"\n label=\"URL\"\n hint=\"Target URL. Expressions are supported for dynamic hosts, paths, and query values.\"\n [required]=\"isConfigFieldRequired('url')\"\n />\n </div>\n <div class=\"mt-3\">\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'headers',\n rows: headerRows(),\n keyLabel: 'Header',\n valueLabel: 'Value',\n addLabel: 'Add header',\n }\n \"\n />\n </div>\n <div class=\"mt-3\">\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'query',\n rows: queryRows(),\n keyLabel: 'Query param',\n valueLabel: 'Value',\n addLabel: 'Add query param',\n }\n \"\n />\n </div>\n @if (httpMethodAllowsPayload()) {\n @if (httpSupportsBodyMode()) {\n <mt-select-field\n class=\"mt-3\"\n [ngModel]=\"httpBodyMode()\"\n (ngModelChange)=\"onHttpBodyModeChange($event)\"\n label=\"Payload mode\"\n hint=\"How the request payload should be sent by the backend HTTP runtime.\"\n [required]=\"isConfigFieldRequired(httpBodyModeConfigKey())\"\n [options]=\"httpBodyModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n }\n @if (httpBodyEnabled()) {\n <mt-textarea-field\n class=\"mt-3 w-full font-mono\"\n [ngModel]=\"valueText(config()['body'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:body')\"\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\n label=\"Request payload\"\n hint=\"Payload sent by this HTTP method. Use JSON or expressions when the backend schema allows it.\"\n [required]=\"isConfigFieldRequired('body')\"\n rows=\"6\"\n />\n }\n } @else {\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ httpMethod() }} requests do not send a payload. Put request\n data in query parameters or headers.\n </div>\n }\n @if (\n supportsConfigKey(\"timeoutSeconds\") ||\n supportsConfigKey(\"responseHandling\")\n ) {\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n @if (supportsConfigKey(\"timeoutSeconds\")) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('timeoutSeconds', $event)\n \"\n label=\"Timeout seconds\"\n hint=\"Request timeout when exposed by the backend schema.\"\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\n [min]=\"0\"\n />\n }\n @if (supportsConfigKey(\"responseHandling\")) {\n <mt-text-field\n [ngModel]=\"config()['responseHandling'] ?? ''\"\n (ngModelChange)=\"\n onConfigFieldChange('responseHandling', $event)\n \"\n label=\"Response handling\"\n hint=\"Backend-supported response handling mode or expression.\"\n [required]=\"isConfigFieldRequired('responseHandling')\"\n />\n }\n </div>\n }\n @if (sectionInMain(\"credentials\")) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Credential</div>\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\n </div>\n }\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"fp-ae-label\">Configuration check</div>\n <p class=\"fp-ae-copy m-0 mt-1\">\n Runs backend dry-run validation for method, URL,\n expressions, and request policy before execution.\n </p>\n </div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"secondary\"\n label=\"Check request\"\n [disabled]=\"httpCheckState() === 'loading'\"\n (onClick)=\"checkHttpRequest()\"\n />\n </div>\n @if (httpLocalIssues().length > 0) {\n <div\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-warning))]/25 bg-[rgb(var(--fp-warning))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\n >\n {{ httpLocalIssues()[0] }}\n </div>\n } @else if (httpCheckError(); as error) {\n <div\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-error))]/25 bg-[rgb(var(--fp-error))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\n >\n {{ error }}\n </div>\n } @else if (httpCheckResult(); as result) {\n <div\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-commit))]/25 bg-[rgb(var(--fp-commit))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\n >\n Backend check\n {{\n result.success || result.canStart\n ? \"passed\"\n : \"completed with issues\"\n }}.\n </div>\n }\n </div>\n </section>\n }\n }\n @case (\"Wait\") {\n @if (sectionInMain(\"wait\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Wait</div>\n <div class=\"grid gap-3 md:grid-cols-3\">\n <mt-select-field\n [ngModel]=\"waitMode()\"\n (ngModelChange)=\"onWaitModeChange($event)\"\n label=\"Mode\"\n hint=\"Choose how execution should pause.\"\n [required]=\"isConfigFieldRequired('mode')\"\n [options]=\"waitModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (showWaitDuration()) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['durationSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('durationSeconds', $event)\n \"\n label=\"Wait duration\"\n hint=\"Seconds to pause before continuing.\"\n [required]=\"isConfigFieldRequired('durationSeconds')\"\n [min]=\"1\"\n />\n }\n @if (showWaitUntilDate()) {\n <mt-date-field\n [ngModel]=\"waitUntilDateValue()\"\n (ngModelChange)=\"onWaitUntilDateChange($event)\"\n label=\"Resume at\"\n hint=\"Date and time when execution should continue.\"\n [required]=\"isConfigFieldRequired('untilDate')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n </div>\n @if (showWaitResumePayloadSchema()) {\n <mt-textarea-field\n class=\"mt-3 w-full font-mono\"\n [ngModel]=\"waitResumePayloadSchemaText()\"\n (focusin)=\"setExpressionTarget('config:resumePayloadSchema')\"\n (ngModelChange)=\"onWaitResumePayloadSchemaChange($event)\"\n label=\"Resume payload schema\"\n hint=\"Optional JSON schema for data sent back when this wait is resumed.\"\n [required]=\"isConfigFieldRequired('resumePayloadSchema')\"\n rows=\"5\"\n />\n }\n </section>\n }\n }\n @case (\"HumanTask\") {\n @if (sectionInMain(\"humanTask\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Human task</div>\n @if (\n assignmentOptions()?.providerStatus &&\n assignmentOptions()?.providerStatus !== \"Available\"\n ) {\n <p class=\"fp-ae-copy\">\n Assignment provider status:\n {{ assignmentOptions()?.providerStatus }}. The backend provider\n is the source of truth for available assignees.\n </p>\n }\n <div\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n Task details\n </h3>\n <div class=\"grid gap-3 p-4 xl:grid-cols-3\">\n <mt-text-field\n [ngModel]=\"config()['title'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:title')\"\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\n label=\"Task title\"\n hint=\"Title shown to the assigned user while the execution waits.\"\n [required]=\"isConfigFieldRequired('title')\"\n />\n <mt-select-field\n [ngModel]=\"selectedAssignmentKey()\"\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\n label=\"Assignment\"\n hint=\"Backend-provided assignee, role, or group that can submit this task.\"\n [required]=\"isConfigFieldRequired('assignment')\"\n [options]=\"assignmentSelectOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"config()['priority'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('priority', $event)\"\n label=\"Priority\"\n hint=\"Optional task priority passed to backend task orchestration.\"\n [required]=\"isConfigFieldRequired('priority')\"\n />\n <mt-textarea-field\n class=\"xl:col-span-3\"\n [ngModel]=\"config()['description'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:description')\"\n (ngModelChange)=\"onConfigFieldChange('description', $event)\"\n label=\"Task description\"\n hint=\"Instructions shown with the HumanTask form.\"\n [required]=\"isConfigFieldRequired('description')\"\n rows=\"4\"\n />\n </div>\n </div>\n\n <div\n class=\"mt-3 flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n Task behavior\n </h3>\n <div class=\"grid gap-3 p-4 xl:grid-cols-4\">\n <mt-date-field\n [ngModel]=\"\n config()['dueDateUtc'] ?? config()['dueDate'] ?? ''\n \"\n (ngModelChange)=\"onConfigFieldChange('dueDateUtc', $event)\"\n label=\"Due date\"\n hint=\"Optional backend due timestamp for the waiting task.\"\n [required]=\"isConfigFieldRequired('dueDateUtc')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n <mt-number-field\n [ngModel]=\"numberValue(config()['dueInSeconds'])\"\n (ngModelChange)=\"onConfigFieldChange('dueInSeconds', $event)\"\n label=\"Due in seconds\"\n hint=\"Relative due duration when the backend should calculate the timestamp.\"\n [required]=\"isConfigFieldRequired('dueInSeconds')\"\n [min]=\"0\"\n />\n <mt-text-field\n [ngModel]=\"config()['contextOutputPath'] ?? 'task'\"\n (ngModelChange)=\"\n onConfigFieldChange('contextOutputPath', $event)\n \"\n label=\"Context output path\"\n hint=\"Where submitted task values are written in execution context.\"\n [required]=\"isConfigFieldRequired('contextOutputPath')\"\n />\n <mt-select-field\n [ngModel]=\"config()['cancelBehavior'] ?? 'RouteCancelled'\"\n (ngModelChange)=\"\n onConfigFieldChange('cancelBehavior', $event)\n \"\n label=\"Cancel behavior\"\n hint=\"Route cancelled when users cancel, or fail this node.\"\n [required]=\"isConfigFieldRequired('cancelBehavior')\"\n [options]=\"humanTaskCancelBehaviorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <div\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\n >\n <mt-toggle-field\n size=\"small\"\n label=\"Allow draft save\"\n labelPosition=\"end\"\n [ngModel]=\"config()['saveDraft'] !== false\"\n [required]=\"isConfigFieldRequired('saveDraft')\"\n (ngModelChange)=\"\n onConfigFieldChange('saveDraft', $event === true)\n \"\n hint=\"Expose backend draft save for this task when supported.\"\n />\n </div>\n @if (supportsConfigKey(\"submitBehavior\")) {\n <mt-text-field\n [ngModel]=\"config()['submitBehavior'] ?? ''\"\n (ngModelChange)=\"\n onConfigFieldChange('submitBehavior', $event)\n \"\n label=\"Submit behavior\"\n hint=\"Backend submit behavior when exposed by the node schema.\"\n [required]=\"isConfigFieldRequired('submitBehavior')\"\n />\n }\n </div>\n </div>\n\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Validate assignment\"\n (onClick)=\"validateAssignment()\"\n />\n </div>\n @if (assignmentValidation(); as result) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n {{\n result.isValid === false\n ? \"Assignment invalid\"\n : \"Assignment accepted by helper\"\n }}\n @for (issue of resultIssues(result); track $index) {\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\n {{ issue }}\n </div>\n }\n </div>\n }\n </section>\n }\n\n @if (sectionInMain(\"formBinding\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Task input form</div>\n <mt-radio-cards-field\n [ngModel]=\"humanTaskFormSource()\"\n (ngModelChange)=\"onHumanTaskFormSourceChange($event)\"\n label=\"Form source\"\n hint=\"HumanTask requires either its own node form binding or an explicit reused FormSubmitTrigger payload.\"\n [required]=\"isConfigFieldRequired('formSource')\"\n [options]=\"humanTaskFormSourceOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n size=\"small\"\n >\n <ng-template #option let-item>\n <div class=\"w-full min-w-0\">\n <div class=\"text-[13px] font-semibold leading-5\">\n {{ item.label }}\n </div>\n <p\n class=\"mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ item.description }}\n </p>\n </div>\n </ng-template>\n </mt-radio-cards-field>\n\n @if (humanTaskUsesExplicitFormBinding()) {\n <div\n class=\"mt-3 flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n HumanTask input binding\n </h3>\n <div class=\"grid gap-3 p-4 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"selectedFormId() || formBinding()?.formId || ''\"\n (ngModelChange)=\"onFormChange($event)\"\n label=\"Form\"\n hint=\"Choose a backend form for this HumanTask node.\"\n [required]=\"humanTaskUsesExplicitFormBinding()\"\n [options]=\"formOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"\n selectedFormVersionId() ||\n formBinding()?.formVersionId ||\n ''\n \"\n (ngModelChange)=\"onFormVersionChange($event)\"\n label=\"Form version\"\n hint=\"Persist the exact backend formVersionId for the node binding.\"\n [required]=\"humanTaskUsesExplicitFormBinding()\"\n [options]=\"formVersionOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"border-t border-surface-200 px-4 py-3\">\n @if (humanTaskFormSummary(); as summary) {\n <div\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\n >\n @if (summary.formLabel) {\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.formLabel }}\n </span>\n }\n @if (summary.formVersionId) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ summary.formVersionId }}\n </span>\n }\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.fieldCount }} fields\n </span>\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.requiredCount }} required\n </span>\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.writableCount }} writable\n </span>\n </div>\n }\n\n @if (humanTaskHasCustomInputMapping()) {\n <div\n class=\"mt-3 rounded-md border border-[rgb(var(--fp-warning))]/30 bg-[rgb(var(--fp-warning))]/10 px-3 py-2 text-[12px] leading-5 text-(--p-text-color)\"\n >\n Existing custom input mapping will be preserved unless\n field prefill options are changed.\n </div>\n }\n\n @if (humanTaskFormFields().length > 0) {\n <div\n class=\"mt-3 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <div\n class=\"flex flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\n >\n <div class=\"text-[13px] font-semibold text-color\">\n FormBuilder fields\n </div>\n <div class=\"flex flex-wrap gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"secondary\"\n label=\"Select all\"\n (onClick)=\"selectAllHumanTaskPrefillFields()\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"secondary\"\n label=\"Clear\"\n (onClick)=\"clearHumanTaskPrefillFields()\"\n />\n </div>\n </div>\n <div class=\"divide-y divide-surface-200\">\n @for (field of humanTaskFormFields(); track field.key) {\n <div\n class=\"grid gap-3 px-3 py-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-center\"\n >\n <div class=\"min-w-0\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <span\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\n >\n {{ field.label }}\n </span>\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 font-mono text-[11px] text-(--p-text-muted-color)\"\n >\n {{ field.key }}\n </span>\n @if (field.required) {\n <span\n class=\"rounded-md bg-[rgb(var(--fp-danger))]/10 px-2 py-0.5 text-[11px] font-semibold text-[rgb(var(--fp-danger))]\"\n >\n Required\n </span>\n }\n @if (field.read === true) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\n >\n Read\n </span>\n }\n @if (field.write === true) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\n >\n Write\n </span>\n }\n </div>\n @if (field.description) {\n <p\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ field.description }}\n </p>\n }\n </div>\n <mt-toggle-field\n size=\"small\"\n label=\"Prefill\"\n labelPosition=\"end\"\n [ngModel]=\"\n humanTaskPrefillFieldKeySet().has(field.key)\n \"\n (ngModelChange)=\"\n onHumanTaskPrefillFieldToggle(\n field.key,\n $event === true\n )\n \"\n />\n </div>\n }\n </div>\n <div\n class=\"border-t border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n Selected fields are saved as\n <span class=\"font-mono\"\n >inputMappingJson.fieldKeys</span\n >\n so backend can prefill matching upstream values.\n </div>\n </div>\n } @else if (formSchema()) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n No FormBuilder fields were returned for this form schema.\n </div>\n }\n\n @if (formSchema()?.requiresSnapshotOnPublish) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n Backend requires this form version snapshot during\n publish.\n </div>\n }\n </div>\n <div class=\"border-t border-surface-200 px-4 py-3\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Save task binding\"\n (onClick)=\"saveFormBinding()\"\n />\n @if (formBinding()) {\n <span\n class=\"rounded-lg bg-(--p-surface-100) px-2 py-1 font-mono text-[11px] text-(--p-text-muted-color)\"\n >\n {{ formBinding()!.formVersionId }}\n </span>\n }\n </div>\n </div>\n </div>\n } @else {\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['triggerKey'] ?? ''\"\n (ngModelChange)=\"onHumanTaskReuseTriggerChange($event)\"\n label=\"Trigger to reuse\"\n hint=\"Explicitly select the FormSubmitTrigger whose submitted form payload is reused.\"\n [options]=\"triggerOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n @if (humanTaskReuseFormBinding()) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Reused trigger form</div>\n @if (humanTaskFormSummary(); as summary) {\n <div\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\n >\n @if (summary.formLabel) {\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.formLabel }}\n </span>\n }\n @if (summary.formVersionId) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ summary.formVersionId }}\n </span>\n }\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.fieldCount }} fields\n </span>\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\n {{ summary.requiredCount }} required\n </span>\n </div>\n }\n </div>\n }\n }\n\n @if (formSchema(); as schema) {\n <details\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Form schema preview\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(schema.schema) }}</pre>\n </details>\n }\n\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">HumanTask routes</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (key of routeOutputKeys(); track key) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ key }}</span\n >\n }\n </div>\n </div>\n </section>\n }\n }\n @case (\"HumanApproval\") {\n @if (sectionInMain(\"approvalTask\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Approval task</div>\n @if (\n assignmentOptions()?.providerStatus &&\n assignmentOptions()?.providerStatus !== \"Available\"\n ) {\n <p class=\"fp-ae-copy\">\n Assignment provider status:\n {{ assignmentOptions()?.providerStatus }}. The backend provider\n is the source of truth for available assignees.\n </p>\n }\n <div class=\"grid gap-3 xl:grid-cols-3\">\n <mt-text-field\n [ngModel]=\"config()['title'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:title')\"\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\n label=\"Approval title\"\n hint=\"Approval title shown to the assigned human approver.\"\n [required]=\"isConfigFieldRequired('title')\"\n />\n <mt-select-field\n [ngModel]=\"selectedAssignmentKey()\"\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\n label=\"Assignment\"\n hint=\"Backend-provided assignee, role, or group that can decide this approval.\"\n [required]=\"isConfigFieldRequired('assignment')\"\n [options]=\"assignmentSelectOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (humanApprovalSupportsConfig(\"priority\")) {\n <mt-text-field\n [ngModel]=\"config()['priority'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('priority', $event)\"\n label=\"Priority\"\n hint=\"Approval priority when supported by the backend schema.\"\n [required]=\"isConfigFieldRequired('priority')\"\n />\n }\n </div>\n <mt-textarea-field\n class=\"mt-3 w-full\"\n [ngModel]=\"config()['message'] ?? config()['instructions'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message')\"\n (ngModelChange)=\"onConfigFieldChange('message', $event)\"\n label=\"Approval message\"\n hint=\"Decision instructions shown to the approver.\"\n [required]=\"isConfigFieldRequired('message')\"\n rows=\"4\"\n />\n @if (\n humanApprovalSupportsConfig(\"dueDate\") ||\n humanApprovalSupportsConfig(\"dueInSeconds\") ||\n humanApprovalSupportsConfig(\"timeoutSeconds\") ||\n humanApprovalSupportsConfig(\"expiresAt\") ||\n humanApprovalSupportsConfig(\"commentsRequired\") ||\n humanApprovalSupportsConfig(\"allowReturn\")\n ) {\n <div class=\"mt-3 grid gap-3 xl:grid-cols-4\">\n @if (humanApprovalSupportsConfig(\"dueDate\")) {\n <mt-date-field\n [ngModel]=\"config()['dueDate'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('dueDate', $event)\"\n label=\"Due date\"\n hint=\"Backend-supported approval due date.\"\n [required]=\"isConfigFieldRequired('dueDate')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n @if (humanApprovalSupportsConfig(\"dueInSeconds\")) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['dueInSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('dueInSeconds', $event)\n \"\n label=\"Due in seconds\"\n hint=\"Relative approval due duration when supported by the backend schema.\"\n [required]=\"isConfigFieldRequired('dueInSeconds')\"\n [min]=\"0\"\n />\n }\n @if (humanApprovalSupportsConfig(\"timeoutSeconds\")) {\n <mt-number-field\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\n (ngModelChange)=\"\n onConfigFieldChange('timeoutSeconds', $event)\n \"\n label=\"Timeout seconds\"\n hint=\"Approval timeout emitted according to backend approval runtime support.\"\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\n [min]=\"0\"\n />\n }\n @if (humanApprovalSupportsConfig(\"expiresAt\")) {\n <mt-date-field\n [ngModel]=\"config()['expiresAt'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('expiresAt', $event)\"\n label=\"Expires at\"\n hint=\"Backend-supported approval expiry timestamp.\"\n [required]=\"isConfigFieldRequired('expiresAt')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n }\n @if (humanApprovalSupportsConfig(\"commentsRequired\")) {\n <div\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\n >\n <mt-toggle-field\n size=\"small\"\n label=\"Comments required\"\n labelPosition=\"end\"\n [ngModel]=\"config()['commentsRequired'] === true\"\n [required]=\"isConfigFieldRequired('commentsRequired')\"\n (ngModelChange)=\"\n onConfigFieldChange('commentsRequired', $event === true)\n \"\n hint=\"Require approver comments when the backend supports this flag.\"\n />\n </div>\n }\n @if (humanApprovalSupportsConfig(\"allowReturn\")) {\n <div\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\n >\n <mt-toggle-field\n size=\"small\"\n label=\"Allow return for changes\"\n labelPosition=\"end\"\n [ngModel]=\"config()['allowReturn'] !== false\"\n [required]=\"isConfigFieldRequired('allowReturn')\"\n (ngModelChange)=\"\n onConfigFieldChange('allowReturn', $event === true)\n \"\n hint=\"Allow ReturnForChanges when the backend schema exposes this option.\"\n />\n </div>\n }\n </div>\n }\n <div class=\"mt-3 space-y-3\">\n <div class=\"fp-ae-section-title\">Decision options</div>\n <div\n class=\"overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n @if (selectedApprovalDecisionRows().length > 0) {\n <div\n class=\"hidden border-b border-surface-200 bg-surface-50 px-3 py-2 text-[12px] font-semibold text-(--p-text-muted-color) xl:grid xl:grid-cols-[1.1fr_1fr_1.2fr_80px_44px] xl:gap-3\"\n >\n <span>Decision label</span>\n <span>Canonical value</span>\n <span>Route output key</span>\n <span>Routes</span>\n <span>Action</span>\n </div>\n @for (\n decision of selectedApprovalDecisionRows();\n track decision.value\n ) {\n <div\n class=\"grid gap-2 border-b border-surface-100 px-3 py-3 last:border-b-0 xl:grid-cols-[1.1fr_1fr_1.2fr_80px_44px] xl:items-center xl:gap-3\"\n >\n <div class=\"min-w-0\">\n <div\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\n >\n Decision label\n </div>\n <div\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\n >\n {{ decision.label }}\n </div>\n </div>\n <div class=\"min-w-0\">\n <div\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\n >\n Canonical value\n </div>\n <div\n class=\"truncate font-mono text-[12px] text-(--p-text-color)\"\n >\n {{ decision.value }}\n </div>\n </div>\n <div class=\"min-w-0\">\n <div\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\n >\n Route output key\n </div>\n <div\n class=\"truncate font-mono text-[12px] text-(--p-text-muted-color)\"\n >\n {{ decision.routeOutputKey }}\n </div>\n </div>\n <div>\n <div\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\n >\n Routes\n </div>\n <div class=\"text-[13px] text-(--p-text-color)\">\n {{ decision.routeCount }}\n </div>\n </div>\n <mt-button\n class=\"justify-self-start xl:justify-self-end\"\n size=\"small\"\n variant=\"outlined\"\n severity=\"danger\"\n icon=\"general.trash-01\"\n tooltip=\"Remove decision option\"\n (onClick)=\"removeApprovalDecision(decision.value)\"\n />\n </div>\n }\n } @else {\n <div\n class=\"px-3 py-4 text-[12px] text-(--p-text-muted-color)\"\n >\n Add at least one backend-supported approval decision.\n </div>\n }\n </div>\n @if (approvalDecisionIssues().length > 0) {\n <div\n class=\"rounded-lg border border-[rgb(var(--fp-error))]/30 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\n >\n @for (issue of approvalDecisionIssues(); track $index) {\n <div>{{ issue }}</div>\n }\n </div>\n }\n <div class=\"grid gap-2 md:grid-cols-[minmax(0,1fr)_auto]\">\n <mt-select-field\n [ngModel]=\"approvalDecisionToAdd()\"\n (ngModelChange)=\"approvalDecisionToAdd.set($event)\"\n label=\"Decision to add\"\n hint=\"Only backend-supported decisions with matching route outputs are available.\"\n [required]=\"isConfigFieldRequired('allowedDecisions')\"\n [options]=\"addableApprovalDecisionOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-button\n class=\"self-end\"\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add decision option\"\n [disabled]=\"!approvalDecisionToAdd()\"\n (onClick)=\"addApprovalDecision()\"\n />\n </div>\n </div>\n @if (\n humanApprovalSupportsConfig(\"payload\") ||\n humanApprovalSupportsConfig(\"context\") ||\n humanApprovalSupportsConfig(\"metadata\")\n ) {\n <div class=\"mt-3 grid gap-3\">\n @if (humanApprovalSupportsConfig(\"payload\")) {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"valueText(config()['payload'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:payload')\"\n (ngModelChange)=\"onConfigFieldChange('payload', $event)\"\n label=\"Payload\"\n hint=\"Approval task payload or context fields supported by backend schema.\"\n [required]=\"isConfigFieldRequired('payload')\"\n rows=\"5\"\n />\n }\n @if (humanApprovalSupportsConfig(\"context\")) {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"valueText(config()['context'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:context')\"\n (ngModelChange)=\"onConfigFieldChange('context', $event)\"\n label=\"Context\"\n hint=\"Additional approval context supported by backend schema.\"\n [required]=\"isConfigFieldRequired('context')\"\n rows=\"5\"\n />\n }\n @if (humanApprovalSupportsConfig(\"metadata\")) {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"valueText(config()['metadata'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:metadata')\"\n (ngModelChange)=\"onConfigFieldChange('metadata', $event)\"\n label=\"Metadata\"\n hint=\"Additional approval metadata supported by the backend schema.\"\n [required]=\"isConfigFieldRequired('metadata')\"\n rows=\"4\"\n />\n }\n </div>\n }\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Validate assignment\"\n (onClick)=\"validateAssignment()\"\n />\n </div>\n @if (assignmentValidation(); as result) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n {{\n result.isValid === false\n ? \"Assignment invalid\"\n : \"Assignment accepted by helper\"\n }}\n @for (issue of resultIssues(result); track $index) {\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\n {{ issue }}\n </div>\n }\n </div>\n }\n </section>\n }\n }\n @case (\"ConnectorAction\") {\n @if (sectionInMain(\"humanReviewRequest\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">\n {{ humanReviewProviderLabel() }} human review\n </div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"config()['channelType'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('channelType', $event)\"\n label=\"Channel\"\n [required]=\"isConfigFieldRequired('channelType')\"\n />\n </div>\n\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\n <mt-text-field\n [ngModel]=\"humanReviewDestination()['recipient'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:destination:recipient')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('destination', 'recipient', $event)\n \"\n label=\"Recipient\"\n [required]=\"isConnectorDestinationRequired()\"\n />\n <mt-text-field\n [ngModel]=\"humanReviewDestination()['channelId'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:destination:channelId')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('destination', 'channelId', $event)\n \"\n label=\"Channel ID\"\n [required]=\"isConnectorDestinationRequired()\"\n />\n <mt-text-field\n [ngModel]=\"humanReviewDestination()['threadKey'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:destination:threadKey')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('destination', 'threadKey', $event)\n \"\n label=\"Thread key\"\n [required]=\"isConnectorDestinationRequired()\"\n />\n </div>\n\n <div class=\"mt-4 grid gap-3\">\n <mt-text-field\n [ngModel]=\"humanReviewMessage()['subject'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message:subject')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('message', 'subject', $event)\n \"\n label=\"Subject\"\n [required]=\"isConnectorMessageRequired()\"\n />\n <mt-textarea-field\n class=\"w-full\"\n [ngModel]=\"humanReviewMessage()['body'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message:body')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('message', 'body', $event)\n \"\n label=\"Message\"\n [required]=\"isConnectorMessageRequired()\"\n rows=\"5\"\n />\n <mt-text-field\n [ngModel]=\"humanReviewMessage()['templateKey'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message:templateKey')\"\n (ngModelChange)=\"\n onNestedConfigFieldChange('message', 'templateKey', $event)\n \"\n label=\"Template key\"\n [required]=\"isConnectorMessageRequired()\"\n />\n </div>\n\n <div class=\"mt-4\">\n <div class=\"mb-2 flex items-center justify-between gap-3\">\n <div class=\"fp-ae-label\">Response options</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Add option\"\n (onClick)=\"addHumanReviewResponseOption()\"\n />\n </div>\n <div class=\"grid gap-2\">\n @for (\n option of humanReviewResponseOptions();\n track option.key + \":\" + $index\n ) {\n <div\n class=\"grid gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-2 md:grid-cols-[1fr_1fr_1fr_auto]\"\n >\n <mt-text-field\n [ngModel]=\"option.key\"\n (ngModelChange)=\"\n updateHumanReviewResponseOption($index, 'key', $event)\n \"\n label=\"Key\"\n [required]=\"isConfigFieldRequired('responseOptions')\"\n />\n <mt-text-field\n [ngModel]=\"option.label\"\n (ngModelChange)=\"\n updateHumanReviewResponseOption($index, 'label', $event)\n \"\n label=\"Label\"\n [required]=\"isConfigFieldRequired('responseOptions')\"\n />\n <mt-text-field\n [ngModel]=\"option.routeOutputKey\"\n (ngModelChange)=\"\n updateHumanReviewResponseOption(\n $index,\n 'routeOutputKey',\n $event\n )\n \"\n label=\"Route output\"\n [required]=\"isConfigFieldRequired('responseOptions')\"\n />\n <div class=\"flex items-end justify-end\">\n <mt-button\n size=\"small\"\n variant=\"text\"\n severity=\"secondary\"\n icon=\"general.x-close\"\n [disabled]=\"option.routeCount > 0\"\n (onClick)=\"removeHumanReviewResponseOption($index)\"\n [attr.aria-label]=\"'Remove response option'\"\n />\n </div>\n </div>\n }\n </div>\n </div>\n\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"selectedAssignmentKey()\"\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\n label=\"Assignment\"\n [required]=\"isConfigFieldRequired('assignment')\"\n [options]=\"assignmentSelectOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"config()['credentialRef'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('credentialRef', $event)\"\n label=\"Credential\"\n [required]=\"isConfigFieldRequired('credentialRef')\"\n />\n <mt-number-field\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\n (ngModelChange)=\"onConfigFieldChange('timeoutSeconds', $event)\"\n label=\"Timeout seconds\"\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\n [min]=\"1\"\n />\n <mt-select-field\n [ngModel]=\"config()['failureBehavior'] ?? 'RouteFailure'\"\n (ngModelChange)=\"onConfigFieldChange('failureBehavior', $event)\"\n label=\"Failure behavior\"\n [required]=\"isConfigFieldRequired('failureBehavior')\"\n [options]=\"humanReviewFailureBehaviorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n label=\"Validate assignment\"\n (onClick)=\"validateAssignment()\"\n />\n </div>\n @if (assignmentValidation(); as result) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n {{\n result.isValid === false\n ? \"Assignment invalid\"\n : \"Assignment accepted by helper\"\n }}\n @for (issue of resultIssues(result); track $index) {\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\n {{ issue }}\n </div>\n }\n </div>\n }\n\n <div\n class=\"mt-4 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Route outputs</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (key of routeOutputKeys(); track key) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ key }}</span\n >\n }\n </div>\n </div>\n </section>\n }\n }\n @case (\"ConvertToFile\") {\n @if (sectionInMain(\"convertToFile\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">\n {{ convertToFileActionLabel() }}\n </div>\n @if (convertToFileUnavailableReason(); as reason) {\n <div\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ reason }}\n </div>\n }\n\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"config()['fileName'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:fileName')\"\n (ngModelChange)=\"onConfigFieldChange('fileName', $event)\"\n label=\"File name\"\n [required]=\"isConfigFieldRequired('fileName')\"\n />\n @if (convertToFileIsBase64()) {\n <mt-text-field\n [ngModel]=\"config()['contentType'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('contentType', $event)\"\n label=\"Content type\"\n [required]=\"isConfigFieldRequired('contentType')\"\n />\n } @else {\n <mt-text-field\n [ngModel]=\"convertToFileContentType()\"\n [readonly]=\"true\"\n label=\"Content type\"\n />\n }\n </div>\n\n @if (\n convertToFileIsCsv() ||\n convertToFileIsJson() ||\n convertToFileIsSpreadsheet()\n ) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"convertToFileSourceMode()\"\n (ngModelChange)=\"onConvertToFileSourceModeChange($event)\"\n label=\"Data source\"\n [required]=\"isConfigFieldRequired('sourceExpression')\"\n [options]=\"convertToFileSourceOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (convertToFileSourceMode() === \"expression\") {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"convertToFileSourceExpression()\"\n (focusin)=\"setExpressionTarget('config:sourceExpression')\"\n (ngModelChange)=\"\n onConvertToFileSourceExpressionChange($event)\n \"\n label=\"Source expression\"\n [required]=\"isConfigFieldRequired('sourceExpression')\"\n />\n }\n @if (convertToFileIsCsv() || convertToFileIsSpreadsheet()) {\n <div\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3 md:col-span-2\"\n >\n <div class=\"mb-2 flex items-center justify-between gap-3\">\n <div class=\"fp-ae-label\">Columns</div>\n @if (convertToFileColumns().length) {\n <mt-button\n size=\"small\"\n variant=\"text\"\n severity=\"secondary\"\n label=\"Clear\"\n (onClick)=\"clearConvertToFileColumns()\"\n />\n }\n </div>\n <div class=\"mb-3 flex min-h-8 flex-wrap gap-1.5\">\n @if (convertToFileColumns().length) {\n @for (column of convertToFileColumns(); track column) {\n <span\n class=\"inline-flex items-center gap-1 rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >\n {{ column }}\n <button\n type=\"button\"\n class=\"inline-flex h-4 w-4 items-center justify-center rounded-sm text-[10px] hover:bg-(--p-surface-200) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n (click)=\"removeConvertToFileColumn(column)\"\n [attr.aria-label]=\"'Remove column ' + column\"\n >\n x\n </button>\n </span>\n }\n } @else {\n <span\n class=\"rounded-md bg-(--p-content-background) px-2 py-1 text-[12px] text-(--p-text-muted-color)\"\n >\n All fields\n </span>\n }\n </div>\n <div class=\"grid items-end gap-2 md:grid-cols-[1fr_auto]\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"convertToFileColumnDraft()\"\n (ngModelChange)=\"\n onConvertToFileColumnDraftChange($event)\n \"\n (keyup.enter)=\"addConvertToFileColumns()\"\n label=\"Add column\"\n [required]=\"isConfigFieldRequired('columns')\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add\"\n [disabled]=\"!convertToFileColumnDraft().trim()\"\n (onClick)=\"addConvertToFileColumns()\"\n />\n </div>\n </div>\n }\n </div>\n }\n\n @if (convertToFileIsCsv()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['includeHeaders'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('includeHeaders', $event === true)\n \"\n label=\"Include headers\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['delimiter'] ?? ','\"\n (ngModelChange)=\"onConfigFieldChange('delimiter', $event)\"\n label=\"Delimiter\"\n [required]=\"isConfigFieldRequired('delimiter')\"\n />\n <mt-text-field\n [ngModel]=\"config()['encoding'] ?? 'utf-8'\"\n [readonly]=\"true\"\n label=\"Encoding\"\n />\n </div>\n }\n\n @if (convertToFileIsJson()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['pretty'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('pretty', $event === true)\n \"\n label=\"Pretty JSON\"\n />\n </div>\n }\n\n @if (convertToFileIsHtml()) {\n <div class=\"mt-4 grid gap-3\">\n <mt-text-field\n [ngModel]=\"config()['title'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:title')\"\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\n label=\"Title\"\n [required]=\"isConfigFieldRequired('title')\"\n />\n <mt-textarea-field\n class=\"w-full\"\n [ngModel]=\"config()['body'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:body')\"\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\n label=\"Body\"\n [required]=\"isConfigFieldRequired('body')\"\n rows=\"7\"\n />\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['sanitize'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('sanitize', $event === true)\n \"\n label=\"Sanitize HTML\"\n />\n </div>\n }\n\n @if (convertToFileIsIcs()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"config()['summary'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:summary')\"\n (ngModelChange)=\"onConfigFieldChange('summary', $event)\"\n label=\"Event summary\"\n [required]=\"isConfigFieldRequired('summary')\"\n />\n <mt-text-field\n [ngModel]=\"config()['timezone'] ?? 'UTC'\"\n (ngModelChange)=\"onConfigFieldChange('timezone', $event)\"\n label=\"Timezone\"\n [required]=\"isConfigFieldRequired('timezone')\"\n />\n <mt-date-field\n [ngModel]=\"config()['start'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('start', $event)\"\n label=\"Start\"\n [required]=\"isConfigFieldRequired('start')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n <mt-date-field\n [ngModel]=\"config()['end'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('end', $event)\"\n label=\"End\"\n [required]=\"isConfigFieldRequired('end')\"\n [showTime]=\"true\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n <mt-text-field\n [ngModel]=\"config()['location'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:location')\"\n (ngModelChange)=\"onConfigFieldChange('location', $event)\"\n label=\"Location\"\n [required]=\"isConfigFieldRequired('location')\"\n />\n <mt-textarea-field\n class=\"w-full\"\n [ngModel]=\"config()['description'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:description')\"\n (ngModelChange)=\"onConfigFieldChange('description', $event)\"\n label=\"Description\"\n [required]=\"isConfigFieldRequired('description')\"\n rows=\"4\"\n />\n </div>\n }\n\n @if (convertToFileIsSpreadsheet()) {\n <div\n class=\"mt-4 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"mb-3 flex items-center justify-between gap-3\">\n <div class=\"fp-ae-label\">Sheets</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add sheet\"\n (onClick)=\"addConvertToFileSheet()\"\n />\n </div>\n <div class=\"grid gap-3\">\n @for (sheet of convertToFileSheetRows(); track sheet.index) {\n <div\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\n >\n <div\n class=\"grid gap-3 md:grid-cols-[minmax(140px,0.45fr)_minmax(0,1fr)_minmax(180px,0.6fr)_auto]\"\n >\n <mt-text-field\n [ngModel]=\"sheet.name\"\n (ngModelChange)=\"\n updateConvertToFileSheet(\n sheet.index,\n 'name',\n $event\n )\n \"\n label=\"Sheet name\"\n [required]=\"isConfigFieldRequired('sheets')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"sheet.sourceExpression\"\n (focusin)=\"\n setExpressionTarget(\n 'config:sheets:' + sheet.index + ':source'\n )\n \"\n (ngModelChange)=\"\n updateConvertToFileSheet(\n sheet.index,\n 'sourceExpression',\n $event\n )\n \"\n label=\"Source\"\n [required]=\"isConfigFieldRequired('sheets')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"sheet.columnsText\"\n (ngModelChange)=\"\n updateConvertToFileSheet(\n sheet.index,\n 'columns',\n $event\n )\n \"\n label=\"Columns\"\n [required]=\"isConfigFieldRequired('sheets')\"\n />\n <div class=\"flex items-end justify-end\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"danger\"\n icon=\"general.trash-01\"\n tooltip=\"Remove sheet\"\n (onClick)=\"removeConvertToFileSheet(sheet.index)\"\n />\n </div>\n </div>\n </div>\n } @empty {\n <div\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\n >\n Single sheet uses the selected data source.\n </div>\n }\n </div>\n </div>\n }\n\n @if (convertToFileIsText()) {\n <div class=\"mt-4 grid gap-3\">\n <mt-textarea-field\n class=\"w-full\"\n [ngModel]=\"config()['content'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:content')\"\n (ngModelChange)=\"onConfigFieldChange('content', $event)\"\n label=\"Content\"\n [required]=\"isConfigFieldRequired('content')\"\n rows=\"7\"\n />\n <mt-text-field\n [ngModel]=\"config()['encoding'] ?? 'utf-8'\"\n [readonly]=\"true\"\n label=\"Encoding\"\n />\n </div>\n }\n\n @if (convertToFileIsBase64()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"config()['base64Expression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:base64Expression')\"\n (ngModelChange)=\"\n onConfigFieldChange('base64Expression', $event)\n \"\n label=\"Base64 source\"\n [required]=\"isConfigFieldRequired('base64Expression')\"\n rows=\"5\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['extension'] ?? convertToFileExtension()\"\n (ngModelChange)=\"onConfigFieldChange('extension', $event)\"\n label=\"Extension\"\n [required]=\"isConfigFieldRequired('extension')\"\n />\n </div>\n }\n </section>\n }\n }\n @case (\"DataTransform\") {\n @if (sectionInMain(\"dataTransform\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">\n {{ dataTransformActionLabel() }}\n </div>\n @if (dataTransformUnavailableReason(); as reason) {\n <div\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ reason }}\n </div>\n }\n\n @if (\n !dataTransformIsMerge() && dataTransformUsesKnownActionEditor()\n ) {\n <div class=\"grid gap-3\">\n <div class=\"grid gap-3 md:grid-cols-[minmax(220px,0.45fr)_1fr]\">\n <mt-select-field\n [ngModel]=\"dataTransformSourcePreset()\"\n (ngModelChange)=\"onDataTransformSourcePresetChange($event)\"\n [label]=\"dataTransformSourceLabel()\"\n hint=\"Choose which list this action should transform.\"\n [required]=\"isConfigFieldRequired('sourceExpression')\"\n [options]=\"dataTransformSourceOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (showDataTransformSourceExpression()) {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"dataTransformSourceExpression()\"\n (focusin)=\"setExpressionTarget('config:sourceExpression')\"\n (ngModelChange)=\"\n onDataTransformSourceExpressionChange($event)\n \"\n [label]=\"dataTransformSourceExpressionLabel()\"\n hint=\"Must resolve to an array of items.\"\n [required]=\"isConfigFieldRequired('sourceExpression')\"\n />\n } @else {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2\"\n >\n <div class=\"fp-ae-label mb-1\">Selected input</div>\n <div\n class=\"truncate font-mono text-sm text-(--p-text-color)\"\n >\n {{ dataTransformSourceSummary() }}\n </div>\n </div>\n }\n </div>\n <div class=\"flex flex-wrap gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.search-sm\"\n label=\"Preview source\"\n [disabled]=\"!dataTransformSourceExpression()\"\n (onClick)=\"previewDataTransformSourceExpression()\"\n />\n </div>\n </div>\n }\n\n @if (expressionPreview(); as preview) {\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Source preview</div>\n <pre class=\"fp-ae-code\">{{ schemaText(preview) }}</pre>\n </div>\n }\n\n @if (dataTransformIsFilter()) {\n <div class=\"mt-4 grid gap-3\">\n @if (dataTransformUsesStructuredConditionEditor()) {\n <div class=\"fp-ae-label\">Keep item when</div>\n <div class=\"grid gap-3 md:grid-cols-[1fr_180px_1fr]\">\n <mt-text-field\n [ngModel]=\"dataTransformConditionFieldText('left')\"\n (focusin)=\"setExpressionTarget('config:condition:left')\"\n (ngModelChange)=\"\n onDataTransformConditionFieldChange('left', $event)\n \"\n label=\"Left value\"\n hint=\"Field, value, or expression from each item.\"\n [required]=\"isConfigFieldRequired('condition.left')\"\n />\n <mt-select-field\n [ngModel]=\"\n dataTransformConditionFieldValue('operator') || 'equals'\n \"\n (ngModelChange)=\"\n onDataTransformConditionFieldChange('operator', $event)\n \"\n label=\"Operator\"\n [required]=\"isConfigFieldRequired('condition.operator')\"\n [options]=\"ifOperatorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n [ngModel]=\"dataTransformConditionFieldText('right')\"\n (focusin)=\"setExpressionTarget('config:condition:right')\"\n (ngModelChange)=\"\n onDataTransformConditionFieldChange('right', $event)\n \"\n label=\"Right value\"\n hint=\"Value or expression to compare against.\"\n [required]=\"isConfigFieldRequired('condition.right')\"\n />\n </div>\n } @else {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"dataTransformConditionText()\"\n (focusin)=\"setExpressionTarget('config:condition')\"\n (ngModelChange)=\"onDataTransformConditionChange($event)\"\n label=\"Keep item when\"\n hint=\"Condition expression or JSON supplied by the backend catalog.\"\n [required]=\"isConfigFieldRequired('condition')\"\n rows=\"4\"\n />\n }\n </div>\n }\n\n @if (dataTransformIsSort()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['sortBy'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:sortBy')\"\n (ngModelChange)=\"onConfigFieldChange('sortBy', $event)\"\n label=\"Sort by field\"\n hint=\"Field path inside each item, for example createdAt.\"\n [required]=\"isConfigFieldRequired('sortBy')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['sortExpression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:sortExpression')\"\n (ngModelChange)=\"\n onConfigFieldChange('sortExpression', $event)\n \"\n label=\"Custom sort expression\"\n hint=\"Optional expression when a simple field path is not enough.\"\n [required]=\"isConfigFieldRequired('sortExpression')\"\n />\n <mt-select-field\n [ngModel]=\"config()['direction'] ?? 'asc'\"\n (ngModelChange)=\"onConfigFieldChange('direction', $event)\"\n label=\"Direction\"\n [required]=\"isConfigFieldRequired('direction')\"\n [options]=\"dataTransformDirectionOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['valueType'] ?? 'string'\"\n (ngModelChange)=\"onConfigFieldChange('valueType', $event)\"\n label=\"Value type\"\n [required]=\"isConfigFieldRequired('valueType')\"\n [options]=\"dataTransformValueTypeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n }\n\n @if (dataTransformIsLimit()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-number-field\n [ngModel]=\"numberValue(config()['offset']) ?? 0\"\n (ngModelChange)=\"onConfigFieldChange('offset', $event)\"\n label=\"Skip first\"\n [required]=\"isConfigFieldRequired('offset')\"\n [min]=\"0\"\n />\n <mt-number-field\n [ngModel]=\"numberValue(config()['limit'])\"\n (ngModelChange)=\"onConfigFieldChange('limit', $event)\"\n label=\"Take at most\"\n [required]=\"isConfigFieldRequired('limit')\"\n [min]=\"1\"\n />\n </div>\n }\n\n @if (dataTransformIsMapFields()) {\n <div class=\"mt-4 grid gap-3\">\n <mt-select-field\n [ngModel]=\"config()['mode'] ?? 'merge'\"\n (ngModelChange)=\"onConfigFieldChange('mode', $event)\"\n label=\"Field mode\"\n [required]=\"isConfigFieldRequired('mode')\"\n [options]=\"dataTransformMapModeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'fields',\n rows: fieldsRows(),\n keyLabel: 'Output field',\n valueLabel: 'Value or expression',\n addLabel: 'Add mapped field',\n emptyText:\n 'No fields mapped yet. Add an output field to create or update item data.',\n keyHint: 'Name of the field in the output item.',\n valueHint:\n 'Use a fixed value or an expression from each item.',\n required: isConfigFieldRequired('fields'),\n }\n \"\n />\n </div>\n }\n\n @if (dataTransformIsAggregate()) {\n <div class=\"mt-4 grid gap-3\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['groupBy'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:groupBy')\"\n (ngModelChange)=\"onConfigFieldChange('groupBy', $event)\"\n label=\"Group items by\"\n hint=\"Optional field path or expression used to group results.\"\n [required]=\"isConfigFieldRequired('groupBy')\"\n />\n <div class=\"rounded-md border border-surface-200 p-3\">\n <div\n class=\"mb-3 flex flex-wrap items-center justify-between gap-2\"\n >\n <div class=\"fp-ae-label\">Summary fields</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add summary\"\n (onClick)=\"addDataTransformAggregation()\"\n />\n </div>\n <div class=\"space-y-3\">\n @for (\n item of dataTransformAggregationRows();\n track item.index\n ) {\n <div\n class=\"grid gap-2 rounded-md bg-surface-50 p-3 md:grid-cols-[minmax(160px,0.8fr)_minmax(180px,0.9fr)_1fr_minmax(150px,0.7fr)_auto]\"\n >\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"item.key\"\n (ngModelChange)=\"\n updateDataTransformAggregation(\n item.index,\n 'key',\n $event\n )\n \"\n label=\"Output field\"\n hint=\"Name saved in the summary output.\"\n [required]=\"isConfigFieldRequired('aggregations')\"\n />\n <mt-select-field\n [ngModel]=\"item.operation\"\n (ngModelChange)=\"\n updateDataTransformAggregation(\n item.index,\n 'operation',\n $event\n )\n \"\n label=\"Calculation\"\n [required]=\"isConfigFieldRequired('aggregations')\"\n [options]=\"dataTransformAggregationOperationOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"item.field\"\n [readonly]=\"\n !dataTransformAggregationNeedsField(item.operation)\n \"\n (focusin)=\"\n setExpressionTarget(\n 'config:aggregations:' + item.index + ':field'\n )\n \"\n (ngModelChange)=\"\n updateDataTransformAggregation(\n item.index,\n 'field',\n $event\n )\n \"\n label=\"Field or expression\"\n [hint]=\"\n dataTransformAggregationFieldHint(item.operation)\n \"\n [required]=\"\n isConfigFieldRequired('aggregations') &&\n dataTransformAggregationNeedsField(item.operation)\n \"\n />\n <mt-select-field\n [ngModel]=\"item.valueType\"\n (ngModelChange)=\"\n updateDataTransformAggregation(\n item.index,\n 'valueType',\n $event\n )\n \"\n label=\"Value type\"\n [required]=\"isConfigFieldRequired('aggregations')\"\n [options]=\"dataTransformValueTypeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-button\n class=\"self-end\"\n size=\"small\"\n severity=\"secondary\"\n variant=\"outlined\"\n label=\"Remove\"\n (onClick)=\"removeDataTransformAggregation(item.index)\"\n />\n </div>\n } @empty {\n <div\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\n >\n No summary fields yet. Add a summary such as count,\n total, average, minimum, or maximum.\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n @if (dataTransformIsMerge()) {\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"config()['leftExpression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:leftExpression')\"\n (ngModelChange)=\"\n onConfigFieldChange('leftExpression', $event)\n \"\n label=\"First list\"\n hint=\"Expression that resolves to the first list of items.\"\n [required]=\"isConfigFieldRequired('leftExpression')\"\n rows=\"3\"\n />\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"config()['rightExpression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:rightExpression')\"\n (ngModelChange)=\"\n onConfigFieldChange('rightExpression', $event)\n \"\n label=\"Second list\"\n hint=\"Expression that resolves to the second list of items.\"\n [required]=\"isConfigFieldRequired('rightExpression')\"\n rows=\"3\"\n />\n <mt-select-field\n [ngModel]=\"config()['strategy'] ?? 'append'\"\n (ngModelChange)=\"onConfigFieldChange('strategy', $event)\"\n label=\"Merge strategy\"\n [required]=\"isConfigFieldRequired('strategy')\"\n [options]=\"dataTransformMergeStrategyOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (dataTransformUsesKeyedMerge()) {\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['leftKey'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('leftKey', $event)\"\n label=\"First list key\"\n [required]=\"isConfigFieldRequired('leftKey')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['rightKey'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('rightKey', $event)\"\n label=\"Second list key\"\n [required]=\"isConfigFieldRequired('rightKey')\"\n />\n </div>\n }\n </div>\n }\n\n @if (!dataTransformUsesKnownActionEditor()) {\n <div class=\"mt-4 grid gap-3\">\n @for (field of dataTransformGenericFields(); track field.key) {\n @if (field.enumValues.length) {\n <mt-select-field\n [ngModel]=\"config()[field.key] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n [options]=\"enumOptions(field.enumValues)\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n } @else if (field.type === \"number\") {\n <mt-number-field\n [ngModel]=\"numberValue(config()[field.key])\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n } @else {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"fieldText(field.key)\"\n (focusin)=\"setExpressionTarget('config:' + field.key)\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n }\n } @empty {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"dataTransformActionKey()\"\n [readonly]=\"true\"\n label=\"Action\"\n />\n }\n </div>\n }\n\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\n <mt-number-field\n [ngModel]=\"numberValue(config()['maxOutputItems'])\"\n (ngModelChange)=\"onConfigFieldChange('maxOutputItems', $event)\"\n label=\"Output limit\"\n [required]=\"isConfigFieldRequired('maxOutputItems')\"\n [min]=\"1\"\n />\n <div\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3 md:col-span-2\"\n >\n <div class=\"fp-ae-label mb-2\">Output</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (field of dataTransformOutputFieldLabels; track field) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ field }}</span\n >\n }\n </div>\n </div>\n </div>\n </section>\n }\n }\n @case (\"LoopOverItems\") {\n @if (sectionInMain(\"loopOverItems\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Loop over items</div>\n\n <div class=\"grid gap-3\">\n <div class=\"grid gap-3 md:grid-cols-[minmax(220px,0.45fr)_1fr]\">\n <mt-select-field\n [ngModel]=\"loopSourceMode()\"\n (ngModelChange)=\"onLoopSourceModeChange($event)\"\n label=\"List source\"\n hint=\"Choose an output array or type an array expression.\"\n [required]=\"isConfigFieldRequired('itemsExpression')\"\n [options]=\"loopSourceModeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (showLoopItemsExpression()) {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"loopItemsExpression()\"\n (focusin)=\"setExpressionTarget('config:itemsExpression')\"\n (ngModelChange)=\"onLoopItemsExpressionChange($event)\"\n label=\"Array expression\"\n hint=\"Must resolve to an array.\"\n [required]=\"isConfigFieldRequired('itemsExpression')\"\n />\n } @else {\n <mt-select-field\n [ngModel]=\"loopSelectedOutputArray()\"\n (ngModelChange)=\"onLoopOutputArrayChange($event)\"\n label=\"Output array\"\n hint=\"Arrays exposed by previous connected steps.\"\n [required]=\"isConfigFieldRequired('itemsExpression')\"\n [options]=\"loopOutputArrayOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n }\n </div>\n <div class=\"flex flex-wrap gap-2\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.search-sm\"\n label=\"Preview array\"\n [disabled]=\"!loopItemsExpression()\"\n (onClick)=\"previewLoopItemsExpression()\"\n />\n </div>\n </div>\n\n @if (expressionPreview(); as preview) {\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Expression preview</div>\n <pre class=\"fp-ae-code\">{{ schemaText(preview) }}</pre>\n </div>\n }\n\n <div class=\"mt-4 border-t border-surface-100 pt-4\">\n <div class=\"fp-ae-label mb-3\">Run settings</div>\n <div class=\"grid gap-3 md:grid-cols-2 xl:grid-cols-4\">\n <mt-number-field\n [ngModel]=\"\n numberValue(\n config()['maxItems'] ?? config()['maxIterations']\n )\n \"\n (ngModelChange)=\"onConfigFieldChange('maxItems', $event)\"\n label=\"Item limit\"\n hint=\"Optional maximum number of items to process.\"\n [required]=\"isConfigFieldRequired('maxItems')\"\n [min]=\"1\"\n />\n <mt-select-field\n [ngModel]=\"loopFailureBehavior()\"\n (ngModelChange)=\"onLoopFailureBehaviorChange($event)\"\n label=\"When an item fails\"\n [required]=\"isConfigFieldRequired('failureBehavior')\"\n [options]=\"loopFailureBehaviorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['emptyBehavior'] ?? 'done'\"\n (ngModelChange)=\"onConfigFieldChange('emptyBehavior', $event)\"\n label=\"When list is empty\"\n [required]=\"isConfigFieldRequired('emptyBehavior')\"\n [options]=\"loopEmptyBehaviorOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-toggle-field\n class=\"self-end pb-1\"\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['collectResults'] === true\"\n (ngModelChange)=\"\n onConfigFieldChange('collectResults', $event === true)\n \"\n label=\"Save item results\"\n hint=\"Stores a bounded summary for the done route.\"\n />\n </div>\n </div>\n\n <details class=\"mt-4 border-t border-surface-100 pt-3\">\n <summary\n class=\"cursor-pointer text-sm font-semibold text-(--p-text-color)\"\n >\n Advanced runtime details\n </summary>\n <div class=\"mt-3 grid gap-3 md:grid-cols-3\">\n <div>\n <div class=\"fp-ae-label mb-1\">Current item token</div>\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2 font-mono text-sm text-(--p-text-color)\"\n >\n $item.json\n </div>\n </div>\n <div>\n <div class=\"fp-ae-label mb-1\">Loop context token</div>\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2 font-mono text-sm text-(--p-text-color)\"\n >\n $loop.current\n </div>\n </div>\n <mt-number-field\n [ngModel]=\"numberValue(config()['concurrency']) ?? 1\"\n (ngModelChange)=\"onLoopConcurrencyChange($event)\"\n label=\"Concurrency\"\n hint=\"This deployment supports 1.\"\n [required]=\"isConfigFieldRequired('concurrency')\"\n [min]=\"1\"\n [max]=\"1\"\n />\n </div>\n </details>\n </section>\n }\n }\n @case (\"FlowPlusCommit\") {\n @if (sectionInMain(\"flowplusCommit\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">FlowPlus commit</div>\n <p class=\"fp-ae-copy\">\n This node is the explicit module-data write boundary. Approval\n does not commit data unless this node is reached.\n </p>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['targetModule'] ?? ''\"\n (ngModelChange)=\"onModuleChange($event)\"\n label=\"Module\"\n hint=\"Module whose records will be written by this explicit FlowPlus commit.\"\n [required]=\"isConfigFieldRequired('targetModule')\"\n [options]=\"moduleOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['operation'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('operation', $event)\"\n label=\"Operation\"\n hint=\"Write operation supported by the selected backend module schema.\"\n [required]=\"isConfigFieldRequired('operation')\"\n [options]=\"operationOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <mt-text-field\n class=\"mt-3 font-mono\"\n [ngModel]=\"config()['idempotencyKey'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:idempotencyKey')\"\n (ngModelChange)=\"onConfigFieldChange('idempotencyKey', $event)\"\n label=\"Idempotency key\"\n hint=\"Optional stable key used by the backend to prevent duplicate writes.\"\n [required]=\"isConfigFieldRequired('idempotencyKey')\"\n />\n @if (selectedModuleFields().length) {\n <div class=\"mt-3 space-y-2\">\n <div class=\"fp-ae-section-title\">Module schema</div>\n <div class=\"grid gap-2 md:grid-cols-2\">\n @for (field of selectedModuleFields(); track field.key) {\n <div class=\"fp-ae-kv\">\n <span>{{ field.displayName ?? field.key }}</span>\n <strong\n >{{ field.viewType ?? \"Value\" }}\n @if (field.required) {\n *\n }\n </strong>\n </div>\n }\n </div>\n </div>\n }\n <div class=\"mt-3\">\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'mapping',\n rows: mappingRows(),\n keyLabel: 'Module field',\n valueLabel: 'Expression / value',\n addLabel: 'Add module field',\n required: isConfigFieldRequired('mapping'),\n }\n \"\n />\n </div>\n <div class=\"mt-3 flex gap-2\">\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Validate mapping\"\n (onClick)=\"validateCommitMapping()\"\n />\n </div>\n @if (commitValidation(); as result) {\n <div\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\n >\n {{\n result.isValid === false\n ? \"Mapping invalid\"\n : \"Mapping accepted by helper\"\n }}\n @for (issue of resultIssues(result); track $index) {\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\n {{ issue }}\n </div>\n }\n </div>\n }\n @if (sectionInMain(\"credentials\")) {\n <div\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"fp-ae-label mb-2\">Credential</div>\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\n </div>\n }\n </section>\n }\n }\n @case (\"WebhookResponse\") {\n @if (sectionInMain(\"webhookResponse\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Webhook response</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-number-field\n [ngModel]=\"numberValue(config()['statusCode']) ?? 200\"\n (ngModelChange)=\"onConfigFieldChange('statusCode', $event)\"\n label=\"Status code\"\n hint=\"HTTP status code sent back by the webhook response node.\"\n [required]=\"isConfigFieldRequired('statusCode')\"\n [min]=\"100\"\n [max]=\"599\"\n />\n <mt-select-field\n [ngModel]=\"config()['responseMode'] ?? 'json'\"\n (ngModelChange)=\"onConfigFieldChange('responseMode', $event)\"\n label=\"Response mode\"\n hint=\"How the webhook response body should be serialized.\"\n [required]=\"isConfigFieldRequired('responseMode')\"\n [options]=\"responseModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"mt-3\">\n <ng-container\n *ngTemplateOutlet=\"\n mapEditor;\n context: {\n objectKey: 'headers',\n rows: headerRows(),\n keyLabel: 'Header',\n valueLabel: 'Value',\n addLabel: 'Add header',\n }\n \"\n />\n </div>\n <mt-textarea-field\n class=\"mt-3 w-full font-mono\"\n [ngModel]=\"valueText(config()['body'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:body')\"\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\n label=\"Body\"\n hint=\"Body returned to the webhook caller. Expressions can use current execution data.\"\n [required]=\"isConfigFieldRequired('body')\"\n rows=\"6\"\n />\n </section>\n }\n }\n @case (\"CallAutomation\") {\n @if (sectionInMain(\"callAutomation\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Subworkflow</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['targetAutomationId'] ?? ''\"\n (ngModelChange)=\"onSubworkflowTargetChange($event)\"\n label=\"Target automation\"\n hint=\"Backend-provided automation candidate. Tenant and recursion validation stay on the backend.\"\n [required]=\"isConfigFieldRequired('targetAutomationId')\"\n [options]=\"subworkflowAutomationOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['revisionMode'] ?? 'Active'\"\n (ngModelChange)=\"onConfigFieldChange('revisionMode', $event)\"\n label=\"Revision mode\"\n hint=\"Choose which published or active child revision to call.\"\n [required]=\"isConfigFieldRequired('revisionMode')\"\n [options]=\"revisionModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (config()[\"revisionMode\"] === \"SpecificRevision\") {\n <mt-text-field\n [ngModel]=\"config()['specificRevisionId'] ?? ''\"\n (ngModelChange)=\"\n onConfigFieldChange('specificRevisionId', $event)\n \"\n label=\"Specific revision id\"\n hint=\"Required only when revision mode is Specific revision.\"\n [required]=\"isConfigFieldRequired('specificRevisionId')\"\n />\n }\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['waitForCompletion'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('waitForCompletion', $event === true)\n \"\n label=\"Wait for completion\"\n hint=\"When disabled, the parent continues after dispatch and backend records the child linkage.\"\n />\n </div>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['inputMappingJson'] ?? '{}')\"\n (focusin)=\"setExpressionTarget('config:inputMappingJson')\"\n (ngModelChange)=\"\n onConfigFieldChange('inputMappingJson', $event)\n \"\n label=\"Input mapping JSON\"\n hint=\"JSON object or expression map sent into the child automation input.\"\n [required]=\"isConfigFieldRequired('inputMappingJson')\"\n rows=\"7\"\n />\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['outputMappingJson'] ?? '{}')\"\n (focusin)=\"setExpressionTarget('config:outputMappingJson')\"\n (ngModelChange)=\"\n onConfigFieldChange('outputMappingJson', $event)\n \"\n label=\"Output mapping JSON\"\n hint=\"JSON object or expression map for child output/status returned to the parent.\"\n [required]=\"isConfigFieldRequired('outputMappingJson')\"\n rows=\"7\"\n />\n </div>\n </section>\n }\n }\n @case (\"Subworkflow\") {\n @if (sectionInMain(\"callAutomation\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Subworkflow</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['targetAutomationId'] ?? ''\"\n (ngModelChange)=\"onSubworkflowTargetChange($event)\"\n label=\"Target automation\"\n hint=\"Backend-provided automation candidate. Tenant and recursion validation stay on the backend.\"\n [required]=\"isConfigFieldRequired('targetAutomationId')\"\n [options]=\"subworkflowAutomationOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-select-field\n [ngModel]=\"config()['revisionMode'] ?? 'Active'\"\n (ngModelChange)=\"onConfigFieldChange('revisionMode', $event)\"\n label=\"Revision mode\"\n hint=\"Choose which published or active child revision to call.\"\n [required]=\"isConfigFieldRequired('revisionMode')\"\n [options]=\"revisionModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (config()[\"revisionMode\"] === \"SpecificRevision\") {\n <mt-text-field\n [ngModel]=\"config()['specificRevisionId'] ?? ''\"\n (ngModelChange)=\"\n onConfigFieldChange('specificRevisionId', $event)\n \"\n label=\"Specific revision id\"\n hint=\"Required only when revision mode is Specific revision.\"\n [required]=\"isConfigFieldRequired('specificRevisionId')\"\n />\n }\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['waitForCompletion'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('waitForCompletion', $event === true)\n \"\n label=\"Wait for completion\"\n hint=\"When disabled, the parent continues after dispatch and backend records the child linkage.\"\n />\n </div>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['inputMappingJson'] ?? '{}')\"\n (focusin)=\"setExpressionTarget('config:inputMappingJson')\"\n (ngModelChange)=\"\n onConfigFieldChange('inputMappingJson', $event)\n \"\n label=\"Input mapping JSON\"\n hint=\"JSON object or expression map sent into the child automation input.\"\n [required]=\"isConfigFieldRequired('inputMappingJson')\"\n rows=\"7\"\n />\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['outputMappingJson'] ?? '{}')\"\n (focusin)=\"setExpressionTarget('config:outputMappingJson')\"\n (ngModelChange)=\"\n onConfigFieldChange('outputMappingJson', $event)\n \"\n label=\"Output mapping JSON\"\n hint=\"JSON object or expression map for child output/status returned to the parent.\"\n [required]=\"isConfigFieldRequired('outputMappingJson')\"\n rows=\"7\"\n />\n </div>\n </section>\n }\n }\n @case (\"ParallelStart\") {\n @if (sectionInMain(\"parallelStart\")) {\n <section class=\"fp-ae-panel\">\n <div\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-50 px-4 py-3\"\n >\n <div class=\"fp-ae-section-title !m-0\">Parallel start</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add branch\"\n (onClick)=\"addParallelBranch()\"\n />\n </div>\n <div class=\"space-y-3 p-4\">\n @for (\n branch of parallelBranches();\n track branch.key;\n let i = $index\n ) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div class=\"grid gap-3 md:grid-cols-[160px_1fr_1fr_auto]\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"branch.key\"\n (ngModelChange)=\"updateParallelBranch(i, 'key', $event)\"\n label=\"Branch key\"\n hint=\"Persisted route key. Do not use array index names.\"\n [required]=\"isConfigFieldRequired('branches')\"\n />\n <mt-text-field\n [ngModel]=\"branch.label\"\n (ngModelChange)=\"updateParallelBranch(i, 'label', $event)\"\n label=\"Label\"\n hint=\"Visual branch label. Changing it does not change existing routes.\"\n [required]=\"isConfigFieldRequired('branches')\"\n />\n <mt-text-field\n [ngModel]=\"branch.description\"\n (ngModelChange)=\"\n updateParallelBranch(i, 'description', $event)\n \"\n label=\"Description\"\n hint=\"Optional branch note for reviewers.\"\n [required]=\"false\"\n />\n <div class=\"flex items-end gap-1\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"arrow.arrow-up\"\n tooltip=\"Move up\"\n (onClick)=\"moveParallelBranch(i, -1)\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"arrow.arrow-down\"\n tooltip=\"Move down\"\n (onClick)=\"moveParallelBranch(i, 1)\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"danger\"\n icon=\"general.trash-01\"\n tooltip=\"Remove branch\"\n (onClick)=\"removeParallelBranch(i)\"\n />\n </div>\n </div>\n <div\n class=\"mt-3 flex flex-wrap items-center gap-1.5 text-[11px]\"\n >\n <span\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-50 px-2 font-mono font-semibold text-(--p-text-color)\"\n >{{ branch.key }}</span\n >\n <span\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 font-medium text-(--p-text-muted-color)\"\n >{{ branch.routeCount }} connected route{{\n branch.routeCount === 1 ? \"\" : \"s\"\n }}</span\n >\n </div>\n </div>\n }\n @if (parallelBranches().length === 0) {\n <p class=\"fp-ae-copy\">\n Add at least two stable branch keys. Backend validation blocks\n publish until branches are valid.\n </p>\n }\n </div>\n </section>\n }\n }\n @case (\"ParallelJoin\") {\n @if (sectionInMain(\"parallelJoin\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Parallel join</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-select-field\n [ngModel]=\"config()['joinPolicy'] ?? 'All'\"\n (ngModelChange)=\"onConfigFieldChange('joinPolicy', $event)\"\n label=\"Join policy\"\n hint=\"Backend policy used to decide when branch wait is complete.\"\n [required]=\"isConfigFieldRequired('joinPolicy')\"\n [options]=\"joinPolicyOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n @if (config()[\"joinPolicy\"] === \"Threshold\") {\n <mt-number-field\n [ngModel]=\"numberValue(config()['threshold'])\"\n (ngModelChange)=\"onConfigFieldChange('threshold', $event)\"\n label=\"Threshold\"\n hint=\"Minimum completed branch count required for Threshold policy.\"\n [required]=\"isConfigFieldRequired('threshold')\"\n [min]=\"1\"\n />\n }\n <mt-select-field\n [ngModel]=\"config()['aggregationStrategy'] ?? 'MergeObjects'\"\n (ngModelChange)=\"\n onConfigFieldChange('aggregationStrategy', $event)\n \"\n label=\"Aggregation strategy\"\n hint=\"How backend joins branch outputs into the join node output.\"\n [required]=\"isConfigFieldRequired('aggregationStrategy')\"\n [options]=\"aggregationStrategyOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['outputTargetPath'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:outputTargetPath')\"\n (ngModelChange)=\"\n onConfigFieldChange('outputTargetPath', $event)\n \"\n label=\"Output target path\"\n hint=\"Optional context path where joined output should be written.\"\n [required]=\"isConfigFieldRequired('outputTargetPath')\"\n />\n </div>\n </section>\n }\n }\n @case (\"Switch\") {\n @if (sectionInMain(\"switch\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Switch</div>\n <div class=\"grid gap-4\">\n <div\n class=\"grid items-end gap-3 md:grid-cols-[minmax(0,1fr)_minmax(280px,0.7fr)]\"\n >\n <mt-select-field\n [ngModel]=\"config()['mode'] ?? 'value'\"\n (ngModelChange)=\"onConfigFieldChange('mode', $event)\"\n label=\"Mode\"\n hint=\"Backend evaluation mode: expression, rules, or source-value matching.\"\n [required]=\"isConfigFieldRequired('mode')\"\n [options]=\"switchModeOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n <mt-toggle-field\n class=\"self-end pb-1\"\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['firstMatch'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('firstMatch', $event === true)\n \"\n label=\"First match\"\n hint=\"Use the first matching case unless backend explicitly supports multi-match.\"\n />\n </div>\n @if (showSwitchSourceValue() || showSwitchExpression()) {\n <div class=\"grid gap-3 md:grid-cols-2\">\n @if (showSwitchSourceValue()) {\n <mt-text-field\n [class]=\"\n showSwitchExpression()\n ? 'font-mono'\n : 'font-mono md:col-span-2'\n \"\n [ngModel]=\"config()['sourceValue'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:sourceValue')\"\n (ngModelChange)=\"\n onConfigFieldChange('sourceValue', $event)\n \"\n label=\"Source value\"\n hint=\"Value or expression used for value matching.\"\n [required]=\"isConfigFieldRequired('sourceValue')\"\n />\n }\n @if (showSwitchExpression()) {\n <mt-text-field\n [class]=\"\n showSwitchSourceValue()\n ? 'font-mono'\n : 'font-mono md:col-span-2'\n \"\n [ngModel]=\"config()['expression'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:expression')\"\n (ngModelChange)=\"\n onConfigFieldChange('expression', $event)\n \"\n label=\"Expression\"\n hint=\"Expression evaluated when mode is expression or rules.\"\n [required]=\"isConfigFieldRequired('expression')\"\n />\n }\n </div>\n }\n <div\n class=\"grid items-end gap-3 md:grid-cols-[minmax(0,1fr)_minmax(280px,0.7fr)]\"\n >\n @if (showSwitchDefaultOutputKey()) {\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"config()['defaultOutputKey'] ?? 'default'\"\n (ngModelChange)=\"\n onConfigFieldChange('defaultOutputKey', $event)\n \"\n label=\"Default output key\"\n hint=\"Stable default route key used when no case matches.\"\n [required]=\"isConfigFieldRequired('defaultOutputKey')\"\n />\n }\n <mt-toggle-field\n [class]=\"\n showSwitchDefaultOutputKey()\n ? 'self-end pb-1'\n : 'self-end pb-1 md:col-span-2'\n \"\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"config()['includeDefaultOutput'] !== false\"\n (ngModelChange)=\"\n onConfigFieldChange('includeDefaultOutput', $event === true)\n \"\n label=\"Include default output\"\n hint=\"Expose the default route output key in the canvas.\"\n />\n </div>\n </div>\n </section>\n <section class=\"fp-ae-panel\">\n <div\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-50 px-4 py-3\"\n >\n <div class=\"fp-ae-section-title !m-0\">Switch cases</div>\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"general.plus\"\n label=\"Add case\"\n (onClick)=\"addSwitchCase()\"\n />\n </div>\n <div class=\"space-y-3 p-4\">\n @for (item of switchCases(); track item.key; let i = $index) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\n >\n <div\n class=\"flex flex-wrap items-start justify-between gap-3 border-b border-surface-100 pb-3\"\n >\n <div class=\"min-w-0\">\n <div class=\"flex flex-wrap items-center gap-1.5\">\n <span\n class=\"text-sm font-semibold text-(--p-text-color)\"\n >Case {{ i + 1 }}</span\n >\n <span\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-50 px-2 font-mono text-[11px] font-semibold text-(--p-text-color)\"\n >{{ item.routeKey }}</span\n >\n <span\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 text-[11px] font-medium text-(--p-text-muted-color)\"\n >{{ item.routeCount }} connected route{{\n item.routeCount === 1 ? \"\" : \"s\"\n }}</span\n >\n </div>\n <div\n class=\"mt-1 truncate text-xs font-medium text-(--p-text-muted-color)\"\n >\n {{ item.label || item.routeKey }}\n </div>\n </div>\n <div class=\"flex shrink-0 items-center gap-1\">\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"arrow.arrow-up\"\n tooltip=\"Move up\"\n (onClick)=\"moveSwitchCase(i, -1)\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n icon=\"arrow.arrow-down\"\n tooltip=\"Move down\"\n (onClick)=\"moveSwitchCase(i, 1)\"\n />\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n severity=\"danger\"\n icon=\"general.trash-01\"\n tooltip=\"Remove case\"\n (onClick)=\"removeSwitchCase(i)\"\n />\n </div>\n </div>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"item.label\"\n (ngModelChange)=\"updateSwitchCase(i, 'label', $event)\"\n label=\"Case name\"\n hint=\"Shown in route labels.\"\n [required]=\"isConfigFieldRequired('cases')\"\n />\n @if (showSwitchCaseValue()) {\n <mt-text-field\n [ngModel]=\"item.value\"\n (focusin)=\"\n setExpressionTarget(\n 'config:cases:' + item.key + ':value'\n )\n \"\n (ngModelChange)=\"updateSwitchCase(i, 'value', $event)\"\n label=\"Match value\"\n hint=\"Compared with the source value.\"\n [required]=\"isConfigFieldRequired('cases')\"\n />\n }\n @if (showSwitchCaseCondition()) {\n <mt-text-field\n class=\"font-mono md:col-span-2\"\n [ngModel]=\"item.condition\"\n (focusin)=\"\n setExpressionTarget(\n 'config:cases:' + item.key + ':condition'\n )\n \"\n (ngModelChange)=\"\n updateSwitchCase(i, 'condition', $event)\n \"\n label=\"Rule condition\"\n hint=\"When true, this case is selected.\"\n [required]=\"isConfigFieldRequired('cases')\"\n />\n }\n </div>\n <details\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-xs text-(--p-text-muted-color)\"\n >\n <summary class=\"cursor-pointer font-medium\">\n Advanced route settings\n </summary>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"item.key\"\n (ngModelChange)=\"updateSwitchCase(i, 'key', $event)\"\n label=\"Route key\"\n hint=\"Stable output key for connected routes.\"\n [required]=\"isConfigFieldRequired('cases')\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"item.expression\"\n (focusin)=\"\n setExpressionTarget(\n 'config:cases:' + item.key + ':expression'\n )\n \"\n (ngModelChange)=\"\n updateSwitchCase(i, 'expression', $event)\n \"\n label=\"Advanced condition expression\"\n hint=\"Optional true or false expression for this case.\"\n [required]=\"false\"\n />\n </div>\n <div\n class=\"mt-3 inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 font-medium\"\n >\n Visual order {{ i + 1 }}\n </div>\n </details>\n </div>\n }\n @if (switchCases().length === 0) {\n <p class=\"fp-ae-copy\">\n Add a case for each route this switch can select.\n </p>\n }\n </div>\n </section>\n }\n }\n @case (\"Stop\") {\n @if (sectionInMain(\"stop\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">End execution</div>\n @if (\n supportsConfigKey(\"status\") ||\n supportsConfigKey(\"message\") ||\n supportsConfigKey(\"output\")\n ) {\n <div class=\"grid gap-3 md:grid-cols-2\">\n @if (supportsConfigKey(\"status\")) {\n <mt-text-field\n [ngModel]=\"config()['status'] ?? ''\"\n (ngModelChange)=\"onConfigFieldChange('status', $event)\"\n label=\"Result status\"\n hint=\"Terminal status emitted by the backend stop node.\"\n />\n }\n @if (supportsConfigKey(\"message\")) {\n <mt-text-field\n [ngModel]=\"config()['message'] ?? ''\"\n (focusin)=\"setExpressionTarget('config:message')\"\n (ngModelChange)=\"onConfigFieldChange('message', $event)\"\n label=\"Message\"\n hint=\"Optional message or reason saved with the terminal result.\"\n />\n }\n @if (supportsConfigKey(\"output\")) {\n <mt-textarea-field\n class=\"w-full font-mono md:col-span-2\"\n [ngModel]=\"valueText(config()['output'] ?? '')\"\n (focusin)=\"setExpressionTarget('config:output')\"\n (ngModelChange)=\"onConfigFieldChange('output', $event)\"\n label=\"Output payload\"\n hint=\"Terminal output payload when exposed by the backend schema.\"\n rows=\"5\"\n />\n }\n </div>\n } @else {\n <p class=\"fp-ae-copy\">\n Stop nodes use their backend defaults and do not expose extra\n configuration.\n </p>\n }\n </section>\n }\n }\n }\n\n @if (sectionInAdvanced(\"policy\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">\n Retry, timeout, and failure handling\n </div>\n <div class=\"grid gap-3 md:grid-cols-3\">\n <mt-number-field\n [ngModel]=\"policyNumberValue('timeoutPolicyJson', 'timeoutSeconds')\"\n (ngModelChange)=\"\n updatePolicyNumber('timeoutPolicyJson', 'timeoutSeconds', $event)\n \"\n label=\"Step timeout (seconds)\"\n hint=\"How long this step may run before Automation Engine marks it timed out.\"\n [min]=\"1\"\n />\n <mt-number-field\n [ngModel]=\"policyNumberValue('retryPolicyJson', 'maxAttempts')\"\n (ngModelChange)=\"\n updatePolicyNumber('retryPolicyJson', 'maxAttempts', $event)\n \"\n label=\"Max attempts\"\n hint=\"Total tries including the first run. Use 1 for no retry.\"\n [min]=\"1\"\n />\n <mt-select-field\n [ngModel]=\"errorPolicyMode()\"\n (ngModelChange)=\"updateErrorPolicyMode($event)\"\n label=\"When this step fails\"\n hint=\"Choose whether failures pause for recovery, use the failure path, or fail the workflow.\"\n [options]=\"errorPolicyOptions\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n @if (sectionHasAdvancedEmptyState(\"policy\")) {\n <p class=\"fp-ae-copy mt-3\">\n Backend defaults apply until you override a timeout, retry count, or\n failure rule here.\n </p>\n }\n </section>\n }\n\n @if (sectionInAdvanced(\"credentials\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Auth and credentials</div>\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\n </section>\n }\n\n @if (sectionInAdvanced(\"mapping\")) {\n <section class=\"fp-ae-panel\">\n <div class=\"fp-ae-section-title\">Custom data handoff</div>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <div>\n <div class=\"fp-ae-label mb-2\">Inputs sent to this step</div>\n @for (row of inputMappingRows(); track row.key) {\n <label class=\"mb-2 block space-y-1\">\n <span class=\"text-[11px] text-(--p-text-muted-color)\">{{\n row.key\n }}</span>\n <mt-text-field\n [ngModel]=\"valueText(row.value)\"\n (focusin)=\"\n setExpressionTarget('config:inputMapping:' + row.key)\n \"\n (ngModelChange)=\"\n updateJsonField('inputMappingJson', row.key, $event)\n \"\n hint=\"Value this step receives from the workflow context.\"\n />\n </label>\n }\n </div>\n <div>\n <div class=\"fp-ae-label mb-2\">Outputs saved for next steps</div>\n @for (row of outputMappingRows(); track row.key) {\n <label class=\"mb-2 block space-y-1\">\n <span class=\"text-[11px] text-(--p-text-muted-color)\">{{\n row.key\n }}</span>\n <mt-text-field\n [ngModel]=\"valueText(row.value)\"\n (focusin)=\"\n setExpressionTarget('config:outputMapping:' + row.key)\n \"\n (ngModelChange)=\"\n updateJsonField('outputMappingJson', row.key, $event)\n \"\n hint=\"Value this step exposes to the rest of the workflow.\"\n />\n </label>\n }\n </div>\n </div>\n @if (\n sectionHasAdvancedEmptyState(\"mapping\") &&\n inputMappingRows().length === 0 &&\n outputMappingRows().length === 0\n ) {\n <p class=\"fp-ae-copy mt-3\">\n Default handoff is active. This step receives the current workflow\n item and exposes its normal output to the next connected step. Add a\n custom handoff only when the business data must be reshaped.\n </p>\n }\n </section>\n }\n\n @if (sectionInAdvanced(\"triggerContext\") && trigger()) {\n <details class=\"fp-ae-panel\" open>\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\n Trigger payload and context\n </summary>\n <p class=\"fp-ae-copy mt-3\">\n Trigger payload/context is documentation for expressions only.\n Triggers do not edit node input or output mappings here.\n </p>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\n Config schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(configSchema()) }}</pre>\n </details>\n @if (hasTriggerPayloadSchema() && triggerPayloadSchema(); as schema) {\n <details\n class=\"rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Payload schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(schema) }}</pre>\n </details>\n }\n @if (authPolicySchema()) {\n <details\n class=\"rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Auth policy schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(authPolicySchema()) }}</pre>\n </details>\n }\n @if (triggerPayloadSample(); as sample) {\n <details\n class=\"rounded-lg border border-(--p-content-border-color)\"\n >\n <summary\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\n >\n Payload or request sample\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(sample) }}</pre>\n </details>\n }\n </div>\n </details>\n }\n\n @if (sectionInAdvanced(\"backendSchema\") && step()) {\n <details class=\"fp-ae-panel\" open>\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\n Backend schemas and outputs\n </summary>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\n Config schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(configSchema()) }}</pre>\n </details>\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\n Input schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(inputSchema()) }}</pre>\n </details>\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\n Output schema\n </summary>\n <pre class=\"fp-ae-code\">{{ schemaText(outputSchema()) }}</pre>\n </details>\n <div class=\"rounded-lg border border-(--p-content-border-color) p-3\">\n <div class=\"fp-ae-label mb-2\">Route output keys</div>\n <div class=\"flex flex-wrap gap-1.5\">\n @for (key of routeOutputKeys(); track key) {\n <span\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\n >{{ key }}</span\n >\n }\n @if (routeOutputKeys().length === 0) {\n <span class=\"text-[12px] text-(--p-text-muted-color)\"\n >No outgoing route keys</span\n >\n }\n </div>\n </div>\n </div>\n </details>\n }\n\n @if (sectionInAdvanced(\"schemaFields\") && configRows().length > 0) {\n <details class=\"fp-ae-panel\" open>\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\n Backend schema fields\n </summary>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n @for (field of configRows(); track field.key) {\n <div\n class=\"space-y-1.5\"\n [class.md:col-span-2]=\"\n field.type === 'object' || field.type === 'array'\n \"\n >\n @if (field.enumValues.length) {\n <mt-select-field\n [ngModel]=\"fieldValue(field.key)\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n [options]=\"enumOptions(field.enumValues)\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n } @else if (field.type === \"boolean\") {\n <mt-toggle-field\n size=\"small\"\n labelPosition=\"end\"\n [ngModel]=\"!!fieldValue(field.key)\"\n (ngModelChange)=\"\n onConfigFieldChange(field.key, $event === true)\n \"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n } @else if (field.type === \"number\") {\n <mt-number-field\n [ngModel]=\"numberValue(fieldValue(field.key))\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n } @else if (field.type === \"date\") {\n <mt-date-field\n [ngModel]=\"fieldValue(field.key)\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n [showTime]=\"field.format !== 'date'\"\n [showClear]=\"true\"\n [pInputs]=\"dateTimePickerInputs\"\n />\n } @else if (field.type === \"object\" || field.type === \"array\") {\n <mt-textarea-field\n class=\"w-full font-mono\"\n [ngModel]=\"valueText(fieldValue(field.key))\"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n rows=\"5\"\n />\n } @else {\n <mt-text-field\n [ngModel]=\"fieldText(field.key)\"\n (focusin)=\"\n field.expressionEnabled &&\n setExpressionTarget('config:' + field.key)\n \"\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\n [label]=\"field.label\"\n [hint]=\"field.description ?? ''\"\n [required]=\"isConfigFieldRequired(field.key)\"\n />\n }\n </div>\n }\n </div>\n </details>\n }\n </div>\n</div>\n\n<ng-template #credentialSelector>\n @if (activeCredentialProvider(); as provider) {\n <div\n class=\"mb-4 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-4 shadow-[0_1px_2px_rgba(15,23,42,0.05)]\"\n >\n <div class=\"mb-2 text-[12px] font-bold text-(--p-text-color)\">\n Credential\n </div>\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (provider.connectAvailable) {\n <button\n type=\"button\"\n class=\"inline-flex min-h-10 max-w-full items-center gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2 text-[13px] font-bold text-(--p-text-color) shadow-sm transition-[border-color,box-shadow,background] hover:border-(--p-primary-color) hover:shadow-[0_2px_7px_rgba(15,23,42,0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n [disabled]=\"credentialOauthState() === 'loading'\"\n [attr.aria-busy]=\"\n credentialOauthState() === 'loading' ? 'true' : null\n \"\n (click)=\"connectCredentialProvider()\"\n >\n <fp-automation-node-icon\n class=\"text-[22px]\"\n [itemKey]=\"provider.providerKey\"\n [displayName]=\"provider.displayName\"\n [metadata]=\"credentialProviderIconMetadata(provider)\"\n />\n <span class=\"truncate\">{{ credentialConnectLabel() }}</span>\n </button>\n @if (provider.manualSetupAvailable) {\n <span class=\"text-[12px] text-(--p-text-muted-color)\">or</span>\n <button\n type=\"button\"\n class=\"rounded-md px-1 py-1 text-[12px] font-semibold text-(--p-text-color) underline decoration-(--p-text-muted-color) underline-offset-4 transition-colors hover:text-(--p-primary-color) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n (click)=\"showManualCredentialSetup()\"\n >\n setup manually\n </button>\n }\n } @else if (provider.manualSetupAvailable) {\n <button\n type=\"button\"\n class=\"inline-flex min-h-10 max-w-full items-center gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2 text-[13px] font-bold text-(--p-text-color) shadow-sm transition-[border-color,box-shadow,background] hover:border-(--p-primary-color) hover:shadow-[0_2px_7px_rgba(15,23,42,0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n (click)=\"showManualCredentialSetup()\"\n >\n <fp-automation-node-icon\n class=\"text-[22px]\"\n [itemKey]=\"provider.providerKey\"\n [displayName]=\"provider.displayName\"\n [metadata]=\"credentialProviderIconMetadata(provider)\"\n />\n <span class=\"truncate\"\n >Set up\n {{ provider.displayName || humanReviewProviderLabel() }}\n credential</span\n >\n </button>\n }\n </div>\n <div\n class=\"mt-2 flex flex-wrap items-center gap-1.5 text-[11px] text-(--p-text-muted-color)\"\n >\n @if (provider.authModes?.length) {\n @for (mode of provider.authModes; track mode) {\n <span\n class=\"rounded-full bg-(--p-surface-100) px-2 py-0.5 font-semibold\"\n >\n {{ mode }}\n </span>\n }\n }\n @if (provider.requiredScopes?.length) {\n <span class=\"truncate\">\n Scopes: {{ provider.requiredScopes.join(\", \") }}\n </span>\n }\n </div>\n @if (provider.setupInstructions) {\n <p class=\"m-0 mt-2 text-[11.5px] leading-5 text-(--p-text-muted-color)\">\n {{ provider.setupInstructions }}\n </p>\n }\n </div>\n }\n\n @if (manualCredentialOpen()) {\n <div\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\n >\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-text-field\n [ngModel]=\"manualCredentialDisplayName()\"\n (ngModelChange)=\"onManualCredentialDisplayNameChange($event)\"\n label=\"Credential name\"\n />\n <mt-select-field\n [ngModel]=\"manualCredentialType()\"\n (ngModelChange)=\"onManualCredentialTypeChange($event)\"\n label=\"Credential type\"\n [options]=\"credentialTypeOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n />\n </div>\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\n @for (field of manualCredentialFields(); track field.key) {\n @if (manualCredentialFieldIsToggle(field)) {\n <mt-toggle-field\n [ngModel]=\"manualCredentialFieldValue(field.key) === 'true'\"\n (ngModelChange)=\"onManualCredentialFieldChange(field.key, $event)\"\n [label]=\"\n manualCredentialFieldLabel(field) + (field.required ? ' *' : '')\n \"\n size=\"small\"\n />\n } @else {\n <mt-text-field\n [ngModel]=\"manualCredentialFieldValue(field.key)\"\n (ngModelChange)=\"onManualCredentialFieldChange(field.key, $event)\"\n [label]=\"\n manualCredentialFieldLabel(field) + (field.required ? ' *' : '')\n \"\n [placeholder]=\"field.placeholder ?? ''\"\n [type]=\"manualCredentialFieldInputType(field)\"\n />\n }\n }\n </div>\n @if (credentialManualError(); as error) {\n <div\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ error }}\n </div>\n }\n <div class=\"mt-3 flex justify-end gap-2\">\n <mt-button\n size=\"small\"\n variant=\"text\"\n label=\"Cancel\"\n (onClick)=\"cancelManualCredentialSetup()\"\n />\n <mt-button\n size=\"small\"\n severity=\"primary\"\n label=\"Save credential\"\n [loading]=\"credentialManualState() === 'loading'\"\n [disabled]=\"credentialManualState() === 'loading'\"\n (onClick)=\"createManualCredential()\"\n />\n </div>\n </div>\n }\n\n @if (credentialOptions().length) {\n <div class=\"space-y-2\">\n <mt-select-field\n [ngModel]=\"selectedCredentialRef()\"\n (ngModelChange)=\"selectCredential($event)\"\n label=\"Saved credential\"\n [options]=\"credentialOptions()\"\n optionValue=\"value\"\n optionLabel=\"label\"\n [showClear]=\"true\"\n />\n @for (credential of credentials(); track credential.credentialRef) {\n @if (credential.credentialRef === selectedCredentialRef()) {\n <div\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2\"\n >\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"truncate text-[12px] font-semibold\">\n {{ credential.displayName ?? credential.credentialRef }}\n </div>\n <div\n class=\"truncate font-mono text-[11px] text-(--p-text-muted-color)\"\n >\n {{ credential.credentialRef }} /\n {{\n credential.status ??\n (credential.resolved ? \"Resolved\" : \"Unresolved\")\n }}\n </div>\n </div>\n <div class=\"flex flex-none items-center gap-2\">\n <mt-button\n size=\"small\"\n variant=\"text\"\n label=\"Test\"\n [loading]=\"\n credentialTestingRef() === credential.credentialRef\n \"\n [disabled]=\"\n credentialTestingRef() === credential.credentialRef\n \"\n (onClick)=\"testCredential(credential.credentialRef)\"\n />\n @if (activeCredentialProvider()?.connectAvailable) {\n <mt-button\n size=\"small\"\n variant=\"text\"\n label=\"Reconnect\"\n [disabled]=\"credentialOauthState() === 'loading'\"\n (onClick)=\"connectCredentialProvider()\"\n />\n }\n <mt-button\n size=\"small\"\n variant=\"text\"\n severity=\"danger\"\n label=\"Revoke\"\n [loading]=\"credentialRevokeRef() === credential.credentialRef\"\n [disabled]=\"\n credentialRevokeRef() === credential.credentialRef\n \"\n (onClick)=\"revokeCredential(credential.credentialRef)\"\n />\n </div>\n </div>\n @if (credential.maskedFields; as maskedFields) {\n <div\n class=\"mt-2 flex flex-wrap gap-1.5 text-[11px] text-(--p-text-muted-color)\"\n >\n @for (field of maskedFields | keyvalue; track field.key) {\n <span\n class=\"rounded-md bg-(--p-content-background) px-2 py-1 font-mono\"\n >\n {{ field.key }}: {{ field.value }}\n </span>\n }\n </div>\n }\n </div>\n }\n }\n </div>\n } @else if (sectionHasAdvancedEmptyState(\"credentials\")) {\n <p class=\"fp-ae-copy\">\n No saved credential exists for this provider. Connect the provider or set\n it up manually before publishing.\n </p>\n }\n @if (credentialTest(); as test) {\n <div class=\"mt-3 rounded-md bg-(--p-surface-100) px-3 py-2 text-[12px]\">\n {{ test.status ?? (test.succeeded ? \"Succeeded\" : \"Failed\") }}\n @if (test.message) {\n <span>- {{ test.message }}</span>\n }\n </div>\n }\n</ng-template>\n\n<ng-template\n #mapEditor\n let-objectKey=\"objectKey\"\n let-rows=\"rows\"\n let-keyLabel=\"keyLabel\"\n let-valueLabel=\"valueLabel\"\n let-addLabel=\"addLabel\"\n let-emptyText=\"emptyText\"\n let-keyHint=\"keyHint\"\n let-valueHint=\"valueHint\"\n let-required=\"required\"\n>\n <div class=\"space-y-2\">\n @for (row of rows; track row.key) {\n <div class=\"grid gap-2 md:grid-cols-[180px_1fr_auto]\">\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"row.key\"\n (ngModelChange)=\"\n updateObjectRow(objectKey, row.key, $event, row.value)\n \"\n [label]=\"keyLabel\"\n [hint]=\"\n keyHint || 'Configuration key saved to the backend JSON object.'\n \"\n [required]=\"required === true\"\n />\n <mt-text-field\n class=\"font-mono\"\n [ngModel]=\"valueText(row.value)\"\n (focusin)=\"setExpressionTarget('config:' + objectKey + ':' + row.key)\"\n (ngModelChange)=\"updateObjectRow(objectKey, row.key, row.key, $event)\"\n [label]=\"valueLabel\"\n [hint]=\"\n valueHint ||\n 'Configuration value. Use expressions when this field should resolve at runtime.'\n \"\n [required]=\"required === true\"\n />\n <mt-button\n class=\"self-end\"\n size=\"small\"\n severity=\"secondary\"\n variant=\"outlined\"\n label=\"Remove\"\n (onClick)=\"removeObjectRow(objectKey, row.key)\"\n />\n </div>\n } @empty {\n @if (emptyText) {\n <div\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\n >\n {{ emptyText }}\n </div>\n }\n }\n <mt-button\n size=\"small\"\n variant=\"outlined\"\n [label]=\"addLabel || 'Add ' + keyLabel\"\n (onClick)=\"addObjectRow(objectKey)\"\n />\n </div>\n</ng-template>\n" }]
|
|
20307
|
+
], host: { class: 'block h-full min-h-0' }, template: "<div\r\n class=\"fp-scroll flex h-full min-h-0 flex-col overflow-y-auto\"\r\n fpDropData\r\n [fpDropAutoInsert]=\"true\"\r\n (dataDropEvent)=\"insertExpressionDrop($event)\"\r\n>\r\n <div class=\"space-y-4 px-5 py-5\">\r\n @if (helperError()) {\r\n <div\r\n class=\"rounded-lg border border-[rgb(var(--fp-warning))]/30 bg-[rgb(var(--fp-warning))]/10 px-3 py-2 text-[12px] leading-5 text-(--p-text-color)\"\r\n >\r\n {{ helperError() }}\r\n </div>\r\n }\r\n\r\n @if (sectionInMain(\"startConnection\") && trigger()) {\r\n <section\r\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Start connection\r\n </h3>\r\n <div class=\"space-y-3 p-4\">\r\n @if (startConnection().key) {\r\n @if (startConnection().step) {\r\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\r\n <div class=\"min-w-0 space-y-1\">\r\n <div\r\n class=\"text-[12px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n First node connected\r\n </div>\r\n <div\r\n class=\"truncate text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ startConnection().label }}\r\n </div>\r\n <div\r\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n <span>Managed on canvas</span>\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ startConnection().key }}\r\n </span>\r\n </div>\r\n </div>\r\n <div class=\"flex shrink-0 flex-wrap gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n label=\"Focus connected node\"\r\n (onClick)=\"focusStartConnection()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"space-y-2\">\r\n <div class=\"text-[14px] font-semibold text-(--p-text-color)\">\r\n No first node connected\r\n </div>\r\n <p\r\n class=\"m-0 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n The saved start connection points to a node key that is not on\r\n the canvas. Connect this trigger to the first node on the\r\n canvas.\r\n </p>\r\n <div\r\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n <span>Technical key</span>\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ startConnection().key }}\r\n </span>\r\n <span>Managed on canvas</span>\r\n </div>\r\n </div>\r\n }\r\n } @else {\r\n <div\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"text-[14px] font-semibold text-(--p-text-color)\">\r\n No first node connected\r\n </div>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Connect this trigger to the first node on the canvas.\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n\r\n @switch (editorType()) {\r\n @case (\"ManualTrigger\") {\r\n @if (sectionInMain(\"manualTrigger\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Manual run input</div>\r\n @if (\r\n hasTriggerPayloadSchema() && triggerPayloadSchema();\r\n as schema\r\n ) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Payload schema</div>\r\n <pre class=\"fp-ae-code\">{{ schemaText(schema) }}</pre>\r\n </div>\r\n }\r\n @if (triggerPayloadSample(); as sample) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Sample payload</div>\r\n <pre class=\"fp-ae-code\">{{ schemaText(sample) }}</pre>\r\n </div>\r\n }\r\n @if (!hasTriggerPayloadSchema() && !triggerPayloadSample()) {\r\n <p class=\"fp-ae-copy\">\r\n This manual trigger has no backend-provided input schema. It can\r\n still be connected to the first step on the canvas.\r\n </p>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"WebhookTrigger\") {\r\n @if (sectionInMain(\"webhookSetup\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Webhook setup</div>\r\n @if (webhookSetup(); as setup) {\r\n <div class=\"flex gap-2\">\r\n <mt-text-field\r\n class=\"flex-1 font-mono\"\r\n [ngModel]=\"setup.webhookUrl ?? ''\"\r\n [readonly]=\"true\"\r\n label=\"Webhook URL\"\r\n hint=\"Backend-generated endpoint clients should call to start this trigger.\"\r\n />\r\n <mt-button\r\n class=\"self-end\"\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Copy\"\r\n (onClick)=\"copyWebhookUrl()\"\r\n />\r\n </div>\r\n <div class=\"grid gap-2 md:grid-cols-2\">\r\n <div class=\"fp-ae-kv\">\r\n <span>Auth mode</span>\r\n <strong>{{ setup.authMode ?? \"Backend default\" }}</strong>\r\n </div>\r\n <div class=\"fp-ae-kv\">\r\n <span>Required headers</span>\r\n <strong>{{\r\n (setup.requiredHeaders ?? []).join(\", \") || \"-\"\r\n }}</strong>\r\n </div>\r\n </div>\r\n @if (setup.hmacSigning) {\r\n <p class=\"fp-ae-copy\">{{ setup.hmacSigning }}</p>\r\n }\r\n @if (setup.sampleRequest) {\r\n <details\r\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Sample request\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{\r\n schemaText(setup.sampleRequest)\r\n }}</pre>\r\n </details>\r\n }\r\n } @else {\r\n <p class=\"fp-ae-copy\">\r\n Webhook setup is not available for this draft yet.\r\n </p>\r\n }\r\n </section>\r\n }\r\n @if (sectionInMain(\"authPolicy\") && hasAuthPolicy()) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Authentication policy</div>\r\n <div class=\"grid gap-3 md:grid-cols-3\">\r\n <mt-select-field\r\n [ngModel]=\"authPolicy()['mode'] ?? ''\"\r\n (ngModelChange)=\"onAuthFieldChange('mode', $event)\"\r\n label=\"Mode\"\r\n hint=\"Authentication mode required by the backend webhook policy.\"\r\n [required]=\"isAuthFieldRequired('mode')\"\r\n [options]=\"authModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"authPolicy()['signatureHeaderName'] ?? ''\"\r\n (ngModelChange)=\"\r\n onAuthFieldChange('signatureHeaderName', $event)\r\n \"\r\n label=\"Signature header\"\r\n hint=\"Header name that carries the webhook signature when the policy requires signed requests.\"\r\n [required]=\"isAuthFieldRequired('signatureHeaderName')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"authPolicy()['timestampHeaderName'] ?? ''\"\r\n (ngModelChange)=\"\r\n onAuthFieldChange('timestampHeaderName', $event)\r\n \"\r\n label=\"Timestamp header\"\r\n hint=\"Header name that carries the request timestamp for replay protection.\"\r\n [required]=\"isAuthFieldRequired('timestampHeaderName')\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"FormSubmitTrigger\") {\r\n @if (sectionInMain(\"formBinding\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Form binding</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"selectedFormId() || formBinding()?.formId || ''\"\r\n (ngModelChange)=\"onFormChange($event)\"\r\n label=\"Form\"\r\n hint=\"Choose a backend form that will submit data into this trigger.\"\r\n [required]=\"true\"\r\n [options]=\"formOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"\r\n selectedFormVersionId() || formBinding()?.formVersionId || ''\r\n \"\r\n (ngModelChange)=\"onFormVersionChange($event)\"\r\n label=\"Form version\"\r\n hint=\"Persist the exact backend formVersionId. Do not type or generate IDs manually.\"\r\n [required]=\"true\"\r\n [options]=\"formVersionOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Save binding\"\r\n (onClick)=\"saveFormBinding()\"\r\n />\r\n @if (formBinding()) {\r\n <span\r\n class=\"rounded-lg bg-(--p-surface-100) px-2 py-1 font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ formBinding()!.formVersionId }}\r\n </span>\r\n }\r\n </div>\r\n @if (formSchema(); as schema) {\r\n <details\r\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Form schema preview\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(schema.schema) }}</pre>\r\n </details>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"ScheduleTrigger\") {\r\n @if (sectionInMain(\"schedule\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Schedule</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"scheduleMode()\"\r\n (ngModelChange)=\"onScheduleModeChange($event)\"\r\n label=\"Mode\"\r\n hint=\"Pick one schedule mode: cron, interval, or once. Backend validation requires exactly one mode.\"\r\n [required]=\"true\"\r\n [options]=\"scheduleModeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['timezone'] ?? 'UTC'\"\r\n (ngModelChange)=\"onConfigFieldChange('timezone', $event)\"\r\n label=\"Timezone\"\r\n hint=\"Timezone used to calculate the next fire time.\"\r\n [required]=\"isConfigFieldRequired('timezone')\"\r\n [options]=\"timeZoneOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (showScheduleCron()) {\r\n <mt-text-field\r\n class=\"font-mono md:col-span-2\"\r\n [ngModel]=\"config()['cron'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('cron', $event)\"\r\n label=\"Cron\"\r\n hint=\"Cron expression, for example 0 9 * * *.\"\r\n [required]=\"showScheduleCron()\"\r\n />\r\n }\r\n @if (showScheduleInterval()) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['intervalSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('intervalSeconds', $event)\r\n \"\r\n label=\"Interval seconds\"\r\n hint=\"Repeat interval in seconds.\"\r\n [required]=\"showScheduleInterval()\"\r\n [min]=\"0\"\r\n />\r\n }\r\n @if (showScheduleOnce()) {\r\n <mt-date-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"\r\n config()['runAt'] ??\r\n config()['runAtUtc'] ??\r\n config()['oneTimeAt'] ??\r\n config()['at'] ??\r\n ''\r\n \"\r\n (ngModelChange)=\"onConfigFieldChange('runAt', $event)\"\r\n label=\"Run at UTC\"\r\n hint=\"UTC date/time for the one-time schedule.\"\r\n [required]=\"showScheduleOnce()\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n @if (showScheduleStartDate()) {\r\n <mt-date-field\r\n [ngModel]=\"config()['startDate'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('startDate', $event)\"\r\n label=\"Start date UTC\"\r\n hint=\"Optional first allowed run time for this schedule.\"\r\n [required]=\"isConfigFieldRequired('startDate')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n <mt-select-field\r\n [ngModel]=\"config()['misfirePolicy'] ?? 'SkipMissed'\"\r\n (ngModelChange)=\"onConfigFieldChange('misfirePolicy', $event)\"\r\n label=\"Misfire policy\"\r\n hint=\"Backend behavior when a scheduled run is missed while the automation is unavailable.\"\r\n [required]=\"isConfigFieldRequired('misfirePolicy')\"\r\n [options]=\"misfirePolicyOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Validate\"\r\n (onClick)=\"validateSchedule()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Preview next fire\"\r\n (onClick)=\"previewSchedule()\"\r\n />\r\n </div>\r\n @if (schedulePreview(); as preview) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n <strong class=\"text-emerald-600\">Valid</strong>\r\n <span class=\"ms-2\"\r\n >Next fire: {{ preview.nextFireAtUtc ?? \"-\" }}</span\r\n >\r\n </div>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"SetFields\") {\r\n @if (sectionInMain(\"setFields\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Set fields</div>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'fields',\r\n rows: fieldsRows(),\r\n keyLabel: 'Field',\r\n valueLabel: 'Value',\r\n addLabel: 'Add field',\r\n required: isConfigFieldRequired('fields'),\r\n }\r\n \"\r\n />\r\n </section>\r\n }\r\n }\r\n @case (\"If\") {\r\n @if (sectionInMain(\"condition\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Condition</div>\r\n <div class=\"grid gap-3 md:grid-cols-[1fr_180px_1fr]\">\r\n <mt-text-field\r\n [ngModel]=\"ifConditionFieldText('left')\"\r\n (focusin)=\"setExpressionTarget('config:left')\"\r\n (ngModelChange)=\"onIfConditionFieldChange('left', $event)\"\r\n label=\"Left\"\r\n hint=\"Left-side value or expression to compare.\"\r\n [required]=\"isConfigFieldRequired('left')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"ifConditionFieldValue('operator') || 'equals'\"\r\n (ngModelChange)=\"onIfConditionFieldChange('operator', $event)\"\r\n label=\"Operator\"\r\n hint=\"Comparison operator used by the backend condition evaluator.\"\r\n [required]=\"isConfigFieldRequired('operator')\"\r\n [options]=\"ifOperatorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"ifConditionFieldText('right')\"\r\n (focusin)=\"setExpressionTarget('config:right')\"\r\n (ngModelChange)=\"onIfConditionFieldChange('right', $event)\"\r\n label=\"Right\"\r\n hint=\"Right-side value or expression to compare against.\"\r\n [required]=\"isConfigFieldRequired('right')\"\r\n />\r\n </div>\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Route outputs</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (key of routeOutputKeys(); track key) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ key }}</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"HTTP\") {\r\n @if (sectionInMain(\"httpRequest\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">HTTP request</div>\r\n <div class=\"grid gap-3 md:grid-cols-[150px_1fr]\">\r\n <mt-select-field\r\n [ngModel]=\"httpMethod()\"\r\n (ngModelChange)=\"onHttpMethodChange($event)\"\r\n label=\"Method\"\r\n hint=\"HTTP method used for the outbound request.\"\r\n [required]=\"isConfigFieldRequired('method')\"\r\n [options]=\"httpMethodOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['url'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:url')\"\r\n (ngModelChange)=\"onConfigFieldChange('url', $event)\"\r\n label=\"URL\"\r\n hint=\"Target URL. Expressions are supported for dynamic hosts, paths, and query values.\"\r\n [required]=\"isConfigFieldRequired('url')\"\r\n />\r\n </div>\r\n <div class=\"mt-3\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'headers',\r\n rows: headerRows(),\r\n keyLabel: 'Header',\r\n valueLabel: 'Value',\r\n addLabel: 'Add header',\r\n }\r\n \"\r\n />\r\n </div>\r\n <div class=\"mt-3\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'query',\r\n rows: queryRows(),\r\n keyLabel: 'Query param',\r\n valueLabel: 'Value',\r\n addLabel: 'Add query param',\r\n }\r\n \"\r\n />\r\n </div>\r\n @if (httpMethodAllowsPayload()) {\r\n @if (httpSupportsBodyMode()) {\r\n <mt-select-field\r\n class=\"mt-3\"\r\n [ngModel]=\"httpBodyMode()\"\r\n (ngModelChange)=\"onHttpBodyModeChange($event)\"\r\n label=\"Payload mode\"\r\n hint=\"How the request payload should be sent by the backend HTTP runtime.\"\r\n [required]=\"isConfigFieldRequired(httpBodyModeConfigKey())\"\r\n [options]=\"httpBodyModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n }\r\n @if (httpBodyEnabled()) {\r\n <mt-textarea-field\r\n class=\"mt-3 w-full font-mono\"\r\n [ngModel]=\"valueText(config()['body'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:body')\"\r\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\r\n label=\"Request payload\"\r\n hint=\"Payload sent by this HTTP method. Use JSON or expressions when the backend schema allows it.\"\r\n [required]=\"isConfigFieldRequired('body')\"\r\n rows=\"6\"\r\n />\r\n }\r\n } @else {\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ httpMethod() }} requests do not send a payload. Put request\r\n data in query parameters or headers.\r\n </div>\r\n }\r\n @if (\r\n supportsConfigKey(\"timeoutSeconds\") ||\r\n supportsConfigKey(\"responseHandling\")\r\n ) {\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n @if (supportsConfigKey(\"timeoutSeconds\")) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('timeoutSeconds', $event)\r\n \"\r\n label=\"Timeout seconds\"\r\n hint=\"Request timeout when exposed by the backend schema.\"\r\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\r\n [min]=\"0\"\r\n />\r\n }\r\n @if (supportsConfigKey(\"responseHandling\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['responseHandling'] ?? ''\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('responseHandling', $event)\r\n \"\r\n label=\"Response handling\"\r\n hint=\"Backend-supported response handling mode or expression.\"\r\n [required]=\"isConfigFieldRequired('responseHandling')\"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (sectionInMain(\"credentials\")) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Credential</div>\r\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\r\n </div>\r\n }\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"min-w-0\">\r\n <div class=\"fp-ae-label\">Configuration check</div>\r\n <p class=\"fp-ae-copy m-0 mt-1\">\r\n Runs backend dry-run validation for method, URL,\r\n expressions, and request policy before execution.\r\n </p>\r\n </div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n label=\"Check request\"\r\n [disabled]=\"httpCheckState() === 'loading'\"\r\n (onClick)=\"checkHttpRequest()\"\r\n />\r\n </div>\r\n @if (httpLocalIssues().length > 0) {\r\n <div\r\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-warning))]/25 bg-[rgb(var(--fp-warning))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\r\n >\r\n {{ httpLocalIssues()[0] }}\r\n </div>\r\n } @else if (httpCheckError(); as error) {\r\n <div\r\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-error))]/25 bg-[rgb(var(--fp-error))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\r\n >\r\n {{ error }}\r\n </div>\r\n } @else if (httpCheckResult(); as result) {\r\n <div\r\n class=\"mt-2 rounded-md border border-[rgb(var(--fp-commit))]/25 bg-[rgb(var(--fp-commit))]/10 px-2.5 py-2 text-[12px] text-(--p-text-color)\"\r\n >\r\n Backend check\r\n {{\r\n result.success || result.canStart\r\n ? \"passed\"\r\n : \"completed with issues\"\r\n }}.\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"Wait\") {\r\n @if (sectionInMain(\"wait\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Wait</div>\r\n <div class=\"grid gap-3 md:grid-cols-3\">\r\n <mt-select-field\r\n [ngModel]=\"waitMode()\"\r\n (ngModelChange)=\"onWaitModeChange($event)\"\r\n label=\"Mode\"\r\n hint=\"Choose how execution should pause.\"\r\n [required]=\"isConfigFieldRequired('mode')\"\r\n [options]=\"waitModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (showWaitDuration()) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['durationSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('durationSeconds', $event)\r\n \"\r\n label=\"Wait duration\"\r\n hint=\"Seconds to pause before continuing.\"\r\n [required]=\"isConfigFieldRequired('durationSeconds')\"\r\n [min]=\"1\"\r\n />\r\n }\r\n @if (showWaitUntilDate()) {\r\n <mt-date-field\r\n [ngModel]=\"waitUntilDateValue()\"\r\n (ngModelChange)=\"onWaitUntilDateChange($event)\"\r\n label=\"Resume at\"\r\n hint=\"Date and time when execution should continue.\"\r\n [required]=\"isConfigFieldRequired('untilDate')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n </div>\r\n @if (showWaitResumePayloadSchema()) {\r\n <mt-textarea-field\r\n class=\"mt-3 w-full font-mono\"\r\n [ngModel]=\"waitResumePayloadSchemaText()\"\r\n (focusin)=\"setExpressionTarget('config:resumePayloadSchema')\"\r\n (ngModelChange)=\"onWaitResumePayloadSchemaChange($event)\"\r\n label=\"Resume payload schema\"\r\n hint=\"Optional JSON schema for data sent back when this wait is resumed.\"\r\n [required]=\"isConfigFieldRequired('resumePayloadSchema')\"\r\n rows=\"5\"\r\n />\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"HumanTask\") {\r\n @if (sectionInMain(\"humanTask\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Human task</div>\r\n @if (\r\n assignmentOptions()?.providerStatus &&\r\n assignmentOptions()?.providerStatus !== \"Available\"\r\n ) {\r\n <p class=\"fp-ae-copy\">\r\n Assignment provider status:\r\n {{ assignmentOptions()?.providerStatus }}. The backend provider\r\n is the source of truth for available assignees.\r\n </p>\r\n }\r\n <div\r\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Task details\r\n </h3>\r\n <div class=\"grid gap-3 p-4 xl:grid-cols-3\">\r\n <mt-text-field\r\n [ngModel]=\"config()['title'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:title')\"\r\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\r\n label=\"Task title\"\r\n hint=\"Title shown to the assigned user while the execution waits.\"\r\n [required]=\"isConfigFieldRequired('title')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"selectedAssignmentKey()\"\r\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\r\n label=\"Assignment\"\r\n hint=\"Backend-provided assignee, role, or group that can submit this task.\"\r\n [required]=\"isConfigFieldRequired('assignment')\"\r\n [options]=\"assignmentSelectOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['priority'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('priority', $event)\"\r\n label=\"Priority\"\r\n hint=\"Optional task priority passed to backend task orchestration.\"\r\n [required]=\"isConfigFieldRequired('priority')\"\r\n />\r\n <mt-textarea-field\r\n class=\"xl:col-span-3\"\r\n [ngModel]=\"config()['description'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:description')\"\r\n (ngModelChange)=\"onConfigFieldChange('description', $event)\"\r\n label=\"Task description\"\r\n hint=\"Instructions shown with the HumanTask form.\"\r\n [required]=\"isConfigFieldRequired('description')\"\r\n rows=\"4\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div\r\n class=\"mt-3 flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Task behavior\r\n </h3>\r\n <div class=\"grid gap-3 p-4 xl:grid-cols-4\">\r\n <mt-date-field\r\n [ngModel]=\"\r\n config()['dueDateUtc'] ?? config()['dueDate'] ?? ''\r\n \"\r\n (ngModelChange)=\"onConfigFieldChange('dueDateUtc', $event)\"\r\n label=\"Due date\"\r\n hint=\"Optional backend due timestamp for the waiting task.\"\r\n [required]=\"isConfigFieldRequired('dueDateUtc')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['dueInSeconds'])\"\r\n (ngModelChange)=\"onConfigFieldChange('dueInSeconds', $event)\"\r\n label=\"Due in seconds\"\r\n hint=\"Relative due duration when the backend should calculate the timestamp.\"\r\n [required]=\"isConfigFieldRequired('dueInSeconds')\"\r\n [min]=\"0\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['contextOutputPath'] ?? 'task'\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('contextOutputPath', $event)\r\n \"\r\n label=\"Context output path\"\r\n hint=\"Where submitted task values are written in execution context.\"\r\n [required]=\"isConfigFieldRequired('contextOutputPath')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['cancelBehavior'] ?? 'RouteCancelled'\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('cancelBehavior', $event)\r\n \"\r\n label=\"Cancel behavior\"\r\n hint=\"Route cancelled when users cancel, or fail this node.\"\r\n [required]=\"isConfigFieldRequired('cancelBehavior')\"\r\n [options]=\"humanTaskCancelBehaviorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <div\r\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <mt-toggle-field\r\n size=\"small\"\r\n label=\"Allow draft save\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['saveDraft'] !== false\"\r\n [required]=\"isConfigFieldRequired('saveDraft')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('saveDraft', $event === true)\r\n \"\r\n hint=\"Expose backend draft save for this task when supported.\"\r\n />\r\n </div>\r\n @if (supportsConfigKey(\"submitBehavior\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['submitBehavior'] ?? ''\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('submitBehavior', $event)\r\n \"\r\n label=\"Submit behavior\"\r\n hint=\"Backend submit behavior when exposed by the node schema.\"\r\n [required]=\"isConfigFieldRequired('submitBehavior')\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Validate assignment\"\r\n (onClick)=\"validateAssignment()\"\r\n />\r\n </div>\r\n @if (assignmentValidation(); as result) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n {{\r\n result.isValid === false\r\n ? \"Assignment invalid\"\r\n : \"Assignment accepted by helper\"\r\n }}\r\n @for (issue of resultIssues(result); track $index) {\r\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\r\n {{ issue }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n\r\n @if (sectionInMain(\"formBinding\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Task input form</div>\r\n <mt-radio-cards-field\r\n [ngModel]=\"humanTaskFormSource()\"\r\n (ngModelChange)=\"onHumanTaskFormSourceChange($event)\"\r\n label=\"Form source\"\r\n hint=\"HumanTask requires either its own node form binding or an explicit reused FormSubmitTrigger payload.\"\r\n [required]=\"isConfigFieldRequired('formSource')\"\r\n [options]=\"humanTaskFormSourceOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n size=\"small\"\r\n >\r\n <ng-template #option let-item>\r\n <div class=\"w-full min-w-0\">\r\n <div class=\"text-[13px] font-semibold leading-5\">\r\n {{ item.label }}\r\n </div>\r\n <p\r\n class=\"mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ item.description }}\r\n </p>\r\n </div>\r\n </ng-template>\r\n </mt-radio-cards-field>\r\n\r\n @if (humanTaskUsesExplicitFormBinding()) {\r\n <div\r\n class=\"mt-3 flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n HumanTask input binding\r\n </h3>\r\n <div class=\"grid gap-3 p-4 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"selectedFormId() || formBinding()?.formId || ''\"\r\n (ngModelChange)=\"onFormChange($event)\"\r\n label=\"Form\"\r\n hint=\"Choose a backend form for this HumanTask node.\"\r\n [required]=\"humanTaskUsesExplicitFormBinding()\"\r\n [options]=\"formOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"\r\n selectedFormVersionId() ||\r\n formBinding()?.formVersionId ||\r\n ''\r\n \"\r\n (ngModelChange)=\"onFormVersionChange($event)\"\r\n label=\"Form version\"\r\n hint=\"Persist the exact backend formVersionId for the node binding.\"\r\n [required]=\"humanTaskUsesExplicitFormBinding()\"\r\n [options]=\"formVersionOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"border-t border-surface-200 px-4 py-3\">\r\n @if (humanTaskFormSummary(); as summary) {\r\n <div\r\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n @if (summary.formLabel) {\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.formLabel }}\r\n </span>\r\n }\r\n @if (summary.formVersionId) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ summary.formVersionId }}\r\n </span>\r\n }\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.fieldCount }} fields\r\n </span>\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.requiredCount }} required\r\n </span>\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.writableCount }} writable\r\n </span>\r\n </div>\r\n }\r\n\r\n @if (humanTaskHasCustomInputMapping()) {\r\n <div\r\n class=\"mt-3 rounded-md border border-[rgb(var(--fp-warning))]/30 bg-[rgb(var(--fp-warning))]/10 px-3 py-2 text-[12px] leading-5 text-(--p-text-color)\"\r\n >\r\n Existing custom input mapping will be preserved unless\r\n field prefill options are changed.\r\n </div>\r\n }\r\n\r\n @if (humanTaskFormFields().length > 0) {\r\n <div\r\n class=\"mt-3 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"text-[13px] font-semibold text-color\">\r\n FormBuilder fields\r\n </div>\r\n <div class=\"flex flex-wrap gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n label=\"Select all\"\r\n (onClick)=\"selectAllHumanTaskPrefillFields()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n label=\"Clear\"\r\n (onClick)=\"clearHumanTaskPrefillFields()\"\r\n />\r\n </div>\r\n </div>\r\n <div class=\"divide-y divide-surface-200\">\r\n @for (field of humanTaskFormFields(); track field.key) {\r\n <div\r\n class=\"grid gap-3 px-3 py-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-center\"\r\n >\r\n <div class=\"min-w-0\">\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n <span\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ field.label }}\r\n </span>\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ field.key }}\r\n </span>\r\n @if (field.required) {\r\n <span\r\n class=\"rounded-md bg-[rgb(var(--fp-danger))]/10 px-2 py-0.5 text-[11px] font-semibold text-[rgb(var(--fp-danger))]\"\r\n >\r\n Required\r\n </span>\r\n }\r\n @if (field.read === true) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n Read\r\n </span>\r\n }\r\n @if (field.write === true) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n Write\r\n </span>\r\n }\r\n </div>\r\n @if (field.description) {\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ field.description }}\r\n </p>\r\n }\r\n </div>\r\n <mt-toggle-field\r\n size=\"small\"\r\n label=\"Prefill\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"\r\n humanTaskPrefillFieldKeySet().has(field.key)\r\n \"\r\n (ngModelChange)=\"\r\n onHumanTaskPrefillFieldToggle(\r\n field.key,\r\n $event === true\r\n )\r\n \"\r\n />\r\n </div>\r\n }\r\n </div>\r\n <div\r\n class=\"border-t border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Selected fields are saved as\r\n <span class=\"font-mono\"\r\n >inputMappingJson.fieldKeys</span\r\n >\r\n so backend can prefill matching upstream values.\r\n </div>\r\n </div>\r\n } @else if (formSchema()) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No FormBuilder fields were returned for this form schema.\r\n </div>\r\n }\r\n\r\n @if (formSchema()?.requiresSnapshotOnPublish) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Backend requires this form version snapshot during\r\n publish.\r\n </div>\r\n }\r\n </div>\r\n <div class=\"border-t border-surface-200 px-4 py-3\">\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Save task binding\"\r\n (onClick)=\"saveFormBinding()\"\r\n />\r\n @if (formBinding()) {\r\n <span\r\n class=\"rounded-lg bg-(--p-surface-100) px-2 py-1 font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ formBinding()!.formVersionId }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['triggerKey'] ?? ''\"\r\n (ngModelChange)=\"onHumanTaskReuseTriggerChange($event)\"\r\n label=\"Trigger to reuse\"\r\n hint=\"Explicitly select the FormSubmitTrigger whose submitted form payload is reused.\"\r\n [options]=\"triggerOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n @if (humanTaskReuseFormBinding()) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Reused trigger form</div>\r\n @if (humanTaskFormSummary(); as summary) {\r\n <div\r\n class=\"flex flex-wrap items-center gap-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n @if (summary.formLabel) {\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.formLabel }}\r\n </span>\r\n }\r\n @if (summary.formVersionId) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ summary.formVersionId }}\r\n </span>\r\n }\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.fieldCount }} fields\r\n </span>\r\n <span class=\"rounded-md bg-(--p-surface-100) px-2 py-1\">\r\n {{ summary.requiredCount }} required\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n @if (formSchema(); as schema) {\r\n <details\r\n class=\"mt-3 rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Form schema preview\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(schema.schema) }}</pre>\r\n </details>\r\n }\r\n\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">HumanTask routes</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (key of routeOutputKeys(); track key) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ key }}</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"HumanApproval\") {\r\n @if (sectionInMain(\"approvalTask\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Approval task</div>\r\n @if (\r\n assignmentOptions()?.providerStatus &&\r\n assignmentOptions()?.providerStatus !== \"Available\"\r\n ) {\r\n <p class=\"fp-ae-copy\">\r\n Assignment provider status:\r\n {{ assignmentOptions()?.providerStatus }}. The backend provider\r\n is the source of truth for available assignees.\r\n </p>\r\n }\r\n <div class=\"grid gap-3 xl:grid-cols-3\">\r\n <mt-text-field\r\n [ngModel]=\"config()['title'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:title')\"\r\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\r\n label=\"Approval title\"\r\n hint=\"Approval title shown to the assigned human approver.\"\r\n [required]=\"isConfigFieldRequired('title')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"selectedAssignmentKey()\"\r\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\r\n label=\"Assignment\"\r\n hint=\"Backend-provided assignee, role, or group that can decide this approval.\"\r\n [required]=\"isConfigFieldRequired('assignment')\"\r\n [options]=\"assignmentSelectOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (humanApprovalSupportsConfig(\"priority\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['priority'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('priority', $event)\"\r\n label=\"Priority\"\r\n hint=\"Approval priority when supported by the backend schema.\"\r\n [required]=\"isConfigFieldRequired('priority')\"\r\n />\r\n }\r\n </div>\r\n <mt-textarea-field\r\n class=\"mt-3 w-full\"\r\n [ngModel]=\"config()['message'] ?? config()['instructions'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message')\"\r\n (ngModelChange)=\"onConfigFieldChange('message', $event)\"\r\n label=\"Approval message\"\r\n hint=\"Decision instructions shown to the approver.\"\r\n [required]=\"isConfigFieldRequired('message')\"\r\n rows=\"4\"\r\n />\r\n @if (\r\n humanApprovalSupportsConfig(\"dueDate\") ||\r\n humanApprovalSupportsConfig(\"dueInSeconds\") ||\r\n humanApprovalSupportsConfig(\"timeoutSeconds\") ||\r\n humanApprovalSupportsConfig(\"expiresAt\") ||\r\n humanApprovalSupportsConfig(\"commentsRequired\") ||\r\n humanApprovalSupportsConfig(\"allowReturn\")\r\n ) {\r\n <div class=\"mt-3 grid gap-3 xl:grid-cols-4\">\r\n @if (humanApprovalSupportsConfig(\"dueDate\")) {\r\n <mt-date-field\r\n [ngModel]=\"config()['dueDate'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('dueDate', $event)\"\r\n label=\"Due date\"\r\n hint=\"Backend-supported approval due date.\"\r\n [required]=\"isConfigFieldRequired('dueDate')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"dueInSeconds\")) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['dueInSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('dueInSeconds', $event)\r\n \"\r\n label=\"Due in seconds\"\r\n hint=\"Relative approval due duration when supported by the backend schema.\"\r\n [required]=\"isConfigFieldRequired('dueInSeconds')\"\r\n [min]=\"0\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"timeoutSeconds\")) {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('timeoutSeconds', $event)\r\n \"\r\n label=\"Timeout seconds\"\r\n hint=\"Approval timeout emitted according to backend approval runtime support.\"\r\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\r\n [min]=\"0\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"expiresAt\")) {\r\n <mt-date-field\r\n [ngModel]=\"config()['expiresAt'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('expiresAt', $event)\"\r\n label=\"Expires at\"\r\n hint=\"Backend-supported approval expiry timestamp.\"\r\n [required]=\"isConfigFieldRequired('expiresAt')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"commentsRequired\")) {\r\n <div\r\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <mt-toggle-field\r\n size=\"small\"\r\n label=\"Comments required\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['commentsRequired'] === true\"\r\n [required]=\"isConfigFieldRequired('commentsRequired')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('commentsRequired', $event === true)\r\n \"\r\n hint=\"Require approver comments when the backend supports this flag.\"\r\n />\r\n </div>\r\n }\r\n @if (humanApprovalSupportsConfig(\"allowReturn\")) {\r\n <div\r\n class=\"flex min-h-[4.25rem] items-end rounded-md border border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <mt-toggle-field\r\n size=\"small\"\r\n label=\"Allow return for changes\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['allowReturn'] !== false\"\r\n [required]=\"isConfigFieldRequired('allowReturn')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('allowReturn', $event === true)\r\n \"\r\n hint=\"Allow ReturnForChanges when the backend schema exposes this option.\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n <div class=\"mt-3 space-y-3\">\r\n <div class=\"fp-ae-section-title\">Decision options</div>\r\n <div\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n @if (selectedApprovalDecisionRows().length > 0) {\r\n <div\r\n class=\"hidden border-b border-surface-200 bg-surface-50 px-3 py-2 text-[12px] font-semibold text-(--p-text-muted-color) xl:grid xl:grid-cols-[1.1fr_1fr_1.2fr_80px_44px] xl:gap-3\"\r\n >\r\n <span>Decision label</span>\r\n <span>Canonical value</span>\r\n <span>Route output key</span>\r\n <span>Routes</span>\r\n <span>Action</span>\r\n </div>\r\n @for (\r\n decision of selectedApprovalDecisionRows();\r\n track decision.value\r\n ) {\r\n <div\r\n class=\"grid gap-2 border-b border-surface-100 px-3 py-3 last:border-b-0 xl:grid-cols-[1.1fr_1fr_1.2fr_80px_44px] xl:items-center xl:gap-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <div\r\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\r\n >\r\n Decision label\r\n </div>\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ decision.label }}\r\n </div>\r\n </div>\r\n <div class=\"min-w-0\">\r\n <div\r\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\r\n >\r\n Canonical value\r\n </div>\r\n <div\r\n class=\"truncate font-mono text-[12px] text-(--p-text-color)\"\r\n >\r\n {{ decision.value }}\r\n </div>\r\n </div>\r\n <div class=\"min-w-0\">\r\n <div\r\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\r\n >\r\n Route output key\r\n </div>\r\n <div\r\n class=\"truncate font-mono text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n {{ decision.routeOutputKey }}\r\n </div>\r\n </div>\r\n <div>\r\n <div\r\n class=\"text-[11px] font-semibold text-(--p-text-muted-color) xl:hidden\"\r\n >\r\n Routes\r\n </div>\r\n <div class=\"text-[13px] text-(--p-text-color)\">\r\n {{ decision.routeCount }}\r\n </div>\r\n </div>\r\n <mt-button\r\n class=\"justify-self-start xl:justify-self-end\"\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n icon=\"general.trash-01\"\r\n tooltip=\"Remove decision option\"\r\n (onClick)=\"removeApprovalDecision(decision.value)\"\r\n />\r\n </div>\r\n }\r\n } @else {\r\n <div\r\n class=\"px-3 py-4 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n Add at least one backend-supported approval decision.\r\n </div>\r\n }\r\n </div>\r\n @if (approvalDecisionIssues().length > 0) {\r\n <div\r\n class=\"rounded-lg border border-[rgb(var(--fp-error))]/30 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n @for (issue of approvalDecisionIssues(); track $index) {\r\n <div>{{ issue }}</div>\r\n }\r\n </div>\r\n }\r\n <div class=\"grid gap-2 md:grid-cols-[minmax(0,1fr)_auto]\">\r\n <mt-select-field\r\n [ngModel]=\"approvalDecisionToAdd()\"\r\n (ngModelChange)=\"approvalDecisionToAdd.set($event)\"\r\n label=\"Decision to add\"\r\n hint=\"Only backend-supported decisions with matching route outputs are available.\"\r\n [required]=\"isConfigFieldRequired('allowedDecisions')\"\r\n [options]=\"addableApprovalDecisionOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-button\r\n class=\"self-end\"\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add decision option\"\r\n [disabled]=\"!approvalDecisionToAdd()\"\r\n (onClick)=\"addApprovalDecision()\"\r\n />\r\n </div>\r\n </div>\r\n @if (\r\n humanApprovalSupportsConfig(\"payload\") ||\r\n humanApprovalSupportsConfig(\"context\") ||\r\n humanApprovalSupportsConfig(\"metadata\")\r\n ) {\r\n <div class=\"mt-3 grid gap-3\">\r\n @if (humanApprovalSupportsConfig(\"payload\")) {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"valueText(config()['payload'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:payload')\"\r\n (ngModelChange)=\"onConfigFieldChange('payload', $event)\"\r\n label=\"Payload\"\r\n hint=\"Approval task payload or context fields supported by backend schema.\"\r\n [required]=\"isConfigFieldRequired('payload')\"\r\n rows=\"5\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"context\")) {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"valueText(config()['context'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:context')\"\r\n (ngModelChange)=\"onConfigFieldChange('context', $event)\"\r\n label=\"Context\"\r\n hint=\"Additional approval context supported by backend schema.\"\r\n [required]=\"isConfigFieldRequired('context')\"\r\n rows=\"5\"\r\n />\r\n }\r\n @if (humanApprovalSupportsConfig(\"metadata\")) {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"valueText(config()['metadata'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:metadata')\"\r\n (ngModelChange)=\"onConfigFieldChange('metadata', $event)\"\r\n label=\"Metadata\"\r\n hint=\"Additional approval metadata supported by the backend schema.\"\r\n [required]=\"isConfigFieldRequired('metadata')\"\r\n rows=\"4\"\r\n />\r\n }\r\n </div>\r\n }\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Validate assignment\"\r\n (onClick)=\"validateAssignment()\"\r\n />\r\n </div>\r\n @if (assignmentValidation(); as result) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n {{\r\n result.isValid === false\r\n ? \"Assignment invalid\"\r\n : \"Assignment accepted by helper\"\r\n }}\r\n @for (issue of resultIssues(result); track $index) {\r\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\r\n {{ issue }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"ConnectorAction\") {\r\n @if (sectionInMain(\"humanReviewRequest\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">\r\n {{ humanReviewProviderLabel() }} human review\r\n </div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"config()['channelType'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('channelType', $event)\"\r\n label=\"Channel\"\r\n [required]=\"isConfigFieldRequired('channelType')\"\r\n />\r\n </div>\r\n\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\r\n <mt-text-field\r\n [ngModel]=\"humanReviewDestination()['recipient'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:destination:recipient')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('destination', 'recipient', $event)\r\n \"\r\n label=\"Recipient\"\r\n [required]=\"isConnectorDestinationRequired()\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"humanReviewDestination()['channelId'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:destination:channelId')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('destination', 'channelId', $event)\r\n \"\r\n label=\"Channel ID\"\r\n [required]=\"isConnectorDestinationRequired()\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"humanReviewDestination()['threadKey'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:destination:threadKey')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('destination', 'threadKey', $event)\r\n \"\r\n label=\"Thread key\"\r\n [required]=\"isConnectorDestinationRequired()\"\r\n />\r\n </div>\r\n\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-text-field\r\n [ngModel]=\"humanReviewMessage()['subject'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message:subject')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('message', 'subject', $event)\r\n \"\r\n label=\"Subject\"\r\n [required]=\"isConnectorMessageRequired()\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full\"\r\n [ngModel]=\"humanReviewMessage()['body'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message:body')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('message', 'body', $event)\r\n \"\r\n label=\"Message\"\r\n [required]=\"isConnectorMessageRequired()\"\r\n rows=\"5\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"humanReviewMessage()['templateKey'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message:templateKey')\"\r\n (ngModelChange)=\"\r\n onNestedConfigFieldChange('message', 'templateKey', $event)\r\n \"\r\n label=\"Template key\"\r\n [required]=\"isConnectorMessageRequired()\"\r\n />\r\n </div>\r\n\r\n <div class=\"mt-4\">\r\n <div class=\"mb-2 flex items-center justify-between gap-3\">\r\n <div class=\"fp-ae-label\">Response options</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Add option\"\r\n (onClick)=\"addHumanReviewResponseOption()\"\r\n />\r\n </div>\r\n <div class=\"grid gap-2\">\r\n @for (\r\n option of humanReviewResponseOptions();\r\n track option.key + \":\" + $index\r\n ) {\r\n <div\r\n class=\"grid gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-2 md:grid-cols-[1fr_1fr_1fr_auto]\"\r\n >\r\n <mt-text-field\r\n [ngModel]=\"option.key\"\r\n (ngModelChange)=\"\r\n updateHumanReviewResponseOption($index, 'key', $event)\r\n \"\r\n label=\"Key\"\r\n [required]=\"isConfigFieldRequired('responseOptions')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"option.label\"\r\n (ngModelChange)=\"\r\n updateHumanReviewResponseOption($index, 'label', $event)\r\n \"\r\n label=\"Label\"\r\n [required]=\"isConfigFieldRequired('responseOptions')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"option.routeOutputKey\"\r\n (ngModelChange)=\"\r\n updateHumanReviewResponseOption(\r\n $index,\r\n 'routeOutputKey',\r\n $event\r\n )\r\n \"\r\n label=\"Route output\"\r\n [required]=\"isConfigFieldRequired('responseOptions')\"\r\n />\r\n <div class=\"flex items-end justify-end\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"general.x-close\"\r\n [disabled]=\"option.routeCount > 0\"\r\n (onClick)=\"removeHumanReviewResponseOption($index)\"\r\n [attr.aria-label]=\"'Remove response option'\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"selectedAssignmentKey()\"\r\n (ngModelChange)=\"onAssignmentOptionChange($event)\"\r\n label=\"Assignment\"\r\n [required]=\"isConfigFieldRequired('assignment')\"\r\n [options]=\"assignmentSelectOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['credentialRef'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('credentialRef', $event)\"\r\n label=\"Credential\"\r\n [required]=\"isConfigFieldRequired('credentialRef')\"\r\n />\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['timeoutSeconds'])\"\r\n (ngModelChange)=\"onConfigFieldChange('timeoutSeconds', $event)\"\r\n label=\"Timeout seconds\"\r\n [required]=\"isConfigFieldRequired('timeoutSeconds')\"\r\n [min]=\"1\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['failureBehavior'] ?? 'RouteFailure'\"\r\n (ngModelChange)=\"onConfigFieldChange('failureBehavior', $event)\"\r\n label=\"Failure behavior\"\r\n [required]=\"isConfigFieldRequired('failureBehavior')\"\r\n [options]=\"humanReviewFailureBehaviorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n label=\"Validate assignment\"\r\n (onClick)=\"validateAssignment()\"\r\n />\r\n </div>\r\n @if (assignmentValidation(); as result) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n {{\r\n result.isValid === false\r\n ? \"Assignment invalid\"\r\n : \"Assignment accepted by helper\"\r\n }}\r\n @for (issue of resultIssues(result); track $index) {\r\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\r\n {{ issue }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"mt-4 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Route outputs</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (key of routeOutputKeys(); track key) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ key }}</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"ConvertToFile\") {\r\n @if (sectionInMain(\"convertToFile\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">\r\n {{ convertToFileActionLabel() }}\r\n </div>\r\n @if (convertToFileUnavailableReason(); as reason) {\r\n <div\r\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ reason }}\r\n </div>\r\n }\r\n\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"config()['fileName'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:fileName')\"\r\n (ngModelChange)=\"onConfigFieldChange('fileName', $event)\"\r\n label=\"File name\"\r\n [required]=\"isConfigFieldRequired('fileName')\"\r\n />\r\n @if (convertToFileIsBase64()) {\r\n <mt-text-field\r\n [ngModel]=\"config()['contentType'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('contentType', $event)\"\r\n label=\"Content type\"\r\n [required]=\"isConfigFieldRequired('contentType')\"\r\n />\r\n } @else {\r\n <mt-text-field\r\n [ngModel]=\"convertToFileContentType()\"\r\n [readonly]=\"true\"\r\n label=\"Content type\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (\r\n convertToFileIsCsv() ||\r\n convertToFileIsJson() ||\r\n convertToFileIsSpreadsheet()\r\n ) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"convertToFileSourceMode()\"\r\n (ngModelChange)=\"onConvertToFileSourceModeChange($event)\"\r\n label=\"Data source\"\r\n [required]=\"isConfigFieldRequired('sourceExpression')\"\r\n [options]=\"convertToFileSourceOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (convertToFileSourceMode() === \"expression\") {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"convertToFileSourceExpression()\"\r\n (focusin)=\"setExpressionTarget('config:sourceExpression')\"\r\n (ngModelChange)=\"\r\n onConvertToFileSourceExpressionChange($event)\r\n \"\r\n label=\"Source expression\"\r\n [required]=\"isConfigFieldRequired('sourceExpression')\"\r\n />\r\n }\r\n @if (convertToFileIsCsv() || convertToFileIsSpreadsheet()) {\r\n <div\r\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3 md:col-span-2\"\r\n >\r\n <div class=\"mb-2 flex items-center justify-between gap-3\">\r\n <div class=\"fp-ae-label\">Columns</div>\r\n @if (convertToFileColumns().length) {\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n label=\"Clear\"\r\n (onClick)=\"clearConvertToFileColumns()\"\r\n />\r\n }\r\n </div>\r\n <div class=\"mb-3 flex min-h-8 flex-wrap gap-1.5\">\r\n @if (convertToFileColumns().length) {\r\n @for (column of convertToFileColumns(); track column) {\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >\r\n {{ column }}\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex h-4 w-4 items-center justify-center rounded-sm text-[10px] hover:bg-(--p-surface-200) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n (click)=\"removeConvertToFileColumn(column)\"\r\n [attr.aria-label]=\"'Remove column ' + column\"\r\n >\r\n x\r\n </button>\r\n </span>\r\n }\r\n } @else {\r\n <span\r\n class=\"rounded-md bg-(--p-content-background) px-2 py-1 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n All fields\r\n </span>\r\n }\r\n </div>\r\n <div class=\"grid items-end gap-2 md:grid-cols-[1fr_auto]\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"convertToFileColumnDraft()\"\r\n (ngModelChange)=\"\r\n onConvertToFileColumnDraftChange($event)\r\n \"\r\n (keyup.enter)=\"addConvertToFileColumns()\"\r\n label=\"Add column\"\r\n [required]=\"isConfigFieldRequired('columns')\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add\"\r\n [disabled]=\"!convertToFileColumnDraft().trim()\"\r\n (onClick)=\"addConvertToFileColumns()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsCsv()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['includeHeaders'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('includeHeaders', $event === true)\r\n \"\r\n label=\"Include headers\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['delimiter'] ?? ','\"\r\n (ngModelChange)=\"onConfigFieldChange('delimiter', $event)\"\r\n label=\"Delimiter\"\r\n [required]=\"isConfigFieldRequired('delimiter')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['encoding'] ?? 'utf-8'\"\r\n [readonly]=\"true\"\r\n label=\"Encoding\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsJson()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['pretty'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('pretty', $event === true)\r\n \"\r\n label=\"Pretty JSON\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsHtml()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-text-field\r\n [ngModel]=\"config()['title'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:title')\"\r\n (ngModelChange)=\"onConfigFieldChange('title', $event)\"\r\n label=\"Title\"\r\n [required]=\"isConfigFieldRequired('title')\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full\"\r\n [ngModel]=\"config()['body'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:body')\"\r\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\r\n label=\"Body\"\r\n [required]=\"isConfigFieldRequired('body')\"\r\n rows=\"7\"\r\n />\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['sanitize'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('sanitize', $event === true)\r\n \"\r\n label=\"Sanitize HTML\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsIcs()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"config()['summary'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:summary')\"\r\n (ngModelChange)=\"onConfigFieldChange('summary', $event)\"\r\n label=\"Event summary\"\r\n [required]=\"isConfigFieldRequired('summary')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['timezone'] ?? 'UTC'\"\r\n (ngModelChange)=\"onConfigFieldChange('timezone', $event)\"\r\n label=\"Timezone\"\r\n [required]=\"isConfigFieldRequired('timezone')\"\r\n />\r\n <mt-date-field\r\n [ngModel]=\"config()['start'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('start', $event)\"\r\n label=\"Start\"\r\n [required]=\"isConfigFieldRequired('start')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n <mt-date-field\r\n [ngModel]=\"config()['end'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('end', $event)\"\r\n label=\"End\"\r\n [required]=\"isConfigFieldRequired('end')\"\r\n [showTime]=\"true\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['location'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:location')\"\r\n (ngModelChange)=\"onConfigFieldChange('location', $event)\"\r\n label=\"Location\"\r\n [required]=\"isConfigFieldRequired('location')\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full\"\r\n [ngModel]=\"config()['description'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:description')\"\r\n (ngModelChange)=\"onConfigFieldChange('description', $event)\"\r\n label=\"Description\"\r\n [required]=\"isConfigFieldRequired('description')\"\r\n rows=\"4\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsSpreadsheet()) {\r\n <div\r\n class=\"mt-4 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"mb-3 flex items-center justify-between gap-3\">\r\n <div class=\"fp-ae-label\">Sheets</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add sheet\"\r\n (onClick)=\"addConvertToFileSheet()\"\r\n />\r\n </div>\r\n <div class=\"grid gap-3\">\r\n @for (sheet of convertToFileSheetRows(); track sheet.index) {\r\n <div\r\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n <div\r\n class=\"grid gap-3 md:grid-cols-[minmax(140px,0.45fr)_minmax(0,1fr)_minmax(180px,0.6fr)_auto]\"\r\n >\r\n <mt-text-field\r\n [ngModel]=\"sheet.name\"\r\n (ngModelChange)=\"\r\n updateConvertToFileSheet(\r\n sheet.index,\r\n 'name',\r\n $event\r\n )\r\n \"\r\n label=\"Sheet name\"\r\n [required]=\"isConfigFieldRequired('sheets')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"sheet.sourceExpression\"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:sheets:' + sheet.index + ':source'\r\n )\r\n \"\r\n (ngModelChange)=\"\r\n updateConvertToFileSheet(\r\n sheet.index,\r\n 'sourceExpression',\r\n $event\r\n )\r\n \"\r\n label=\"Source\"\r\n [required]=\"isConfigFieldRequired('sheets')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"sheet.columnsText\"\r\n (ngModelChange)=\"\r\n updateConvertToFileSheet(\r\n sheet.index,\r\n 'columns',\r\n $event\r\n )\r\n \"\r\n label=\"Columns\"\r\n [required]=\"isConfigFieldRequired('sheets')\"\r\n />\r\n <div class=\"flex items-end justify-end\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n icon=\"general.trash-01\"\r\n tooltip=\"Remove sheet\"\r\n (onClick)=\"removeConvertToFileSheet(sheet.index)\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n } @empty {\r\n <div\r\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n Single sheet uses the selected data source.\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsText()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-textarea-field\r\n class=\"w-full\"\r\n [ngModel]=\"config()['content'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:content')\"\r\n (ngModelChange)=\"onConfigFieldChange('content', $event)\"\r\n label=\"Content\"\r\n [required]=\"isConfigFieldRequired('content')\"\r\n rows=\"7\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"config()['encoding'] ?? 'utf-8'\"\r\n [readonly]=\"true\"\r\n label=\"Encoding\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (convertToFileIsBase64()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"config()['base64Expression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:base64Expression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('base64Expression', $event)\r\n \"\r\n label=\"Base64 source\"\r\n [required]=\"isConfigFieldRequired('base64Expression')\"\r\n rows=\"5\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['extension'] ?? convertToFileExtension()\"\r\n (ngModelChange)=\"onConfigFieldChange('extension', $event)\"\r\n label=\"Extension\"\r\n [required]=\"isConfigFieldRequired('extension')\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"DataTransform\") {\r\n @if (sectionInMain(\"dataTransform\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">\r\n {{ dataTransformActionLabel() }}\r\n </div>\r\n @if (dataTransformUnavailableReason(); as reason) {\r\n <div\r\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ reason }}\r\n </div>\r\n }\r\n\r\n @if (\r\n !dataTransformIsMerge() && dataTransformUsesKnownActionEditor()\r\n ) {\r\n <div class=\"grid gap-3\">\r\n <div class=\"grid gap-3 md:grid-cols-[minmax(220px,0.45fr)_1fr]\">\r\n <mt-select-field\r\n [ngModel]=\"dataTransformSourcePreset()\"\r\n (ngModelChange)=\"onDataTransformSourcePresetChange($event)\"\r\n [label]=\"dataTransformSourceLabel()\"\r\n hint=\"Choose which list this action should transform.\"\r\n [required]=\"isConfigFieldRequired('sourceExpression')\"\r\n [options]=\"dataTransformSourceOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (showDataTransformSourceExpression()) {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"dataTransformSourceExpression()\"\r\n (focusin)=\"setExpressionTarget('config:sourceExpression')\"\r\n (ngModelChange)=\"\r\n onDataTransformSourceExpressionChange($event)\r\n \"\r\n [label]=\"dataTransformSourceExpressionLabel()\"\r\n hint=\"Must resolve to an array of items.\"\r\n [required]=\"isConfigFieldRequired('sourceExpression')\"\r\n />\r\n } @else {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"fp-ae-label mb-1\">Selected input</div>\r\n <div\r\n class=\"truncate font-mono text-sm text-(--p-text-color)\"\r\n >\r\n {{ dataTransformSourceSummary() }}\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <div class=\"flex flex-wrap gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.search-sm\"\r\n label=\"Preview source\"\r\n [disabled]=\"!dataTransformSourceExpression()\"\r\n (onClick)=\"previewDataTransformSourceExpression()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (expressionPreview(); as preview) {\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Source preview</div>\r\n <pre class=\"fp-ae-code\">{{ schemaText(preview) }}</pre>\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsFilter()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n @if (dataTransformUsesStructuredConditionEditor()) {\r\n <div class=\"fp-ae-label\">Keep item when</div>\r\n <div class=\"grid gap-3 md:grid-cols-[1fr_180px_1fr]\">\r\n <mt-text-field\r\n [ngModel]=\"dataTransformConditionFieldText('left')\"\r\n (focusin)=\"setExpressionTarget('config:condition:left')\"\r\n (ngModelChange)=\"\r\n onDataTransformConditionFieldChange('left', $event)\r\n \"\r\n label=\"Left value\"\r\n hint=\"Field, value, or expression from each item.\"\r\n [required]=\"isConfigFieldRequired('condition.left')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"\r\n dataTransformConditionFieldValue('operator') || 'equals'\r\n \"\r\n (ngModelChange)=\"\r\n onDataTransformConditionFieldChange('operator', $event)\r\n \"\r\n label=\"Operator\"\r\n [required]=\"isConfigFieldRequired('condition.operator')\"\r\n [options]=\"ifOperatorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"dataTransformConditionFieldText('right')\"\r\n (focusin)=\"setExpressionTarget('config:condition:right')\"\r\n (ngModelChange)=\"\r\n onDataTransformConditionFieldChange('right', $event)\r\n \"\r\n label=\"Right value\"\r\n hint=\"Value or expression to compare against.\"\r\n [required]=\"isConfigFieldRequired('condition.right')\"\r\n />\r\n </div>\r\n } @else {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"dataTransformConditionText()\"\r\n (focusin)=\"setExpressionTarget('config:condition')\"\r\n (ngModelChange)=\"onDataTransformConditionChange($event)\"\r\n label=\"Keep item when\"\r\n hint=\"Condition expression or JSON supplied by the backend catalog.\"\r\n [required]=\"isConfigFieldRequired('condition')\"\r\n rows=\"4\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsSort()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['sortBy'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:sortBy')\"\r\n (ngModelChange)=\"onConfigFieldChange('sortBy', $event)\"\r\n label=\"Sort by field\"\r\n hint=\"Field path inside each item, for example createdAt.\"\r\n [required]=\"isConfigFieldRequired('sortBy')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['sortExpression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:sortExpression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('sortExpression', $event)\r\n \"\r\n label=\"Custom sort expression\"\r\n hint=\"Optional expression when a simple field path is not enough.\"\r\n [required]=\"isConfigFieldRequired('sortExpression')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['direction'] ?? 'asc'\"\r\n (ngModelChange)=\"onConfigFieldChange('direction', $event)\"\r\n label=\"Direction\"\r\n [required]=\"isConfigFieldRequired('direction')\"\r\n [options]=\"dataTransformDirectionOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['valueType'] ?? 'string'\"\r\n (ngModelChange)=\"onConfigFieldChange('valueType', $event)\"\r\n label=\"Value type\"\r\n [required]=\"isConfigFieldRequired('valueType')\"\r\n [options]=\"dataTransformValueTypeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsLimit()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['offset']) ?? 0\"\r\n (ngModelChange)=\"onConfigFieldChange('offset', $event)\"\r\n label=\"Skip first\"\r\n [required]=\"isConfigFieldRequired('offset')\"\r\n [min]=\"0\"\r\n />\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['limit'])\"\r\n (ngModelChange)=\"onConfigFieldChange('limit', $event)\"\r\n label=\"Take at most\"\r\n [required]=\"isConfigFieldRequired('limit')\"\r\n [min]=\"1\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsMapFields()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-select-field\r\n [ngModel]=\"config()['mode'] ?? 'merge'\"\r\n (ngModelChange)=\"onConfigFieldChange('mode', $event)\"\r\n label=\"Field mode\"\r\n [required]=\"isConfigFieldRequired('mode')\"\r\n [options]=\"dataTransformMapModeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'fields',\r\n rows: fieldsRows(),\r\n keyLabel: 'Output field',\r\n valueLabel: 'Value or expression',\r\n addLabel: 'Add mapped field',\r\n emptyText:\r\n 'No fields mapped yet. Add an output field to create or update item data.',\r\n keyHint: 'Name of the field in the output item.',\r\n valueHint:\r\n 'Use a fixed value or an expression from each item.',\r\n required: isConfigFieldRequired('fields'),\r\n }\r\n \"\r\n />\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsAggregate()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['groupBy'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:groupBy')\"\r\n (ngModelChange)=\"onConfigFieldChange('groupBy', $event)\"\r\n label=\"Group items by\"\r\n hint=\"Optional field path or expression used to group results.\"\r\n [required]=\"isConfigFieldRequired('groupBy')\"\r\n />\r\n <div class=\"rounded-md border border-surface-200 p-3\">\r\n <div\r\n class=\"mb-3 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <div class=\"fp-ae-label\">Summary fields</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add summary\"\r\n (onClick)=\"addDataTransformAggregation()\"\r\n />\r\n </div>\r\n <div class=\"space-y-3\">\r\n @for (\r\n item of dataTransformAggregationRows();\r\n track item.index\r\n ) {\r\n <div\r\n class=\"grid gap-2 rounded-md bg-surface-50 p-3 md:grid-cols-[minmax(160px,0.8fr)_minmax(180px,0.9fr)_1fr_minmax(150px,0.7fr)_auto]\"\r\n >\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"item.key\"\r\n (ngModelChange)=\"\r\n updateDataTransformAggregation(\r\n item.index,\r\n 'key',\r\n $event\r\n )\r\n \"\r\n label=\"Output field\"\r\n hint=\"Name saved in the summary output.\"\r\n [required]=\"isConfigFieldRequired('aggregations')\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"item.operation\"\r\n (ngModelChange)=\"\r\n updateDataTransformAggregation(\r\n item.index,\r\n 'operation',\r\n $event\r\n )\r\n \"\r\n label=\"Calculation\"\r\n [required]=\"isConfigFieldRequired('aggregations')\"\r\n [options]=\"dataTransformAggregationOperationOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"item.field\"\r\n [readonly]=\"\r\n !dataTransformAggregationNeedsField(item.operation)\r\n \"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:aggregations:' + item.index + ':field'\r\n )\r\n \"\r\n (ngModelChange)=\"\r\n updateDataTransformAggregation(\r\n item.index,\r\n 'field',\r\n $event\r\n )\r\n \"\r\n label=\"Field or expression\"\r\n [hint]=\"\r\n dataTransformAggregationFieldHint(item.operation)\r\n \"\r\n [required]=\"\r\n isConfigFieldRequired('aggregations') &&\r\n dataTransformAggregationNeedsField(item.operation)\r\n \"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"item.valueType\"\r\n (ngModelChange)=\"\r\n updateDataTransformAggregation(\r\n item.index,\r\n 'valueType',\r\n $event\r\n )\r\n \"\r\n label=\"Value type\"\r\n [required]=\"isConfigFieldRequired('aggregations')\"\r\n [options]=\"dataTransformValueTypeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-button\r\n class=\"self-end\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n label=\"Remove\"\r\n (onClick)=\"removeDataTransformAggregation(item.index)\"\r\n />\r\n </div>\r\n } @empty {\r\n <div\r\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n No summary fields yet. Add a summary such as count,\r\n total, average, minimum, or maximum.\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (dataTransformIsMerge()) {\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-2\">\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"config()['leftExpression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:leftExpression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('leftExpression', $event)\r\n \"\r\n label=\"First list\"\r\n hint=\"Expression that resolves to the first list of items.\"\r\n [required]=\"isConfigFieldRequired('leftExpression')\"\r\n rows=\"3\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"config()['rightExpression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:rightExpression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('rightExpression', $event)\r\n \"\r\n label=\"Second list\"\r\n hint=\"Expression that resolves to the second list of items.\"\r\n [required]=\"isConfigFieldRequired('rightExpression')\"\r\n rows=\"3\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['strategy'] ?? 'append'\"\r\n (ngModelChange)=\"onConfigFieldChange('strategy', $event)\"\r\n label=\"Merge strategy\"\r\n [required]=\"isConfigFieldRequired('strategy')\"\r\n [options]=\"dataTransformMergeStrategyOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (dataTransformUsesKeyedMerge()) {\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['leftKey'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('leftKey', $event)\"\r\n label=\"First list key\"\r\n [required]=\"isConfigFieldRequired('leftKey')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['rightKey'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('rightKey', $event)\"\r\n label=\"Second list key\"\r\n [required]=\"isConfigFieldRequired('rightKey')\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (!dataTransformUsesKnownActionEditor()) {\r\n <div class=\"mt-4 grid gap-3\">\r\n @for (field of dataTransformGenericFields(); track field.key) {\r\n @if (field.enumValues.length) {\r\n <mt-select-field\r\n [ngModel]=\"config()[field.key] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n [options]=\"enumOptions(field.enumValues)\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n } @else if (field.type === \"number\") {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()[field.key])\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n } @else {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"fieldText(field.key)\"\r\n (focusin)=\"setExpressionTarget('config:' + field.key)\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n }\r\n } @empty {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"dataTransformActionKey()\"\r\n [readonly]=\"true\"\r\n label=\"Action\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"mt-4 grid gap-3 md:grid-cols-3\">\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['maxOutputItems'])\"\r\n (ngModelChange)=\"onConfigFieldChange('maxOutputItems', $event)\"\r\n label=\"Output limit\"\r\n [required]=\"isConfigFieldRequired('maxOutputItems')\"\r\n [min]=\"1\"\r\n />\r\n <div\r\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3 md:col-span-2\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Output</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (field of dataTransformOutputFieldLabels; track field) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ field }}</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"LoopOverItems\") {\r\n @if (sectionInMain(\"loopOverItems\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Loop over items</div>\r\n\r\n <div class=\"grid gap-3\">\r\n <div class=\"grid gap-3 md:grid-cols-[minmax(220px,0.45fr)_1fr]\">\r\n <mt-select-field\r\n [ngModel]=\"loopSourceMode()\"\r\n (ngModelChange)=\"onLoopSourceModeChange($event)\"\r\n label=\"List source\"\r\n hint=\"Choose an output array or type an array expression.\"\r\n [required]=\"isConfigFieldRequired('itemsExpression')\"\r\n [options]=\"loopSourceModeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (showLoopItemsExpression()) {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"loopItemsExpression()\"\r\n (focusin)=\"setExpressionTarget('config:itemsExpression')\"\r\n (ngModelChange)=\"onLoopItemsExpressionChange($event)\"\r\n label=\"Array expression\"\r\n hint=\"Must resolve to an array.\"\r\n [required]=\"isConfigFieldRequired('itemsExpression')\"\r\n />\r\n } @else {\r\n <mt-select-field\r\n [ngModel]=\"loopSelectedOutputArray()\"\r\n (ngModelChange)=\"onLoopOutputArrayChange($event)\"\r\n label=\"Output array\"\r\n hint=\"Arrays exposed by previous connected steps.\"\r\n [required]=\"isConfigFieldRequired('itemsExpression')\"\r\n [options]=\"loopOutputArrayOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n }\r\n </div>\r\n <div class=\"flex flex-wrap gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.search-sm\"\r\n label=\"Preview array\"\r\n [disabled]=\"!loopItemsExpression()\"\r\n (onClick)=\"previewLoopItemsExpression()\"\r\n />\r\n </div>\r\n </div>\r\n\r\n @if (expressionPreview(); as preview) {\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Expression preview</div>\r\n <pre class=\"fp-ae-code\">{{ schemaText(preview) }}</pre>\r\n </div>\r\n }\r\n\r\n <div class=\"mt-4 border-t border-surface-100 pt-4\">\r\n <div class=\"fp-ae-label mb-3\">Run settings</div>\r\n <div class=\"grid gap-3 md:grid-cols-2 xl:grid-cols-4\">\r\n <mt-number-field\r\n [ngModel]=\"\r\n numberValue(\r\n config()['maxItems'] ?? config()['maxIterations']\r\n )\r\n \"\r\n (ngModelChange)=\"onConfigFieldChange('maxItems', $event)\"\r\n label=\"Item limit\"\r\n hint=\"Optional maximum number of items to process.\"\r\n [required]=\"isConfigFieldRequired('maxItems')\"\r\n [min]=\"1\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"loopFailureBehavior()\"\r\n (ngModelChange)=\"onLoopFailureBehaviorChange($event)\"\r\n label=\"When an item fails\"\r\n [required]=\"isConfigFieldRequired('failureBehavior')\"\r\n [options]=\"loopFailureBehaviorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['emptyBehavior'] ?? 'done'\"\r\n (ngModelChange)=\"onConfigFieldChange('emptyBehavior', $event)\"\r\n label=\"When list is empty\"\r\n [required]=\"isConfigFieldRequired('emptyBehavior')\"\r\n [options]=\"loopEmptyBehaviorOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-toggle-field\r\n class=\"self-end pb-1\"\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['collectResults'] === true\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('collectResults', $event === true)\r\n \"\r\n label=\"Save item results\"\r\n hint=\"Stores a bounded summary for the done route.\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <details class=\"mt-4 border-t border-surface-100 pt-3\">\r\n <summary\r\n class=\"cursor-pointer text-sm font-semibold text-(--p-text-color)\"\r\n >\r\n Advanced runtime details\r\n </summary>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-3\">\r\n <div>\r\n <div class=\"fp-ae-label mb-1\">Current item token</div>\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2 font-mono text-sm text-(--p-text-color)\"\r\n >\r\n $item.json\r\n </div>\r\n </div>\r\n <div>\r\n <div class=\"fp-ae-label mb-1\">Loop context token</div>\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 px-3 py-2 font-mono text-sm text-(--p-text-color)\"\r\n >\r\n $loop.current\r\n </div>\r\n </div>\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['concurrency']) ?? 1\"\r\n (ngModelChange)=\"onLoopConcurrencyChange($event)\"\r\n label=\"Concurrency\"\r\n hint=\"This deployment supports 1.\"\r\n [required]=\"isConfigFieldRequired('concurrency')\"\r\n [min]=\"1\"\r\n [max]=\"1\"\r\n />\r\n </div>\r\n </details>\r\n </section>\r\n }\r\n }\r\n @case (\"FlowPlusCommit\") {\r\n @if (sectionInMain(\"flowplusCommit\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">FlowPlus commit</div>\r\n <p class=\"fp-ae-copy\">\r\n This node is the explicit module-data write boundary. Approval\r\n does not commit data unless this node is reached.\r\n </p>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['targetModule'] ?? ''\"\r\n (ngModelChange)=\"onModuleChange($event)\"\r\n label=\"Module\"\r\n hint=\"Module whose records will be written by this explicit FlowPlus commit.\"\r\n [required]=\"isConfigFieldRequired('targetModule')\"\r\n [options]=\"moduleOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['operation'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('operation', $event)\"\r\n label=\"Operation\"\r\n hint=\"Write operation supported by the selected backend module schema.\"\r\n [required]=\"isConfigFieldRequired('operation')\"\r\n [options]=\"operationOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <mt-text-field\r\n class=\"mt-3 font-mono\"\r\n [ngModel]=\"config()['idempotencyKey'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:idempotencyKey')\"\r\n (ngModelChange)=\"onConfigFieldChange('idempotencyKey', $event)\"\r\n label=\"Idempotency key\"\r\n hint=\"Optional stable key used by the backend to prevent duplicate writes.\"\r\n [required]=\"isConfigFieldRequired('idempotencyKey')\"\r\n />\r\n @if (selectedModuleFields().length) {\r\n <div class=\"mt-3 space-y-2\">\r\n <div class=\"fp-ae-section-title\">Module schema</div>\r\n <div class=\"grid gap-2 md:grid-cols-2\">\r\n @for (field of selectedModuleFields(); track field.key) {\r\n <div class=\"fp-ae-kv\">\r\n <span>{{ field.displayName ?? field.key }}</span>\r\n <strong\r\n >{{ field.viewType ?? \"Value\" }}\r\n @if (field.required) {\r\n *\r\n }\r\n </strong>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n <div class=\"mt-3\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'mapping',\r\n rows: mappingRows(),\r\n keyLabel: 'Module field',\r\n valueLabel: 'Expression / value',\r\n addLabel: 'Add module field',\r\n required: isConfigFieldRequired('mapping'),\r\n }\r\n \"\r\n />\r\n </div>\r\n <div class=\"mt-3 flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Validate mapping\"\r\n (onClick)=\"validateCommitMapping()\"\r\n />\r\n </div>\r\n @if (commitValidation(); as result) {\r\n <div\r\n class=\"mt-3 rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px]\"\r\n >\r\n {{\r\n result.isValid === false\r\n ? \"Mapping invalid\"\r\n : \"Mapping accepted by helper\"\r\n }}\r\n @for (issue of resultIssues(result); track $index) {\r\n <div class=\"mt-1 text-[11px] text-(--p-text-muted-color)\">\r\n {{ issue }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n @if (sectionInMain(\"credentials\")) {\r\n <div\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"fp-ae-label mb-2\">Credential</div>\r\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\r\n </div>\r\n }\r\n </section>\r\n }\r\n }\r\n @case (\"WebhookResponse\") {\r\n @if (sectionInMain(\"webhookResponse\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Webhook response</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['statusCode']) ?? 200\"\r\n (ngModelChange)=\"onConfigFieldChange('statusCode', $event)\"\r\n label=\"Status code\"\r\n hint=\"HTTP status code sent back by the webhook response node.\"\r\n [required]=\"isConfigFieldRequired('statusCode')\"\r\n [min]=\"100\"\r\n [max]=\"599\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['responseMode'] ?? 'json'\"\r\n (ngModelChange)=\"onConfigFieldChange('responseMode', $event)\"\r\n label=\"Response mode\"\r\n hint=\"How the webhook response body should be serialized.\"\r\n [required]=\"isConfigFieldRequired('responseMode')\"\r\n [options]=\"responseModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"mt-3\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n mapEditor;\r\n context: {\r\n objectKey: 'headers',\r\n rows: headerRows(),\r\n keyLabel: 'Header',\r\n valueLabel: 'Value',\r\n addLabel: 'Add header',\r\n }\r\n \"\r\n />\r\n </div>\r\n <mt-textarea-field\r\n class=\"mt-3 w-full font-mono\"\r\n [ngModel]=\"valueText(config()['body'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:body')\"\r\n (ngModelChange)=\"onConfigFieldChange('body', $event)\"\r\n label=\"Body\"\r\n hint=\"Body returned to the webhook caller. Expressions can use current execution data.\"\r\n [required]=\"isConfigFieldRequired('body')\"\r\n rows=\"6\"\r\n />\r\n </section>\r\n }\r\n }\r\n @case (\"CallAutomation\") {\r\n @if (sectionInMain(\"callAutomation\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Subworkflow</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['targetAutomationId'] ?? ''\"\r\n (ngModelChange)=\"onSubworkflowTargetChange($event)\"\r\n label=\"Target automation\"\r\n hint=\"Backend-provided automation candidate. Tenant and recursion validation stay on the backend.\"\r\n [required]=\"isConfigFieldRequired('targetAutomationId')\"\r\n [options]=\"subworkflowAutomationOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['revisionMode'] ?? 'Active'\"\r\n (ngModelChange)=\"onConfigFieldChange('revisionMode', $event)\"\r\n label=\"Revision mode\"\r\n hint=\"Choose which published or active child revision to call.\"\r\n [required]=\"isConfigFieldRequired('revisionMode')\"\r\n [options]=\"revisionModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (config()[\"revisionMode\"] === \"SpecificRevision\") {\r\n <mt-text-field\r\n [ngModel]=\"config()['specificRevisionId'] ?? ''\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('specificRevisionId', $event)\r\n \"\r\n label=\"Specific revision id\"\r\n hint=\"Required only when revision mode is Specific revision.\"\r\n [required]=\"isConfigFieldRequired('specificRevisionId')\"\r\n />\r\n }\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['waitForCompletion'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('waitForCompletion', $event === true)\r\n \"\r\n label=\"Wait for completion\"\r\n hint=\"When disabled, the parent continues after dispatch and backend records the child linkage.\"\r\n />\r\n </div>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['inputMappingJson'] ?? '{}')\"\r\n (focusin)=\"setExpressionTarget('config:inputMappingJson')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('inputMappingJson', $event)\r\n \"\r\n label=\"Input mapping JSON\"\r\n hint=\"JSON object or expression map sent into the child automation input.\"\r\n [required]=\"isConfigFieldRequired('inputMappingJson')\"\r\n rows=\"7\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['outputMappingJson'] ?? '{}')\"\r\n (focusin)=\"setExpressionTarget('config:outputMappingJson')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('outputMappingJson', $event)\r\n \"\r\n label=\"Output mapping JSON\"\r\n hint=\"JSON object or expression map for child output/status returned to the parent.\"\r\n [required]=\"isConfigFieldRequired('outputMappingJson')\"\r\n rows=\"7\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"Subworkflow\") {\r\n @if (sectionInMain(\"callAutomation\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Subworkflow</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['targetAutomationId'] ?? ''\"\r\n (ngModelChange)=\"onSubworkflowTargetChange($event)\"\r\n label=\"Target automation\"\r\n hint=\"Backend-provided automation candidate. Tenant and recursion validation stay on the backend.\"\r\n [required]=\"isConfigFieldRequired('targetAutomationId')\"\r\n [options]=\"subworkflowAutomationOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"config()['revisionMode'] ?? 'Active'\"\r\n (ngModelChange)=\"onConfigFieldChange('revisionMode', $event)\"\r\n label=\"Revision mode\"\r\n hint=\"Choose which published or active child revision to call.\"\r\n [required]=\"isConfigFieldRequired('revisionMode')\"\r\n [options]=\"revisionModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (config()[\"revisionMode\"] === \"SpecificRevision\") {\r\n <mt-text-field\r\n [ngModel]=\"config()['specificRevisionId'] ?? ''\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('specificRevisionId', $event)\r\n \"\r\n label=\"Specific revision id\"\r\n hint=\"Required only when revision mode is Specific revision.\"\r\n [required]=\"isConfigFieldRequired('specificRevisionId')\"\r\n />\r\n }\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['waitForCompletion'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('waitForCompletion', $event === true)\r\n \"\r\n label=\"Wait for completion\"\r\n hint=\"When disabled, the parent continues after dispatch and backend records the child linkage.\"\r\n />\r\n </div>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['inputMappingJson'] ?? '{}')\"\r\n (focusin)=\"setExpressionTarget('config:inputMappingJson')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('inputMappingJson', $event)\r\n \"\r\n label=\"Input mapping JSON\"\r\n hint=\"JSON object or expression map sent into the child automation input.\"\r\n [required]=\"isConfigFieldRequired('inputMappingJson')\"\r\n rows=\"7\"\r\n />\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['outputMappingJson'] ?? '{}')\"\r\n (focusin)=\"setExpressionTarget('config:outputMappingJson')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('outputMappingJson', $event)\r\n \"\r\n label=\"Output mapping JSON\"\r\n hint=\"JSON object or expression map for child output/status returned to the parent.\"\r\n [required]=\"isConfigFieldRequired('outputMappingJson')\"\r\n rows=\"7\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"ParallelStart\") {\r\n @if (sectionInMain(\"parallelStart\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div\r\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-50 px-4 py-3\"\r\n >\r\n <div class=\"fp-ae-section-title !m-0\">Parallel start</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add branch\"\r\n (onClick)=\"addParallelBranch()\"\r\n />\r\n </div>\r\n <div class=\"space-y-3 p-4\">\r\n @for (\r\n branch of parallelBranches();\r\n track branch.key;\r\n let i = $index\r\n ) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div class=\"grid gap-3 md:grid-cols-[160px_1fr_1fr_auto]\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"branch.key\"\r\n (ngModelChange)=\"updateParallelBranch(i, 'key', $event)\"\r\n label=\"Branch key\"\r\n hint=\"Persisted route key. Do not use array index names.\"\r\n [required]=\"isConfigFieldRequired('branches')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"branch.label\"\r\n (ngModelChange)=\"updateParallelBranch(i, 'label', $event)\"\r\n label=\"Label\"\r\n hint=\"Visual branch label. Changing it does not change existing routes.\"\r\n [required]=\"isConfigFieldRequired('branches')\"\r\n />\r\n <mt-text-field\r\n [ngModel]=\"branch.description\"\r\n (ngModelChange)=\"\r\n updateParallelBranch(i, 'description', $event)\r\n \"\r\n label=\"Description\"\r\n hint=\"Optional branch note for reviewers.\"\r\n [required]=\"false\"\r\n />\r\n <div class=\"flex items-end gap-1\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"arrow.arrow-up\"\r\n tooltip=\"Move up\"\r\n (onClick)=\"moveParallelBranch(i, -1)\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"arrow.arrow-down\"\r\n tooltip=\"Move down\"\r\n (onClick)=\"moveParallelBranch(i, 1)\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n icon=\"general.trash-01\"\r\n tooltip=\"Remove branch\"\r\n (onClick)=\"removeParallelBranch(i)\"\r\n />\r\n </div>\r\n </div>\r\n <div\r\n class=\"mt-3 flex flex-wrap items-center gap-1.5 text-[11px]\"\r\n >\r\n <span\r\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-50 px-2 font-mono font-semibold text-(--p-text-color)\"\r\n >{{ branch.key }}</span\r\n >\r\n <span\r\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 font-medium text-(--p-text-muted-color)\"\r\n >{{ branch.routeCount }} connected route{{\r\n branch.routeCount === 1 ? \"\" : \"s\"\r\n }}</span\r\n >\r\n </div>\r\n </div>\r\n }\r\n @if (parallelBranches().length === 0) {\r\n <p class=\"fp-ae-copy\">\r\n Add at least two stable branch keys. Backend validation blocks\r\n publish until branches are valid.\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"ParallelJoin\") {\r\n @if (sectionInMain(\"parallelJoin\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Parallel join</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-select-field\r\n [ngModel]=\"config()['joinPolicy'] ?? 'All'\"\r\n (ngModelChange)=\"onConfigFieldChange('joinPolicy', $event)\"\r\n label=\"Join policy\"\r\n hint=\"Backend policy used to decide when branch wait is complete.\"\r\n [required]=\"isConfigFieldRequired('joinPolicy')\"\r\n [options]=\"joinPolicyOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n @if (config()[\"joinPolicy\"] === \"Threshold\") {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(config()['threshold'])\"\r\n (ngModelChange)=\"onConfigFieldChange('threshold', $event)\"\r\n label=\"Threshold\"\r\n hint=\"Minimum completed branch count required for Threshold policy.\"\r\n [required]=\"isConfigFieldRequired('threshold')\"\r\n [min]=\"1\"\r\n />\r\n }\r\n <mt-select-field\r\n [ngModel]=\"config()['aggregationStrategy'] ?? 'MergeObjects'\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('aggregationStrategy', $event)\r\n \"\r\n label=\"Aggregation strategy\"\r\n hint=\"How backend joins branch outputs into the join node output.\"\r\n [required]=\"isConfigFieldRequired('aggregationStrategy')\"\r\n [options]=\"aggregationStrategyOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['outputTargetPath'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:outputTargetPath')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('outputTargetPath', $event)\r\n \"\r\n label=\"Output target path\"\r\n hint=\"Optional context path where joined output should be written.\"\r\n [required]=\"isConfigFieldRequired('outputTargetPath')\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"Switch\") {\r\n @if (sectionInMain(\"switch\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Switch</div>\r\n <div class=\"grid gap-4\">\r\n <div\r\n class=\"grid items-end gap-3 md:grid-cols-[minmax(0,1fr)_minmax(280px,0.7fr)]\"\r\n >\r\n <mt-select-field\r\n [ngModel]=\"config()['mode'] ?? 'value'\"\r\n (ngModelChange)=\"onConfigFieldChange('mode', $event)\"\r\n label=\"Mode\"\r\n hint=\"Backend evaluation mode: expression, rules, or source-value matching.\"\r\n [required]=\"isConfigFieldRequired('mode')\"\r\n [options]=\"switchModeOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n <mt-toggle-field\r\n class=\"self-end pb-1\"\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['firstMatch'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('firstMatch', $event === true)\r\n \"\r\n label=\"First match\"\r\n hint=\"Use the first matching case unless backend explicitly supports multi-match.\"\r\n />\r\n </div>\r\n @if (showSwitchSourceValue() || showSwitchExpression()) {\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n @if (showSwitchSourceValue()) {\r\n <mt-text-field\r\n [class]=\"\r\n showSwitchExpression()\r\n ? 'font-mono'\r\n : 'font-mono md:col-span-2'\r\n \"\r\n [ngModel]=\"config()['sourceValue'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:sourceValue')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('sourceValue', $event)\r\n \"\r\n label=\"Source value\"\r\n hint=\"Value or expression used for value matching.\"\r\n [required]=\"isConfigFieldRequired('sourceValue')\"\r\n />\r\n }\r\n @if (showSwitchExpression()) {\r\n <mt-text-field\r\n [class]=\"\r\n showSwitchSourceValue()\r\n ? 'font-mono'\r\n : 'font-mono md:col-span-2'\r\n \"\r\n [ngModel]=\"config()['expression'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:expression')\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('expression', $event)\r\n \"\r\n label=\"Expression\"\r\n hint=\"Expression evaluated when mode is expression or rules.\"\r\n [required]=\"isConfigFieldRequired('expression')\"\r\n />\r\n }\r\n </div>\r\n }\r\n <div\r\n class=\"grid items-end gap-3 md:grid-cols-[minmax(0,1fr)_minmax(280px,0.7fr)]\"\r\n >\r\n @if (showSwitchDefaultOutputKey()) {\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"config()['defaultOutputKey'] ?? 'default'\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('defaultOutputKey', $event)\r\n \"\r\n label=\"Default output key\"\r\n hint=\"Stable default route key used when no case matches.\"\r\n [required]=\"isConfigFieldRequired('defaultOutputKey')\"\r\n />\r\n }\r\n <mt-toggle-field\r\n [class]=\"\r\n showSwitchDefaultOutputKey()\r\n ? 'self-end pb-1'\r\n : 'self-end pb-1 md:col-span-2'\r\n \"\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"config()['includeDefaultOutput'] !== false\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange('includeDefaultOutput', $event === true)\r\n \"\r\n label=\"Include default output\"\r\n hint=\"Expose the default route output key in the canvas.\"\r\n />\r\n </div>\r\n </div>\r\n </section>\r\n <section class=\"fp-ae-panel\">\r\n <div\r\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-50 px-4 py-3\"\r\n >\r\n <div class=\"fp-ae-section-title !m-0\">Switch cases</div>\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"general.plus\"\r\n label=\"Add case\"\r\n (onClick)=\"addSwitchCase()\"\r\n />\r\n </div>\r\n <div class=\"space-y-3 p-4\">\r\n @for (item of switchCases(); track item.key; let i = $index) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-0 p-3\"\r\n >\r\n <div\r\n class=\"flex flex-wrap items-start justify-between gap-3 border-b border-surface-100 pb-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <div class=\"flex flex-wrap items-center gap-1.5\">\r\n <span\r\n class=\"text-sm font-semibold text-(--p-text-color)\"\r\n >Case {{ i + 1 }}</span\r\n >\r\n <span\r\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-50 px-2 font-mono text-[11px] font-semibold text-(--p-text-color)\"\r\n >{{ item.routeKey }}</span\r\n >\r\n <span\r\n class=\"inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >{{ item.routeCount }} connected route{{\r\n item.routeCount === 1 ? \"\" : \"s\"\r\n }}</span\r\n >\r\n </div>\r\n <div\r\n class=\"mt-1 truncate text-xs font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ item.label || item.routeKey }}\r\n </div>\r\n </div>\r\n <div class=\"flex shrink-0 items-center gap-1\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"arrow.arrow-up\"\r\n tooltip=\"Move up\"\r\n (onClick)=\"moveSwitchCase(i, -1)\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n icon=\"arrow.arrow-down\"\r\n tooltip=\"Move down\"\r\n (onClick)=\"moveSwitchCase(i, 1)\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n icon=\"general.trash-01\"\r\n tooltip=\"Remove case\"\r\n (onClick)=\"removeSwitchCase(i)\"\r\n />\r\n </div>\r\n </div>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"item.label\"\r\n (ngModelChange)=\"updateSwitchCase(i, 'label', $event)\"\r\n label=\"Case name\"\r\n hint=\"Shown in route labels.\"\r\n [required]=\"isConfigFieldRequired('cases')\"\r\n />\r\n @if (showSwitchCaseValue()) {\r\n <mt-text-field\r\n [ngModel]=\"item.value\"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:cases:' + item.key + ':value'\r\n )\r\n \"\r\n (ngModelChange)=\"updateSwitchCase(i, 'value', $event)\"\r\n label=\"Match value\"\r\n hint=\"Compared with the source value.\"\r\n [required]=\"isConfigFieldRequired('cases')\"\r\n />\r\n }\r\n @if (showSwitchCaseCondition()) {\r\n <mt-text-field\r\n class=\"font-mono md:col-span-2\"\r\n [ngModel]=\"item.condition\"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:cases:' + item.key + ':condition'\r\n )\r\n \"\r\n (ngModelChange)=\"\r\n updateSwitchCase(i, 'condition', $event)\r\n \"\r\n label=\"Rule condition\"\r\n hint=\"When true, this case is selected.\"\r\n [required]=\"isConfigFieldRequired('cases')\"\r\n />\r\n }\r\n </div>\r\n <details\r\n class=\"mt-3 rounded-md border border-surface-200 bg-surface-50 px-3 py-2 text-xs text-(--p-text-muted-color)\"\r\n >\r\n <summary class=\"cursor-pointer font-medium\">\r\n Advanced route settings\r\n </summary>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"item.key\"\r\n (ngModelChange)=\"updateSwitchCase(i, 'key', $event)\"\r\n label=\"Route key\"\r\n hint=\"Stable output key for connected routes.\"\r\n [required]=\"isConfigFieldRequired('cases')\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"item.expression\"\r\n (focusin)=\"\r\n setExpressionTarget(\r\n 'config:cases:' + item.key + ':expression'\r\n )\r\n \"\r\n (ngModelChange)=\"\r\n updateSwitchCase(i, 'expression', $event)\r\n \"\r\n label=\"Advanced condition expression\"\r\n hint=\"Optional true or false expression for this case.\"\r\n [required]=\"false\"\r\n />\r\n </div>\r\n <div\r\n class=\"mt-3 inline-flex h-7 items-center rounded-md border border-surface-200 bg-surface-0 px-2 font-medium\"\r\n >\r\n Visual order {{ i + 1 }}\r\n </div>\r\n </details>\r\n </div>\r\n }\r\n @if (switchCases().length === 0) {\r\n <p class=\"fp-ae-copy\">\r\n Add a case for each route this switch can select.\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n }\r\n }\r\n @case (\"Stop\") {\r\n @if (sectionInMain(\"stop\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">End execution</div>\r\n @if (\r\n supportsConfigKey(\"status\") ||\r\n supportsConfigKey(\"message\") ||\r\n supportsConfigKey(\"output\")\r\n ) {\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n @if (supportsConfigKey(\"status\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['status'] ?? ''\"\r\n (ngModelChange)=\"onConfigFieldChange('status', $event)\"\r\n label=\"Result status\"\r\n hint=\"Terminal status emitted by the backend stop node.\"\r\n />\r\n }\r\n @if (supportsConfigKey(\"message\")) {\r\n <mt-text-field\r\n [ngModel]=\"config()['message'] ?? ''\"\r\n (focusin)=\"setExpressionTarget('config:message')\"\r\n (ngModelChange)=\"onConfigFieldChange('message', $event)\"\r\n label=\"Message\"\r\n hint=\"Optional message or reason saved with the terminal result.\"\r\n />\r\n }\r\n @if (supportsConfigKey(\"output\")) {\r\n <mt-textarea-field\r\n class=\"w-full font-mono md:col-span-2\"\r\n [ngModel]=\"valueText(config()['output'] ?? '')\"\r\n (focusin)=\"setExpressionTarget('config:output')\"\r\n (ngModelChange)=\"onConfigFieldChange('output', $event)\"\r\n label=\"Output payload\"\r\n hint=\"Terminal output payload when exposed by the backend schema.\"\r\n rows=\"5\"\r\n />\r\n }\r\n </div>\r\n } @else {\r\n <p class=\"fp-ae-copy\">\r\n Stop nodes use their backend defaults and do not expose extra\r\n configuration.\r\n </p>\r\n }\r\n </section>\r\n }\r\n }\r\n }\r\n\r\n @if (sectionInAdvanced(\"policy\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">\r\n Retry, timeout, and failure handling\r\n </div>\r\n <div class=\"grid gap-3 md:grid-cols-3\">\r\n <mt-number-field\r\n [ngModel]=\"policyNumberValue('timeoutPolicyJson', 'timeoutSeconds')\"\r\n (ngModelChange)=\"\r\n updatePolicyNumber('timeoutPolicyJson', 'timeoutSeconds', $event)\r\n \"\r\n label=\"Step timeout (seconds)\"\r\n hint=\"How long this step may run before Automation Engine marks it timed out.\"\r\n [min]=\"1\"\r\n />\r\n <mt-number-field\r\n [ngModel]=\"policyNumberValue('retryPolicyJson', 'maxAttempts')\"\r\n (ngModelChange)=\"\r\n updatePolicyNumber('retryPolicyJson', 'maxAttempts', $event)\r\n \"\r\n label=\"Max attempts\"\r\n hint=\"Total tries including the first run. Use 1 for no retry.\"\r\n [min]=\"1\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"errorPolicyMode()\"\r\n (ngModelChange)=\"updateErrorPolicyMode($event)\"\r\n label=\"When this step fails\"\r\n hint=\"Choose whether failures pause for recovery, use the failure path, or fail the workflow.\"\r\n [options]=\"errorPolicyOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n @if (sectionHasAdvancedEmptyState(\"policy\")) {\r\n <p class=\"fp-ae-copy mt-3\">\r\n Backend defaults apply until you override a timeout, retry count, or\r\n failure rule here.\r\n </p>\r\n }\r\n </section>\r\n }\r\n\r\n @if (sectionInAdvanced(\"credentials\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Auth and credentials</div>\r\n <ng-container *ngTemplateOutlet=\"credentialSelector\" />\r\n </section>\r\n }\r\n\r\n @if (sectionInAdvanced(\"mapping\")) {\r\n <section class=\"fp-ae-panel\">\r\n <div class=\"fp-ae-section-title\">Custom data handoff</div>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <div>\r\n <div class=\"fp-ae-label mb-2\">Inputs sent to this step</div>\r\n @for (row of inputMappingRows(); track row.key) {\r\n <label class=\"mb-2 block space-y-1\">\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">{{\r\n row.key\r\n }}</span>\r\n <mt-text-field\r\n [ngModel]=\"valueText(row.value)\"\r\n (focusin)=\"\r\n setExpressionTarget('config:inputMapping:' + row.key)\r\n \"\r\n (ngModelChange)=\"\r\n updateJsonField('inputMappingJson', row.key, $event)\r\n \"\r\n hint=\"Value this step receives from the workflow context.\"\r\n />\r\n </label>\r\n }\r\n </div>\r\n <div>\r\n <div class=\"fp-ae-label mb-2\">Outputs saved for next steps</div>\r\n @for (row of outputMappingRows(); track row.key) {\r\n <label class=\"mb-2 block space-y-1\">\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">{{\r\n row.key\r\n }}</span>\r\n <mt-text-field\r\n [ngModel]=\"valueText(row.value)\"\r\n (focusin)=\"\r\n setExpressionTarget('config:outputMapping:' + row.key)\r\n \"\r\n (ngModelChange)=\"\r\n updateJsonField('outputMappingJson', row.key, $event)\r\n \"\r\n hint=\"Value this step exposes to the rest of the workflow.\"\r\n />\r\n </label>\r\n }\r\n </div>\r\n </div>\r\n @if (\r\n sectionHasAdvancedEmptyState(\"mapping\") &&\r\n inputMappingRows().length === 0 &&\r\n outputMappingRows().length === 0\r\n ) {\r\n <p class=\"fp-ae-copy mt-3\">\r\n Default handoff is active. This step receives the current workflow\r\n item and exposes its normal output to the next connected step. Add a\r\n custom handoff only when the business data must be reshaped.\r\n </p>\r\n }\r\n </section>\r\n }\r\n\r\n @if (sectionInAdvanced(\"triggerContext\") && trigger()) {\r\n <details class=\"fp-ae-panel\" open>\r\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\r\n Trigger payload and context\r\n </summary>\r\n <p class=\"fp-ae-copy mt-3\">\r\n Trigger payload/context is documentation for expressions only.\r\n Triggers do not edit node input or output mappings here.\r\n </p>\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\r\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\r\n Config schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(configSchema()) }}</pre>\r\n </details>\r\n @if (hasTriggerPayloadSchema() && triggerPayloadSchema(); as schema) {\r\n <details\r\n class=\"rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Payload schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(schema) }}</pre>\r\n </details>\r\n }\r\n @if (authPolicySchema()) {\r\n <details\r\n class=\"rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Auth policy schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(authPolicySchema()) }}</pre>\r\n </details>\r\n }\r\n @if (triggerPayloadSample(); as sample) {\r\n <details\r\n class=\"rounded-lg border border-(--p-content-border-color)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\"\r\n >\r\n Payload or request sample\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(sample) }}</pre>\r\n </details>\r\n }\r\n </div>\r\n </details>\r\n }\r\n\r\n @if (sectionInAdvanced(\"backendSchema\") && step()) {\r\n <details class=\"fp-ae-panel\" open>\r\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\r\n Backend schemas and outputs\r\n </summary>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\r\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\r\n Config schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(configSchema()) }}</pre>\r\n </details>\r\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\r\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\r\n Input schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(inputSchema()) }}</pre>\r\n </details>\r\n <details class=\"rounded-lg border border-(--p-content-border-color)\">\r\n <summary class=\"cursor-pointer px-3 py-2 text-[12px] font-semibold\">\r\n Output schema\r\n </summary>\r\n <pre class=\"fp-ae-code\">{{ schemaText(outputSchema()) }}</pre>\r\n </details>\r\n <div class=\"rounded-lg border border-(--p-content-border-color) p-3\">\r\n <div class=\"fp-ae-label mb-2\">Route output keys</div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (key of routeOutputKeys(); track key) {\r\n <span\r\n class=\"rounded-md bg-(--p-surface-100) px-2 py-1 font-mono text-[11px]\"\r\n >{{ key }}</span\r\n >\r\n }\r\n @if (routeOutputKeys().length === 0) {\r\n <span class=\"text-[12px] text-(--p-text-muted-color)\"\r\n >No outgoing route keys</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </details>\r\n }\r\n\r\n @if (sectionInAdvanced(\"schemaFields\") && configRows().length > 0) {\r\n <details class=\"fp-ae-panel\" open>\r\n <summary class=\"fp-ae-section-title cursor-pointer select-none\">\r\n Backend schema fields\r\n </summary>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n @for (field of configRows(); track field.key) {\r\n <div\r\n class=\"space-y-1.5\"\r\n [class.md:col-span-2]=\"\r\n field.type === 'object' || field.type === 'array'\r\n \"\r\n >\r\n @if (field.enumValues.length) {\r\n <mt-select-field\r\n [ngModel]=\"fieldValue(field.key)\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n [options]=\"enumOptions(field.enumValues)\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n } @else if (field.type === \"boolean\") {\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"!!fieldValue(field.key)\"\r\n (ngModelChange)=\"\r\n onConfigFieldChange(field.key, $event === true)\r\n \"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n } @else if (field.type === \"number\") {\r\n <mt-number-field\r\n [ngModel]=\"numberValue(fieldValue(field.key))\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n } @else if (field.type === \"date\") {\r\n <mt-date-field\r\n [ngModel]=\"fieldValue(field.key)\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n [showTime]=\"field.format !== 'date'\"\r\n [showClear]=\"true\"\r\n [pInputs]=\"dateTimePickerInputs\"\r\n />\r\n } @else if (field.type === \"object\" || field.type === \"array\") {\r\n <mt-textarea-field\r\n class=\"w-full font-mono\"\r\n [ngModel]=\"valueText(fieldValue(field.key))\"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n rows=\"5\"\r\n />\r\n } @else {\r\n <mt-text-field\r\n [ngModel]=\"fieldText(field.key)\"\r\n (focusin)=\"\r\n field.expressionEnabled &&\r\n setExpressionTarget('config:' + field.key)\r\n \"\r\n (ngModelChange)=\"onConfigFieldChange(field.key, $event)\"\r\n [label]=\"field.label\"\r\n [hint]=\"field.description ?? ''\"\r\n [required]=\"isConfigFieldRequired(field.key)\"\r\n />\r\n }\r\n </div>\r\n }\r\n </div>\r\n </details>\r\n }\r\n </div>\r\n</div>\r\n\r\n<ng-template #credentialSelector>\r\n @if (activeCredentialProvider(); as provider) {\r\n <div\r\n class=\"mb-4 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-4 shadow-[0_1px_2px_rgba(15,23,42,0.05)]\"\r\n >\r\n <div class=\"mb-2 text-[12px] font-bold text-(--p-text-color)\">\r\n Credential\r\n </div>\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n @if (provider.connectAvailable) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex min-h-10 max-w-full items-center gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2 text-[13px] font-bold text-(--p-text-color) shadow-sm transition-[border-color,box-shadow,background] hover:border-(--p-primary-color) hover:shadow-[0_2px_7px_rgba(15,23,42,0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n [disabled]=\"credentialOauthState() === 'loading'\"\r\n [attr.aria-busy]=\"\r\n credentialOauthState() === 'loading' ? 'true' : null\r\n \"\r\n (click)=\"connectCredentialProvider()\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[22px]\"\r\n [itemKey]=\"provider.providerKey\"\r\n [displayName]=\"provider.displayName\"\r\n [metadata]=\"credentialProviderIconMetadata(provider)\"\r\n />\r\n <span class=\"truncate\">{{ credentialConnectLabel() }}</span>\r\n </button>\r\n @if (provider.manualSetupAvailable) {\r\n <span class=\"text-[12px] text-(--p-text-muted-color)\">or</span>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-md px-1 py-1 text-[12px] font-semibold text-(--p-text-color) underline decoration-(--p-text-muted-color) underline-offset-4 transition-colors hover:text-(--p-primary-color) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n (click)=\"showManualCredentialSetup()\"\r\n >\r\n setup manually\r\n </button>\r\n }\r\n } @else if (provider.manualSetupAvailable) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex min-h-10 max-w-full items-center gap-2 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2 text-[13px] font-bold text-(--p-text-color) shadow-sm transition-[border-color,box-shadow,background] hover:border-(--p-primary-color) hover:shadow-[0_2px_7px_rgba(15,23,42,0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n (click)=\"showManualCredentialSetup()\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[22px]\"\r\n [itemKey]=\"provider.providerKey\"\r\n [displayName]=\"provider.displayName\"\r\n [metadata]=\"credentialProviderIconMetadata(provider)\"\r\n />\r\n <span class=\"truncate\"\r\n >Set up\r\n {{ provider.displayName || humanReviewProviderLabel() }}\r\n credential</span\r\n >\r\n </button>\r\n }\r\n </div>\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center gap-1.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n @if (provider.authModes?.length) {\r\n @for (mode of provider.authModes; track mode) {\r\n <span\r\n class=\"rounded-full bg-(--p-surface-100) px-2 py-0.5 font-semibold\"\r\n >\r\n {{ mode }}\r\n </span>\r\n }\r\n }\r\n @if (provider.requiredScopes?.length) {\r\n <span class=\"truncate\">\r\n Scopes: {{ provider.requiredScopes.join(\", \") }}\r\n </span>\r\n }\r\n </div>\r\n @if (provider.setupInstructions) {\r\n <p class=\"m-0 mt-2 text-[11.5px] leading-5 text-(--p-text-muted-color)\">\r\n {{ provider.setupInstructions }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (manualCredentialOpen()) {\r\n <div\r\n class=\"mb-3 rounded-md border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-text-field\r\n [ngModel]=\"manualCredentialDisplayName()\"\r\n (ngModelChange)=\"onManualCredentialDisplayNameChange($event)\"\r\n label=\"Credential name\"\r\n />\r\n <mt-select-field\r\n [ngModel]=\"manualCredentialType()\"\r\n (ngModelChange)=\"onManualCredentialTypeChange($event)\"\r\n label=\"Credential type\"\r\n [options]=\"credentialTypeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <div class=\"mt-3 grid gap-3 md:grid-cols-2\">\r\n @for (field of manualCredentialFields(); track field.key) {\r\n @if (manualCredentialFieldIsToggle(field)) {\r\n <mt-toggle-field\r\n [ngModel]=\"manualCredentialFieldValue(field.key) === 'true'\"\r\n (ngModelChange)=\"onManualCredentialFieldChange(field.key, $event)\"\r\n [label]=\"\r\n manualCredentialFieldLabel(field) + (field.required ? ' *' : '')\r\n \"\r\n size=\"small\"\r\n />\r\n } @else {\r\n <mt-text-field\r\n [ngModel]=\"manualCredentialFieldValue(field.key)\"\r\n (ngModelChange)=\"onManualCredentialFieldChange(field.key, $event)\"\r\n [label]=\"\r\n manualCredentialFieldLabel(field) + (field.required ? ' *' : '')\r\n \"\r\n [placeholder]=\"field.placeholder ?? ''\"\r\n [type]=\"manualCredentialFieldInputType(field)\"\r\n />\r\n }\r\n }\r\n </div>\r\n @if (credentialManualError(); as error) {\r\n <div\r\n class=\"mt-3 rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ error }}\r\n </div>\r\n }\r\n <div class=\"mt-3 flex justify-end gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n label=\"Cancel\"\r\n (onClick)=\"cancelManualCredentialSetup()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n label=\"Save credential\"\r\n [loading]=\"credentialManualState() === 'loading'\"\r\n [disabled]=\"credentialManualState() === 'loading'\"\r\n (onClick)=\"createManualCredential()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (credentialOptions().length) {\r\n <div class=\"space-y-2\">\r\n <mt-select-field\r\n [ngModel]=\"selectedCredentialRef()\"\r\n (ngModelChange)=\"selectCredential($event)\"\r\n label=\"Saved credential\"\r\n [options]=\"credentialOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n [showClear]=\"true\"\r\n />\r\n @for (credential of credentials(); track credential.credentialRef) {\r\n @if (credential.credentialRef === selectedCredentialRef()) {\r\n <div\r\n class=\"rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2\"\r\n >\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"min-w-0\">\r\n <div class=\"truncate text-[12px] font-semibold\">\r\n {{ credential.displayName ?? credential.credentialRef }}\r\n </div>\r\n <div\r\n class=\"truncate font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ credential.credentialRef }} /\r\n {{\r\n credential.status ??\r\n (credential.resolved ? \"Resolved\" : \"Unresolved\")\r\n }}\r\n </div>\r\n </div>\r\n <div class=\"flex flex-none items-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n label=\"Test\"\r\n [loading]=\"\r\n credentialTestingRef() === credential.credentialRef\r\n \"\r\n [disabled]=\"\r\n credentialTestingRef() === credential.credentialRef\r\n \"\r\n (onClick)=\"testCredential(credential.credentialRef)\"\r\n />\r\n @if (activeCredentialProvider()?.connectAvailable) {\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n label=\"Reconnect\"\r\n [disabled]=\"credentialOauthState() === 'loading'\"\r\n (onClick)=\"connectCredentialProvider()\"\r\n />\r\n }\r\n <mt-button\r\n size=\"small\"\r\n variant=\"text\"\r\n severity=\"danger\"\r\n label=\"Revoke\"\r\n [loading]=\"credentialRevokeRef() === credential.credentialRef\"\r\n [disabled]=\"\r\n credentialRevokeRef() === credential.credentialRef\r\n \"\r\n (onClick)=\"revokeCredential(credential.credentialRef)\"\r\n />\r\n </div>\r\n </div>\r\n @if (credential.maskedFields; as maskedFields) {\r\n <div\r\n class=\"mt-2 flex flex-wrap gap-1.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n @for (field of maskedFields | keyvalue; track field.key) {\r\n <span\r\n class=\"rounded-md bg-(--p-content-background) px-2 py-1 font-mono\"\r\n >\r\n {{ field.key }}: {{ field.value }}\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n } @else if (sectionHasAdvancedEmptyState(\"credentials\")) {\r\n <p class=\"fp-ae-copy\">\r\n No saved credential exists for this provider. Connect the provider or set\r\n it up manually before publishing.\r\n </p>\r\n }\r\n @if (credentialTest(); as test) {\r\n <div class=\"mt-3 rounded-md bg-(--p-surface-100) px-3 py-2 text-[12px]\">\r\n {{ test.status ?? (test.succeeded ? \"Succeeded\" : \"Failed\") }}\r\n @if (test.message) {\r\n <span>- {{ test.message }}</span>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n<ng-template\r\n #mapEditor\r\n let-objectKey=\"objectKey\"\r\n let-rows=\"rows\"\r\n let-keyLabel=\"keyLabel\"\r\n let-valueLabel=\"valueLabel\"\r\n let-addLabel=\"addLabel\"\r\n let-emptyText=\"emptyText\"\r\n let-keyHint=\"keyHint\"\r\n let-valueHint=\"valueHint\"\r\n let-required=\"required\"\r\n>\r\n <div class=\"space-y-2\">\r\n @for (row of rows; track row.key) {\r\n <div class=\"grid gap-2 md:grid-cols-[180px_1fr_auto]\">\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"row.key\"\r\n (ngModelChange)=\"\r\n updateObjectRow(objectKey, row.key, $event, row.value)\r\n \"\r\n [label]=\"keyLabel\"\r\n [hint]=\"\r\n keyHint || 'Configuration key saved to the backend JSON object.'\r\n \"\r\n [required]=\"required === true\"\r\n />\r\n <mt-text-field\r\n class=\"font-mono\"\r\n [ngModel]=\"valueText(row.value)\"\r\n (focusin)=\"setExpressionTarget('config:' + objectKey + ':' + row.key)\"\r\n (ngModelChange)=\"updateObjectRow(objectKey, row.key, row.key, $event)\"\r\n [label]=\"valueLabel\"\r\n [hint]=\"\r\n valueHint ||\r\n 'Configuration value. Use expressions when this field should resolve at runtime.'\r\n \"\r\n [required]=\"required === true\"\r\n />\r\n <mt-button\r\n class=\"self-end\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n label=\"Remove\"\r\n (onClick)=\"removeObjectRow(objectKey, row.key)\"\r\n />\r\n </div>\r\n } @empty {\r\n @if (emptyText) {\r\n <div\r\n class=\"rounded-md bg-(--p-content-background) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyText }}\r\n </div>\r\n }\r\n }\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n [label]=\"addLabel || 'Add ' + keyLabel\"\r\n (onClick)=\"addObjectRow(objectKey)\"\r\n />\r\n </div>\r\n</ng-template>\r\n" }]
|
|
20305
20308
|
}], ctorParameters: () => [], propDecorators: { step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }] } });
|
|
20306
20309
|
function schemaFieldsFrom$1(schema, locale) {
|
|
20307
20310
|
const raw = asRecord$4(schema);
|
|
@@ -22270,7 +22273,7 @@ class InspectorModalBodyComponent {
|
|
|
22270
22273
|
outputIsStale = computed(() => {
|
|
22271
22274
|
const step = this.selectedStep();
|
|
22272
22275
|
const run = this.selectedBuilderTestRun();
|
|
22273
|
-
return !!step && !!run && run.configFingerprint !== stepConfigFingerprint(step);
|
|
22276
|
+
return (!!step && !!run && run.configFingerprint !== stepConfigFingerprint(step));
|
|
22274
22277
|
}, ...(ngDevMode ? [{ debugName: "outputIsStale" }] : /* istanbul ignore next */ []));
|
|
22275
22278
|
outputData = computed(() => {
|
|
22276
22279
|
const builderOutput = this.selectedBuilderTestRun()?.result.outputJson;
|
|
@@ -23648,7 +23651,7 @@ class InspectorModalBodyComponent {
|
|
|
23648
23651
|
return (resolveTranslatable(trigger.name ?? null, this.transloco.getActiveLang()) || trigger.type);
|
|
23649
23652
|
}
|
|
23650
23653
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: InspectorModalBodyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
23651
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: InspectorModalBodyComponent, isStandalone: true, selector: "fp-inspector-modal-body", host: { classAttribute: "block h-full" }, viewQueries: [{ propertyName: "resizeHost", first: true, predicate: ["resizeHost"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- A connected node action sitting on the modal's outer edge.\r\n The same shared automation icon renderer used by canvas/picker is used\r\n here so modal-side node shortcuts keep one visual source of truth.\r\n `side` = 'start' for upstream nodes that feed in, 'end' for downstream\r\n nodes this step feeds. -->\r\n<ng-template #edgeTab let-n>\r\n <span\r\n class=\"fp-edge-tab pointer-events-auto\"\r\n [style.--fp-edge-tab-color]=\"n.color\"\r\n [style.--fp-edge-tab-bg]=\"n.bg\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"fp-edge-tab-button\"\r\n [attr.aria-label]=\"n.label\"\r\n [mtTooltip]=\"n.label\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n (click)=\"navigateToStep(n.stepId)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[39px]\"\r\n [type]=\"n.type\"\r\n [itemKey]=\"n.key\"\r\n [displayName]=\"n.label\"\r\n [metadata]=\"n.metadata\"\r\n />\r\n </button>\r\n </span>\r\n</ng-template>\r\n\r\n<!-- Type glyph badge for a context value (string / number / date / \u2026). -->\r\n<ng-template #typeBadge let-kind>\r\n <span\r\n class=\"flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-1.5 font-mono text-[12px] font-bold leading-none text-(--p-text-color) shadow-sm\"\r\n >\r\n @switch (kind) {\r\n @case (\"string\") {\r\n T\r\n }\r\n @case (\"number\") {\r\n #\r\n }\r\n @case (\"object\") {\r\n {}\r\n }\r\n @case (\"array\") {\r\n [ ]\r\n }\r\n @case (\"boolean\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2.4]\"\r\n >\r\n <rect x=\"3\" y=\"7\" width=\"18\" height=\"10\" rx=\"5\" />\r\n <circle cx=\"9\" cy=\"12\" r=\"2\" fill=\"currentColor\" stroke=\"none\" />\r\n </svg>\r\n }\r\n @case (\"date\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <rect x=\"4\" y=\"5\" width=\"16\" height=\"16\" rx=\"2.5\" />\r\n <path d=\"M4 9h16M8 3v4M16 3v4\" stroke-linecap=\"round\" />\r\n </svg>\r\n }\r\n @case (\"file\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <path\r\n d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z\"\r\n />\r\n <path d=\"M14 3v5h5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n </svg>\r\n }\r\n @case (\"secret\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <rect x=\"5\" y=\"11\" width=\"14\" height=\"9\" rx=\"2\" />\r\n <path d=\"M8 11V8a4 4 0 0 1 8 0v3\" stroke-linecap=\"round\" />\r\n </svg>\r\n }\r\n @case (\"reference\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <path\r\n d=\"M7 17 17 7M9 7h8v8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n }\r\n @default {\r\n \u2022\r\n }\r\n }\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #inputPickerTree let-nodes=\"nodes\" let-level=\"level\">\r\n <ul class=\"border-t border-(--p-content-border-color)\">\r\n @for (node of nodes; track node.id) {\r\n <li class=\"border-b border-(--p-content-border-color) last:border-b-0\">\r\n <div\r\n [fpDragData]=\"node.rawExpression\"\r\n [mtTooltip]=\"pickerNodeTooltip(node)\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n class=\"fp-data-row group flex cursor-grab items-center gap-1.5 px-2.5 py-2.5 transition duration-200 ease-out hover:bg-(--p-surface-50) active:cursor-grabbing\"\r\n [style.padding-inline-start.rem]=\"0.45 + level * 0.5\"\r\n >\r\n @if (node.children.length > 0) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isInputPickerNodeExpanded(node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n [tooltip]=\"\r\n isInputPickerNodeExpanded(node) ? 'Collapse' : 'Expand'\r\n \"\r\n styleClass=\"fp-modal-tree-toggle !h-6 !w-6\"\r\n (onClick)=\"toggleInputPickerNode(node)\"\r\n />\r\n } @else {\r\n <span class=\"h-6 w-6 shrink-0\"></span>\r\n }\r\n\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n <span\r\n class=\"min-w-0 truncate text-[14px] font-semibold leading-5 text-(--p-text-color)\"\r\n [title]=\"node.label\"\r\n >{{ node.label }}</span\r\n >\r\n @if (node.availability === \"masked\") {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-error))]/12 px-2 py-0.5 text-[10.5px] font-semibold text-[rgb(var(--fp-error))]\"\r\n >\r\n secret\r\n </span>\r\n }\r\n </div>\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [tooltip]=\"\r\n copiedPickerNodeId() === node.id ? 'Copied' : 'Copy expression'\r\n \"\r\n styleClass=\"!h-6 !w-6 shrink-0 opacity-0 group-hover:opacity-100 focus:opacity-100\"\r\n (onClick)=\"copyPickerNode(node, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isInputPickerNodeExpanded(node)) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n inputPickerTree;\r\n context: { nodes: node.children, level: level + 1 }\r\n \"\r\n />\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<ng-template #outputPickerTree let-nodes=\"nodes\" let-level=\"level\">\r\n <ul class=\"space-y-1.5\">\r\n @for (node of nodes; track node.id) {\r\n <li>\r\n <div\r\n [mtTooltip]=\"outputNodeTooltip(node)\"\r\n tooltipPosition=\"left\"\r\n appendTo=\"body\"\r\n class=\"group flex min-w-0 items-center gap-1.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-2.5 py-2.5 shadow-sm transition duration-200 hover:bg-(--p-surface-50)\"\r\n [style.margin-inline-start.rem]=\"level * 0.55\"\r\n >\r\n @if (node.children.length > 0) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isOutputPickerNodeExpanded(node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n [tooltip]=\"\r\n isOutputPickerNodeExpanded(node) ? 'Collapse' : 'Expand'\r\n \"\r\n styleClass=\"fp-modal-tree-toggle !h-6 !w-6\"\r\n (onClick)=\"toggleOutputPickerNode(node)\"\r\n />\r\n } @else {\r\n <span class=\"h-6 w-6 shrink-0\"></span>\r\n }\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n typeBadge;\r\n context: { $implicit: fieldKind(node.type) }\r\n \"\r\n />\r\n\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-[13.5px] font-semibold leading-5 text-(--p-text-color)\"\r\n [title]=\"node.label\"\r\n >\r\n {{ node.label }}\r\n </span>\r\n @if (outputNodePreview(node)) {\r\n <span\r\n class=\"min-w-0 max-w-[45%] truncate font-mono text-[12px] text-(--p-text-muted-color)\"\r\n [title]=\"outputNodePreview(node)\"\r\n >\r\n {{ outputNodePreview(node) }}\r\n </span>\r\n }\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [tooltip]=\"\r\n copiedPickerNodeId() === node.id ? 'Copied' : 'Copy expression'\r\n \"\r\n styleClass=\"!h-6 !w-6 shrink-0 opacity-0 group-hover:opacity-100 focus:opacity-100\"\r\n (onClick)=\"copyPickerNode(node, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isOutputPickerNodeExpanded(node)) {\r\n <div class=\"mt-1.5\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n outputPickerTree;\r\n context: { nodes: node.children, level: level + 1 }\r\n \"\r\n />\r\n </div>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<!-- Outer wrapper: positioning context only (overflow visible) so the connected-\r\n node avatars can straddle the modal border. The inner card clips its own\r\n rounded corners + carries the shadow. -->\r\n<div class=\"relative h-full w-full text-(--p-text-color)\">\r\n <div\r\n class=\"flex h-full min-h-0 flex-col overflow-hidden rounded-2xl bg-(--p-content-background) shadow-2xl shadow-slate-950/25\"\r\n >\r\n <!-- ============================ HEADER ============================ -->\r\n <header\r\n class=\"flex shrink-0 items-center gap-3.5 border-b border-(--p-content-border-color) px-6 py-4\"\r\n >\r\n @if (currentChip(); as chip) {\r\n <span\r\n class=\"grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-sm ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"chip.color\"\r\n [style.--fp-avatar-bg]=\"chip.bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n [type]=\"chip.type\"\r\n [itemKey]=\"chip.key\"\r\n [displayName]=\"chip.label\"\r\n [metadata]=\"chip.metadata\"\r\n />\r\n </span>\r\n } @else {\r\n <span\r\n class=\"grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color) shadow-sm ring-1 ring-(--p-content-border-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [visualKey]=\"\r\n selectionKind() === 'connection'\r\n ? 'state:connection'\r\n : 'state:settings'\r\n \"\r\n />\r\n </span>\r\n }\r\n\r\n <div class=\"min-w-0 flex-1\">\r\n <h2\r\n class=\"truncate text-[17px] font-semibold tracking-tight text-(--p-text-color)\"\r\n >\r\n {{ activeDescriptorLabel() }}\r\n </h2>\r\n <p\r\n class=\"m-0 mt-1 line-clamp-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ activeDescriptorSubtitle() }}\r\n </p>\r\n </div>\r\n\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'flowplus.inspector.modal.close' | transloco\"\r\n (onClick)=\"close()\"\r\n />\r\n </header>\r\n\r\n <!-- ===================== BODY: INPUT | CONFIG | OUTPUT ===================== -->\r\n <div\r\n #resizeHost\r\n class=\"relative flex min-h-0 flex-1\"\r\n [class.select-none]=\"resizing()\"\r\n [style.cursor]=\"resizing() ? 'col-resize' : null\"\r\n >\r\n <!-- Navigation loading beat \u2014 a slim primary progress sweep across the top\r\n while the modal re-derives for a newly selected (connected) step. -->\r\n @if (navigating()) {\r\n <div\r\n class=\"pointer-events-none absolute inset-x-0 top-0 z-40 h-[3px] overflow-hidden\"\r\n >\r\n <div\r\n class=\"h-full w-2/5 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_0.9s_ease-in-out_infinite]\"\r\n ></div>\r\n </div>\r\n }\r\n\r\n @if (selectionKind() !== \"trigger\") {\r\n <!-- --------------------------- INPUT --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 shrink-0 flex-col bg-(--p-surface-50)\"\r\n [style.width.px]=\"leftWidth()\"\r\n >\r\n <div\r\n class=\"shrink-0 border-b border-(--p-content-border-color) px-5 py-4\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p\r\n class=\"text-[15px] font-bold uppercase tracking-[0.04em] text-(--p-text-color)\"\r\n >\r\n {{\r\n selectionKind() === \"trigger\"\r\n ? \"Trigger payload\"\r\n : (\"flowplus.inspector.modal.inputsTitle\" | transloco)\r\n }}\r\n </p>\r\n @if (selectionKind() === \"step\") {\r\n <div class=\"flex items-center gap-1.5\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.search-md\"\r\n [tooltip]=\"'flowplus.context.pickerPlaceholder' | transloco\"\r\n [styleClass]=\"\r\n contextSearchOpen()\r\n ? 'fp-modal-icon-button is-active'\r\n : 'fp-modal-icon-button'\r\n \"\r\n (onClick)=\"toggleContextSearch()\"\r\n />\r\n <div\r\n class=\"inline-flex rounded-lg bg-(--p-surface-100) p-0.5 text-[11px] font-semibold\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.viewSchema' | transloco\r\n \"\r\n [styleClass]=\"\r\n inputView() === 'schema'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"inputView.set('schema')\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.viewJson' | transloco\"\r\n [styleClass]=\"\r\n inputView() === 'json'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"inputView.set('json')\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (selectionKind() === \"step\" && contextSearchOpen()) {\r\n <div class=\"mt-3\">\r\n <mt-text-field\r\n [ngModel]=\"contextSearch()\"\r\n (ngModelChange)=\"contextSearch.set($event)\"\r\n [placeholder]=\"\r\n 'flowplus.context.pickerPlaceholder' | transloco\r\n \"\r\n icon=\"general.search-md\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-y-auto px-5 py-4\">\r\n @if (selectionKind() === \"step\") {\r\n @if (inputView() === \"schema\") {\r\n <div class=\"space-y-3\">\r\n <section class=\"space-y-2.5\">\r\n @if (filteredInputPickerGroups().length > 0) {\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <h3\r\n class=\"flex items-center gap-2 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.availableData\" | transloco\r\n }}\r\n </h3>\r\n </div>\r\n }\r\n\r\n @if (filteredInputPickerGroups().length === 0) {\r\n <div\r\n class=\"flex min-h-[18rem] flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <span\r\n class=\"grid h-14 w-14 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[42px]\"\r\n visualKey=\"state:input-empty\"\r\n />\r\n </span>\r\n <div class=\"space-y-1\">\r\n <p\r\n class=\"text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.noInputData\" | transloco\r\n }}\r\n </p>\r\n <p\r\n class=\"mx-auto max-w-[16rem] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.noAvailableData\"\r\n | transloco\r\n }}\r\n </p>\r\n </div>\r\n </div>\r\n } @else {\r\n <p\r\n class=\"flex items-center gap-1.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"h-3.5 w-3.5 shrink-0 fill-current opacity-70\"\r\n aria-hidden=\"true\"\r\n >\r\n <circle cx=\"5\" cy=\"4\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"4\" r=\"1.1\" />\r\n <circle cx=\"5\" cy=\"8\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"8\" r=\"1.1\" />\r\n <circle cx=\"5\" cy=\"12\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"12\" r=\"1.1\" />\r\n </svg>\r\n {{ \"flowplus.inspector.modal.dragHint\" | transloco }}\r\n </p>\r\n\r\n <div class=\"space-y-1.5\">\r\n @for (\r\n group of filteredInputPickerGroups();\r\n track group.id\r\n ) {\r\n <section\r\n class=\"overflow-hidden rounded-xl border border-(--p-content-border-color) bg-(--p-content-background)\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isGroupCollapsed(group.id)\r\n ? 'arrow.chevron-right'\r\n : 'arrow.chevron-down'\r\n \"\r\n [label]=\"group.label\"\r\n styleClass=\"fp-modal-group-toggle\"\r\n (onClick)=\"toggleGroup(group.id)\"\r\n />\r\n\r\n @if (!isGroupCollapsed(group.id)) {\r\n @if (group.children.length === 0) {\r\n <div\r\n class=\"border-t border-(--p-content-border-color) px-3 py-3 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyPickerGroupMessage(group.id) }}\r\n </div>\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n inputPickerTree;\r\n context: { nodes: group.children, level: 0 }\r\n \"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n } @else {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg border border-(--p-content-border-color) bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ inputJson() }}</pre\r\n >\r\n }\r\n } @else if (\r\n selectionKind() === \"connection\" && connectionEndpoints();\r\n as endpoints\r\n ) {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.connectionSummary\" | transloco }}\r\n </h3>\r\n <div\r\n class=\"space-y-2.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n <div>\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.source\" | transloco }}\r\n </div>\r\n <div class=\"mt-1 text-[13px] text-(--p-text-color)\">\r\n {{ endpoints.source }}\r\n </div>\r\n </div>\r\n <div>\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.target\" | transloco }}\r\n </div>\r\n <div class=\"mt-1 text-[13px] text-(--p-text-color)\">\r\n {{ endpoints.target }}\r\n </div>\r\n </div>\r\n @if (selectedConnection()!.sourcePortKey) {\r\n <div\r\n class=\"font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ selectedConnection()!.sourcePortKey }} ->\r\n {{ selectedConnection()!.targetPortKey || \"in\" }}\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n } @else if (selectionKind() === \"trigger\") {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n Trigger payload and setup\r\n </h3>\r\n <div\r\n class=\"space-y-2.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n @if (selectedTriggerPayloadSchemaJson()) {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Payload schema\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerPayloadSchemaJson() }}</pre\r\n >\r\n </div>\r\n } @else {\r\n <p\r\n class=\"m-0 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n The backend did not return a payload schema for this\r\n trigger.\r\n </p>\r\n }\r\n @if (selectedTriggerPayloadSampleJson()) {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Sample payload\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerPayloadSampleJson() }}</pre\r\n >\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"WebhookTrigger\") {\r\n <div\r\n class=\"space-y-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n <div>\r\n Expected method:\r\n {{ selectedTrigger()?.expectedHttpMethod ?? \"POST\" }}\r\n </div>\r\n <div>\r\n Content types:\r\n {{\r\n (selectedTrigger()?.allowedContentTypes ?? []).join(\r\n \", \"\r\n ) || \"Backend default\"\r\n }}\r\n </div>\r\n @if (selectedTrigger()?.exampleCurl) {\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-28 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTrigger()?.exampleCurl }}</pre\r\n >\r\n }\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"FormSubmitTrigger\") {\r\n <div\r\n class=\"rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n Pinned formVersionId:\r\n <code>{{\r\n selectedTriggerFormVersionId() ?? \"Not selected\"\r\n }}</code>\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"ScheduleTrigger\") {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Schedule config\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerConfigJson() }}</pre\r\n >\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (selectedTriggerIssues().length > 0) {\r\n <div class=\"space-y-1.5\">\r\n @for (issue of selectedTriggerIssues(); track $index) {\r\n <div\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] leading-5 text-amber-800\"\r\n >\r\n {{ resolveTranslatable(issue.message) }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n } @else if (selectionKind() === \"workflow\") {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.workflow.triggers\" | transloco }}\r\n </h3>\r\n @if (store.triggers().length > 0) {\r\n <ul class=\"space-y-1.5\">\r\n @for (trigger of store.triggers(); track trigger.id) {\r\n <li\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2.5\"\r\n >\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div>\r\n <div\r\n class=\"text-[13px] font-medium text-(--p-text-color)\"\r\n >\r\n {{ triggerLabel(trigger) }}\r\n </div>\r\n <div\r\n class=\"mt-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ trigger.type }}\r\n </div>\r\n </div>\r\n <span\r\n class=\"rounded-md px-2 py-0.5 text-[10.5px] font-medium\"\r\n [class.bg-emerald-50]=\"trigger.enabled\"\r\n [class.text-emerald-700]=\"trigger.enabled\"\r\n [class.bg-slate-100]=\"!trigger.enabled\"\r\n [class.text-slate-500]=\"!trigger.enabled\"\r\n >\r\n {{\r\n (trigger.enabled\r\n ? \"flowplus.inspector.workflow.triggerEnabled\"\r\n : \"flowplus.inspector.modal.disabled\"\r\n ) | transloco\r\n }}\r\n </span>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.workflow.noTriggers\" | transloco }}\r\n </p>\r\n }\r\n </section>\r\n } @else {\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.noSelectionHint\" | transloco }}\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n\r\n <!-- resize handle: INPUT | CONFIG -->\r\n <div\r\n class=\"fp-resizer\"\r\n [class.is-active]=\"resizing() === 'left'\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n (pointerdown)=\"startResize('left', $event)\"\r\n (pointermove)=\"onResizeMove($event)\"\r\n (pointerup)=\"endResize($event)\"\r\n (pointercancel)=\"endResize($event)\"\r\n >\r\n <span class=\"fp-resizer-grip\" aria-hidden=\"true\"></span>\r\n </div>\r\n }\r\n\r\n <!-- --------------------------- CONFIGURATION --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 min-w-0 flex-1 flex-col bg-(--p-content-background)\"\r\n >\r\n @if (selectionKind() === \"trigger\" && selectedTrigger(); as trigger) {\r\n <fp-automation-smart-editor [trigger]=\"trigger\" mode=\"modal\" />\r\n } @else {\r\n <fp-inspector-shell\r\n mode=\"modal\"\r\n (stepChange)=\"onStepChange($event)\"\r\n (connectionChange)=\"onConnectionChange($event)\"\r\n (workflowChange)=\"onWorkflowChange($event)\"\r\n />\r\n }\r\n </section>\r\n\r\n <!-- resize handle: CONFIG | OUTPUT -->\r\n <div\r\n class=\"fp-resizer\"\r\n [class.is-active]=\"resizing() === 'right'\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n (pointerdown)=\"startResize('right', $event)\"\r\n (pointermove)=\"onResizeMove($event)\"\r\n (pointerup)=\"endResize($event)\"\r\n (pointercancel)=\"endResize($event)\"\r\n >\r\n <span class=\"fp-resizer-grip\" aria-hidden=\"true\"></span>\r\n </div>\r\n\r\n <!-- --------------------------- OUTPUT --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 shrink-0 flex-col bg-(--p-surface-50)\"\r\n [style.width.px]=\"rightWidth()\"\r\n >\r\n <div\r\n class=\"shrink-0 border-b border-(--p-content-border-color) px-5 py-4\"\r\n >\r\n @if (selectionKind() === \"step\" && mockEditorOpen()) {\r\n <!-- EDIT OUTPUT replaces the OUTPUT header while pinning mock data -->\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p class=\"text-[13.5px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.editOutput\" | transloco }}\r\n </p>\r\n <div class=\"flex items-center gap-1.5\">\r\n <mt-button\r\n variant=\"outlined\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.cancel' | transloco\"\r\n (onClick)=\"cancelMockEditor()\"\r\n />\r\n <mt-button\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.save' | transloco\"\r\n (onClick)=\"saveMock()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p\r\n class=\"text-[15px] font-bold uppercase tracking-[0.04em] text-(--p-text-color)\"\r\n >\r\n {{\r\n selectionKind() === \"trigger\"\r\n ? \"Trigger runtime\"\r\n : (\"flowplus.inspector.modal.outputsTitle\" | transloco)\r\n }}\r\n </p>\n @if (selectionKind() === \"step\") {\n <div class=\"flex shrink-0 items-center gap-2\">\n @if (hasOutputData()) {\n <div\n class=\"inline-flex rounded-lg bg-(--p-surface-100) p-0.5 text-[11px] font-semibold\"\n >\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"\n 'flowplus.inspector.modal.viewSchema' | transloco\n \"\n [styleClass]=\"\n outputView() === 'schema'\n ? 'fp-modal-segment-button is-active'\n : 'fp-modal-segment-button'\n \"\n (onClick)=\"outputView.set('schema')\"\n />\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"'flowplus.inspector.modal.viewJson' | transloco\"\n [styleClass]=\"\n outputView() === 'json'\n ? 'fp-modal-segment-button is-active'\n : 'fp-modal-segment-button'\n \"\n (onClick)=\"outputView.set('json')\"\n />\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-y-auto\">\r\n @if (selectionKind() === \"step\") {\r\n @if (mockEditorOpen()) {\r\n <!-- ----- mock data editor (header lives in the panel header) ----- -->\r\n <div class=\"flex h-full min-h-0 flex-col gap-2 p-4\">\r\n <div\r\n class=\"fp-code-frame flex min-h-0 flex-1 overflow-hidden rounded-xl border bg-(--p-surface-100) p-2 transition\"\r\n [style.borderColor]=\"\r\n mockError() ? '#f43f5e' : 'var(--p-content-border-color)'\r\n \"\r\n >\r\n <mt-textarea-field\r\n class=\"min-h-0 flex-1 [&_textarea]:fp-scroll [&_textarea]:min-h-[18rem] [&_textarea]:resize-none [&_textarea]:bg-transparent [&_textarea]:font-mono [&_textarea]:text-[12px] [&_textarea]:leading-5\"\r\n [field]=\"false\"\r\n [ngModel]=\"mockText()\"\r\n (ngModelChange)=\"mockText.set($event ?? '')\"\r\n [pInputs]=\"mockTextareaInputs\"\r\n [maxLength]=\"null\"\r\n rows=\"18\"\r\n />\r\n </div>\r\n @if (mockError()) {\r\n <p\r\n class=\"flex items-center gap-1.5 text-[11.5px] text-rose-600\"\r\n >\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3.5 w-3.5 fill-none stroke-current stroke-[2]\"\r\n >\r\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\r\n <path d=\"M12 8v4M12 16h.01\" stroke-linecap=\"round\" />\r\n </svg>\r\n {{ mockError() }}\r\n </p>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"flex min-h-full flex-col gap-3 px-5 py-5\">\n <div class=\"flex shrink-0 justify-end\">\n <mt-button\n severity=\"primary\"\n size=\"small\"\n icon=\"general.play\"\n [label]=\"'flowplus.inspector.modal.executeStep' | transloco\"\n [loading]=\"runningTest()\"\n [disabled]=\"runningTest() || !supportsTestRun()\"\n (onClick)=\"executeStep()\"\n />\n </div>\n @if (hasOutputData()) {\n <!-- ----- output payload ----- -->\n <div class=\"space-y-3\">\n @if (outputIsStale()) {\n <p\n class=\"m-0 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-2 text-[11.5px] leading-5 text-amber-800\"\n >\n Output may change because this step configuration\n changed. Execute again to refresh it.\n </p>\n }\n @if (outputIsMock()) {\n <div\n class=\"flex items-center justify-between gap-2 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-2.5 py-1.5\"\n >\r\n <span\r\n class=\"flex items-center gap-1.5 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[18px]\"\r\n visualKey=\"state:pinned-output\"\r\n />\r\n {{\r\n \"flowplus.inspector.modal.mockPinned\" | transloco\r\n }}\r\n </span>\r\n <div class=\"flex items-center gap-1\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.edit' | transloco\r\n \"\r\n (onClick)=\"openMockEditor()\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.clear' | transloco\r\n \"\r\n (onClick)=\"clearMock()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (outputFileResults().length > 0) {\r\n <div class=\"space-y-2\">\r\n @for (file of outputFileResults(); track file.key) {\r\n <fp-automation-file-card\r\n [file]=\"file\"\r\n [inspectable]=\"canInspectOutputFile(file)\"\r\n [downloadable]=\"canDownloadOutputFile(file)\"\r\n (inspect)=\"inspectSelectedNodeData(false)\"\r\n (download)=\"downloadOutputFile(file)\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @if (outputView() === \"json\") {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg border border-(--p-content-border-color) bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ outputJson() }}</pre\r\n >\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n outputPickerTree;\r\n context: { nodes: outputPickerNodes(), level: 0 }\r\n \"\r\n />\r\n }\r\n </div>\r\n } @else {\r\n <!-- ----- empty state ----- -->\n <div\n class=\"flex flex-col items-center justify-center gap-4 rounded-md border border-surface-200 bg-surface-0 px-6 py-8 text-center\"\n >\n <span\r\n class=\"grid h-14 w-14 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[42px]\"\r\n visualKey=\"state:output-empty\"\r\n />\r\n </span>\r\n <div class=\"space-y-1\">\r\n <p\r\n class=\"text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ emptyOutputTitle() }}\r\n </p>\r\n <p\r\n class=\"text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyOutputHint() }}\r\n </p>\r\n </div>\r\n <div class=\"flex flex-col items-center gap-2\">\n <mt-button\n variant=\"text\"\n severity=\"primary\"\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.setMockData' | transloco\r\n \"\r\n (onClick)=\"openMockEditor()\"\r\n />\r\n </div>\r\n @if (!supportsTestRun()) {\n <p\n class=\"max-w-[16rem] rounded-lg bg-(--p-surface-100) px-3 py-2 text-[11px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ testRunUnavailableHint() }}\n </p>\n }\n </div>\r\n }\r\n </div>\r\n }\r\n } @else if (\r\n selectionKind() === \"trigger\" && selectedTrigger();\r\n as trigger\r\n ) {\r\n <div class=\"space-y-3 px-5 py-5\">\r\n <section\r\n class=\"rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <header\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <h3\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Latest run\r\n </h3>\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.link-external-02\"\r\n [tooltip]=\"'Open full run'\"\r\n [disabled]=\"!selectedTriggerRuntimeState()?.executionId\"\r\n (onClick)=\"openSelectedRuntimeRun()\"\r\n />\r\n </header>\r\n <div class=\"p-3\">\r\n @if (selectedTriggerRuntimeState(); as state) {\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2 py-1 text-[11.5px] font-semibold text-(--p-text-color)\"\r\n >{{ state.status }}</span\r\n >\r\n @if (state.executionId) {\r\n <span\r\n class=\"font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n #{{ state.executionId }}\r\n </span>\r\n }\r\n @if (\r\n state.durationMs !== null &&\r\n state.durationMs !== undefined\r\n ) {\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ state.durationMs }} ms\r\n </span>\r\n }\r\n </div>\r\n @if (state.errorMessage) {\r\n <p\r\n class=\"mt-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ state.errorMessage }}\r\n </p>\r\n }\r\n } @else {\r\n <p\r\n class=\"m-0 text-[12.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No runtime state is loaded for this trigger yet. Execute a\r\n real run or open the executions page for history.\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else if (selectionKind() === \"connection\") {\r\n <div class=\"px-4 py-4\">\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.tabs.routing\" | transloco }}\r\n </h3>\r\n <div\r\n class=\"space-y-2 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n @if (selectedConnectionActionsLabel()) {\r\n <div class=\"font-mono text-[11.5px] text-(--p-text-color)\">\r\n {{ selectedConnectionActionsLabel() }}\r\n </div>\r\n } @else {\r\n <div class=\"text-[12.5px] text-(--p-text-muted-color)\">\r\n {{\r\n \"flowplus.inspector.modal.noConfiguredOutputs\"\r\n | transloco\r\n }}\r\n </div>\r\n }\r\n @if (selectedConnection()!.expressionText) {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedConnection()!.expressionText }}</pre\r\n >\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else if (selectionKind() === \"workflow\") {\r\n <div class=\"px-4 py-4\">\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.workflowHealth\" | transloco }}\r\n </h3>\r\n <div class=\"grid grid-cols-3 gap-2\">\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.errorsLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-xl font-semibold text-(--p-text-color)\"\r\n >\r\n {{ workflowHealth().errors }}\r\n </div>\r\n </div>\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.warningsLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-xl font-semibold text-(--p-text-color)\"\r\n >\r\n {{ workflowHealth().warnings }}\r\n </div>\r\n </div>\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.publishLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-[13px] font-semibold\"\r\n [class.text-emerald-600]=\"workflowHealth().canPublish\"\r\n [class.text-amber-600]=\"!workflowHealth().canPublish\"\r\n >\r\n {{\r\n (workflowHealth().canPublish\r\n ? \"flowplus.inspector.modal.publishReady\"\r\n : \"flowplus.inspector.modal.publishBlocked\"\r\n ) | transloco\r\n }}\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n </div>\r\n } @else {\r\n <div class=\"px-4 py-4\">\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.noSelectionHint\" | transloco }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n\r\n <!-- Connected-node avatars \u2014 straddle the modal border (half in / half out),\r\n vertically centered. Rendered outside the clipping card so they protrude. -->\r\n @if (selectionKind() === \"step\") {\r\n @if (previousNeighbours().length > 0) {\r\n <div\r\n class=\"pointer-events-none absolute inset-y-0 start-0 z-50 flex -translate-x-1/2 flex-col items-center justify-center gap-3 rtl:translate-x-1/2\"\r\n >\r\n @for (n of previousNeighbours(); track n.stepId) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n edgeTab;\r\n context: { $implicit: n, side: 'start' }\r\n \"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (nextNeighbours().length > 0) {\r\n <div\r\n class=\"pointer-events-none absolute inset-y-0 end-0 z-50 flex translate-x-1/2 flex-col items-center justify-center gap-3 rtl:-translate-x-1/2\"\r\n >\r\n @for (n of nextNeighbours(); track n.stepId) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n edgeTab;\r\n context: { $implicit: n, side: 'end' }\r\n \"\r\n />\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: InspectorShellComponent, selector: "fp-inspector-shell", inputs: ["mode"], outputs: ["stepChange", "connectionChange", "workflowChange", "closeRequested"] }, { kind: "component", type: AutomationSmartEditorComponent, selector: "fp-automation-smart-editor", inputs: ["step", "trigger", "mode", "view"] }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "component", type: AutomationFileCardComponent, selector: "fp-automation-file-card", inputs: ["file", "inspectable", "downloadable"], outputs: ["inspect", "download"] }, { kind: "directive", type: DragDataDirective, selector: "[fpDragData]", inputs: ["fpDragData"] }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
|
|
23654
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: InspectorModalBodyComponent, isStandalone: true, selector: "fp-inspector-modal-body", host: { classAttribute: "block h-full" }, viewQueries: [{ propertyName: "resizeHost", first: true, predicate: ["resizeHost"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- A connected node action sitting on the modal's outer edge.\r\n The same shared automation icon renderer used by canvas/picker is used\r\n here so modal-side node shortcuts keep one visual source of truth.\r\n `side` = 'start' for upstream nodes that feed in, 'end' for downstream\r\n nodes this step feeds. -->\r\n<ng-template #edgeTab let-n>\r\n <span\r\n class=\"fp-edge-tab pointer-events-auto\"\r\n [style.--fp-edge-tab-color]=\"n.color\"\r\n [style.--fp-edge-tab-bg]=\"n.bg\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"fp-edge-tab-button\"\r\n [attr.aria-label]=\"n.label\"\r\n [mtTooltip]=\"n.label\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n (click)=\"navigateToStep(n.stepId)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[39px]\"\r\n [type]=\"n.type\"\r\n [itemKey]=\"n.key\"\r\n [displayName]=\"n.label\"\r\n [metadata]=\"n.metadata\"\r\n />\r\n </button>\r\n </span>\r\n</ng-template>\r\n\r\n<!-- Type glyph badge for a context value (string / number / date / \u2026). -->\r\n<ng-template #typeBadge let-kind>\r\n <span\r\n class=\"flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-1.5 font-mono text-[12px] font-bold leading-none text-(--p-text-color) shadow-sm\"\r\n >\r\n @switch (kind) {\r\n @case (\"string\") {\r\n T\r\n }\r\n @case (\"number\") {\r\n #\r\n }\r\n @case (\"object\") {\r\n {}\r\n }\r\n @case (\"array\") {\r\n [ ]\r\n }\r\n @case (\"boolean\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2.4]\"\r\n >\r\n <rect x=\"3\" y=\"7\" width=\"18\" height=\"10\" rx=\"5\" />\r\n <circle cx=\"9\" cy=\"12\" r=\"2\" fill=\"currentColor\" stroke=\"none\" />\r\n </svg>\r\n }\r\n @case (\"date\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <rect x=\"4\" y=\"5\" width=\"16\" height=\"16\" rx=\"2.5\" />\r\n <path d=\"M4 9h16M8 3v4M16 3v4\" stroke-linecap=\"round\" />\r\n </svg>\r\n }\r\n @case (\"file\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <path\r\n d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z\"\r\n />\r\n <path d=\"M14 3v5h5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n </svg>\r\n }\r\n @case (\"secret\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <rect x=\"5\" y=\"11\" width=\"14\" height=\"9\" rx=\"2\" />\r\n <path d=\"M8 11V8a4 4 0 0 1 8 0v3\" stroke-linecap=\"round\" />\r\n </svg>\r\n }\r\n @case (\"reference\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <path\r\n d=\"M7 17 17 7M9 7h8v8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n }\r\n @default {\r\n \u2022\r\n }\r\n }\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #inputPickerTree let-nodes=\"nodes\" let-level=\"level\">\r\n <ul class=\"border-t border-(--p-content-border-color)\">\r\n @for (node of nodes; track node.id) {\r\n <li class=\"border-b border-(--p-content-border-color) last:border-b-0\">\r\n <div\r\n [fpDragData]=\"node.rawExpression\"\r\n [mtTooltip]=\"pickerNodeTooltip(node)\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n class=\"fp-data-row group flex cursor-grab items-center gap-1.5 px-2.5 py-2.5 transition duration-200 ease-out hover:bg-(--p-surface-50) active:cursor-grabbing\"\r\n [style.padding-inline-start.rem]=\"0.45 + level * 0.5\"\r\n >\r\n @if (node.children.length > 0) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isInputPickerNodeExpanded(node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n [tooltip]=\"\r\n isInputPickerNodeExpanded(node) ? 'Collapse' : 'Expand'\r\n \"\r\n styleClass=\"fp-modal-tree-toggle !h-6 !w-6\"\r\n (onClick)=\"toggleInputPickerNode(node)\"\r\n />\r\n } @else {\r\n <span class=\"h-6 w-6 shrink-0\"></span>\r\n }\r\n\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n <span\r\n class=\"min-w-0 truncate text-[14px] font-semibold leading-5 text-(--p-text-color)\"\r\n [title]=\"node.label\"\r\n >{{ node.label }}</span\r\n >\r\n @if (node.availability === \"masked\") {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-error))]/12 px-2 py-0.5 text-[10.5px] font-semibold text-[rgb(var(--fp-error))]\"\r\n >\r\n secret\r\n </span>\r\n }\r\n </div>\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [tooltip]=\"\r\n copiedPickerNodeId() === node.id ? 'Copied' : 'Copy expression'\r\n \"\r\n styleClass=\"!h-6 !w-6 shrink-0 opacity-0 group-hover:opacity-100 focus:opacity-100\"\r\n (onClick)=\"copyPickerNode(node, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isInputPickerNodeExpanded(node)) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n inputPickerTree;\r\n context: { nodes: node.children, level: level + 1 }\r\n \"\r\n />\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<ng-template #outputPickerTree let-nodes=\"nodes\" let-level=\"level\">\r\n <ul class=\"space-y-1.5\">\r\n @for (node of nodes; track node.id) {\r\n <li>\r\n <div\r\n [mtTooltip]=\"outputNodeTooltip(node)\"\r\n tooltipPosition=\"left\"\r\n appendTo=\"body\"\r\n class=\"group flex min-w-0 items-center gap-1.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-2.5 py-2.5 shadow-sm transition duration-200 hover:bg-(--p-surface-50)\"\r\n [style.margin-inline-start.rem]=\"level * 0.55\"\r\n >\r\n @if (node.children.length > 0) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isOutputPickerNodeExpanded(node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n [tooltip]=\"\r\n isOutputPickerNodeExpanded(node) ? 'Collapse' : 'Expand'\r\n \"\r\n styleClass=\"fp-modal-tree-toggle !h-6 !w-6\"\r\n (onClick)=\"toggleOutputPickerNode(node)\"\r\n />\r\n } @else {\r\n <span class=\"h-6 w-6 shrink-0\"></span>\r\n }\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n typeBadge;\r\n context: { $implicit: fieldKind(node.type) }\r\n \"\r\n />\r\n\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-[13.5px] font-semibold leading-5 text-(--p-text-color)\"\r\n [title]=\"node.label\"\r\n >\r\n {{ node.label }}\r\n </span>\r\n @if (outputNodePreview(node)) {\r\n <span\r\n class=\"min-w-0 max-w-[45%] truncate font-mono text-[12px] text-(--p-text-muted-color)\"\r\n [title]=\"outputNodePreview(node)\"\r\n >\r\n {{ outputNodePreview(node) }}\r\n </span>\r\n }\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [tooltip]=\"\r\n copiedPickerNodeId() === node.id ? 'Copied' : 'Copy expression'\r\n \"\r\n styleClass=\"!h-6 !w-6 shrink-0 opacity-0 group-hover:opacity-100 focus:opacity-100\"\r\n (onClick)=\"copyPickerNode(node, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isOutputPickerNodeExpanded(node)) {\r\n <div class=\"mt-1.5\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n outputPickerTree;\r\n context: { nodes: node.children, level: level + 1 }\r\n \"\r\n />\r\n </div>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<!-- Outer wrapper: positioning context only (overflow visible) so the connected-\r\n node avatars can straddle the modal border. The inner card clips its own\r\n rounded corners + carries the shadow. -->\r\n<div class=\"relative h-full w-full text-(--p-text-color)\">\r\n <div\r\n class=\"flex h-full min-h-0 flex-col overflow-hidden rounded-2xl bg-(--p-content-background) shadow-2xl shadow-slate-950/25\"\r\n >\r\n <!-- ============================ HEADER ============================ -->\r\n <header\r\n class=\"flex shrink-0 items-center gap-3.5 border-b border-(--p-content-border-color) px-6 py-4\"\r\n >\r\n @if (currentChip(); as chip) {\r\n <span\r\n class=\"grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-sm ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"chip.color\"\r\n [style.--fp-avatar-bg]=\"chip.bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n [type]=\"chip.type\"\r\n [itemKey]=\"chip.key\"\r\n [displayName]=\"chip.label\"\r\n [metadata]=\"chip.metadata\"\r\n />\r\n </span>\r\n } @else {\r\n <span\r\n class=\"grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color) shadow-sm ring-1 ring-(--p-content-border-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [visualKey]=\"\r\n selectionKind() === 'connection'\r\n ? 'state:connection'\r\n : 'state:settings'\r\n \"\r\n />\r\n </span>\r\n }\r\n\r\n <div class=\"min-w-0 flex-1\">\r\n <h2\r\n class=\"truncate text-[17px] font-semibold tracking-tight text-(--p-text-color)\"\r\n >\r\n {{ activeDescriptorLabel() }}\r\n </h2>\r\n <p\r\n class=\"m-0 mt-1 line-clamp-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ activeDescriptorSubtitle() }}\r\n </p>\r\n </div>\r\n\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'flowplus.inspector.modal.close' | transloco\"\r\n (onClick)=\"close()\"\r\n />\r\n </header>\r\n\r\n <!-- ===================== BODY: INPUT | CONFIG | OUTPUT ===================== -->\r\n <div\r\n #resizeHost\r\n class=\"relative flex min-h-0 flex-1\"\r\n [class.select-none]=\"resizing()\"\r\n [style.cursor]=\"resizing() ? 'col-resize' : null\"\r\n >\r\n <!-- Navigation loading beat \u2014 a slim primary progress sweep across the top\r\n while the modal re-derives for a newly selected (connected) step. -->\r\n @if (navigating()) {\r\n <div\r\n class=\"pointer-events-none absolute inset-x-0 top-0 z-40 h-[3px] overflow-hidden\"\r\n >\r\n <div\r\n class=\"h-full w-2/5 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_0.9s_ease-in-out_infinite]\"\r\n ></div>\r\n </div>\r\n }\r\n\r\n @if (selectionKind() !== \"trigger\") {\r\n <!-- --------------------------- INPUT --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 shrink-0 flex-col bg-(--p-surface-50)\"\r\n [style.width.px]=\"leftWidth()\"\r\n >\r\n <div\r\n class=\"shrink-0 border-b border-(--p-content-border-color) px-5 py-4\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p\r\n class=\"text-[15px] font-bold uppercase tracking-[0.04em] text-(--p-text-color)\"\r\n >\r\n {{\r\n selectionKind() === \"trigger\"\r\n ? \"Trigger payload\"\r\n : (\"flowplus.inspector.modal.inputsTitle\" | transloco)\r\n }}\r\n </p>\r\n @if (selectionKind() === \"step\") {\r\n <div class=\"flex items-center gap-1.5\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.search-md\"\r\n [tooltip]=\"'flowplus.context.pickerPlaceholder' | transloco\"\r\n [styleClass]=\"\r\n contextSearchOpen()\r\n ? 'fp-modal-icon-button is-active'\r\n : 'fp-modal-icon-button'\r\n \"\r\n (onClick)=\"toggleContextSearch()\"\r\n />\r\n <div\r\n class=\"inline-flex rounded-lg bg-(--p-surface-100) p-0.5 text-[11px] font-semibold\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.viewSchema' | transloco\r\n \"\r\n [styleClass]=\"\r\n inputView() === 'schema'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"inputView.set('schema')\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.viewJson' | transloco\"\r\n [styleClass]=\"\r\n inputView() === 'json'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"inputView.set('json')\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (selectionKind() === \"step\" && contextSearchOpen()) {\r\n <div class=\"mt-3\">\r\n <mt-text-field\r\n [ngModel]=\"contextSearch()\"\r\n (ngModelChange)=\"contextSearch.set($event)\"\r\n [placeholder]=\"\r\n 'flowplus.context.pickerPlaceholder' | transloco\r\n \"\r\n icon=\"general.search-md\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-y-auto px-5 py-4\">\r\n @if (selectionKind() === \"step\") {\r\n @if (inputView() === \"schema\") {\r\n <div class=\"space-y-3\">\r\n <section class=\"space-y-2.5\">\r\n @if (filteredInputPickerGroups().length > 0) {\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <h3\r\n class=\"flex items-center gap-2 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.availableData\" | transloco\r\n }}\r\n </h3>\r\n </div>\r\n }\r\n\r\n @if (filteredInputPickerGroups().length === 0) {\r\n <div\r\n class=\"flex min-h-[18rem] flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <span\r\n class=\"grid h-14 w-14 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[42px]\"\r\n visualKey=\"state:input-empty\"\r\n />\r\n </span>\r\n <div class=\"space-y-1\">\r\n <p\r\n class=\"text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.noInputData\" | transloco\r\n }}\r\n </p>\r\n <p\r\n class=\"mx-auto max-w-[16rem] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.noAvailableData\"\r\n | transloco\r\n }}\r\n </p>\r\n </div>\r\n </div>\r\n } @else {\r\n <p\r\n class=\"flex items-center gap-1.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"h-3.5 w-3.5 shrink-0 fill-current opacity-70\"\r\n aria-hidden=\"true\"\r\n >\r\n <circle cx=\"5\" cy=\"4\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"4\" r=\"1.1\" />\r\n <circle cx=\"5\" cy=\"8\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"8\" r=\"1.1\" />\r\n <circle cx=\"5\" cy=\"12\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"12\" r=\"1.1\" />\r\n </svg>\r\n {{ \"flowplus.inspector.modal.dragHint\" | transloco }}\r\n </p>\r\n\r\n <div class=\"space-y-1.5\">\r\n @for (\r\n group of filteredInputPickerGroups();\r\n track group.id\r\n ) {\r\n <section\r\n class=\"overflow-hidden rounded-xl border border-(--p-content-border-color) bg-(--p-content-background)\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isGroupCollapsed(group.id)\r\n ? 'arrow.chevron-right'\r\n : 'arrow.chevron-down'\r\n \"\r\n [label]=\"group.label\"\r\n styleClass=\"fp-modal-group-toggle\"\r\n (onClick)=\"toggleGroup(group.id)\"\r\n />\r\n\r\n @if (!isGroupCollapsed(group.id)) {\r\n @if (group.children.length === 0) {\r\n <div\r\n class=\"border-t border-(--p-content-border-color) px-3 py-3 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyPickerGroupMessage(group.id) }}\r\n </div>\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n inputPickerTree;\r\n context: { nodes: group.children, level: 0 }\r\n \"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n } @else {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg border border-(--p-content-border-color) bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ inputJson() }}</pre\r\n >\r\n }\r\n } @else if (\r\n selectionKind() === \"connection\" && connectionEndpoints();\r\n as endpoints\r\n ) {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.connectionSummary\" | transloco }}\r\n </h3>\r\n <div\r\n class=\"space-y-2.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n <div>\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.source\" | transloco }}\r\n </div>\r\n <div class=\"mt-1 text-[13px] text-(--p-text-color)\">\r\n {{ endpoints.source }}\r\n </div>\r\n </div>\r\n <div>\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.target\" | transloco }}\r\n </div>\r\n <div class=\"mt-1 text-[13px] text-(--p-text-color)\">\r\n {{ endpoints.target }}\r\n </div>\r\n </div>\r\n @if (selectedConnection()!.sourcePortKey) {\r\n <div\r\n class=\"font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ selectedConnection()!.sourcePortKey }} ->\r\n {{ selectedConnection()!.targetPortKey || \"in\" }}\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n } @else if (selectionKind() === \"trigger\") {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n Trigger payload and setup\r\n </h3>\r\n <div\r\n class=\"space-y-2.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n @if (selectedTriggerPayloadSchemaJson()) {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Payload schema\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerPayloadSchemaJson() }}</pre\r\n >\r\n </div>\r\n } @else {\r\n <p\r\n class=\"m-0 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n The backend did not return a payload schema for this\r\n trigger.\r\n </p>\r\n }\r\n @if (selectedTriggerPayloadSampleJson()) {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Sample payload\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerPayloadSampleJson() }}</pre\r\n >\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"WebhookTrigger\") {\r\n <div\r\n class=\"space-y-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n <div>\r\n Expected method:\r\n {{ selectedTrigger()?.expectedHttpMethod ?? \"POST\" }}\r\n </div>\r\n <div>\r\n Content types:\r\n {{\r\n (selectedTrigger()?.allowedContentTypes ?? []).join(\r\n \", \"\r\n ) || \"Backend default\"\r\n }}\r\n </div>\r\n @if (selectedTrigger()?.exampleCurl) {\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-28 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTrigger()?.exampleCurl }}</pre\r\n >\r\n }\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"FormSubmitTrigger\") {\r\n <div\r\n class=\"rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n Pinned formVersionId:\r\n <code>{{\r\n selectedTriggerFormVersionId() ?? \"Not selected\"\r\n }}</code>\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"ScheduleTrigger\") {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Schedule config\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerConfigJson() }}</pre\r\n >\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (selectedTriggerIssues().length > 0) {\r\n <div class=\"space-y-1.5\">\r\n @for (issue of selectedTriggerIssues(); track $index) {\r\n <div\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] leading-5 text-amber-800\"\r\n >\r\n {{ resolveTranslatable(issue.message) }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n } @else if (selectionKind() === \"workflow\") {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.workflow.triggers\" | transloco }}\r\n </h3>\r\n @if (store.triggers().length > 0) {\r\n <ul class=\"space-y-1.5\">\r\n @for (trigger of store.triggers(); track trigger.id) {\r\n <li\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2.5\"\r\n >\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div>\r\n <div\r\n class=\"text-[13px] font-medium text-(--p-text-color)\"\r\n >\r\n {{ triggerLabel(trigger) }}\r\n </div>\r\n <div\r\n class=\"mt-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ trigger.type }}\r\n </div>\r\n </div>\r\n <span\r\n class=\"rounded-md px-2 py-0.5 text-[10.5px] font-medium\"\r\n [class.bg-emerald-50]=\"trigger.enabled\"\r\n [class.text-emerald-700]=\"trigger.enabled\"\r\n [class.bg-slate-100]=\"!trigger.enabled\"\r\n [class.text-slate-500]=\"!trigger.enabled\"\r\n >\r\n {{\r\n (trigger.enabled\r\n ? \"flowplus.inspector.workflow.triggerEnabled\"\r\n : \"flowplus.inspector.modal.disabled\"\r\n ) | transloco\r\n }}\r\n </span>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.workflow.noTriggers\" | transloco }}\r\n </p>\r\n }\r\n </section>\r\n } @else {\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.noSelectionHint\" | transloco }}\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n\r\n <!-- resize handle: INPUT | CONFIG -->\r\n <div\r\n class=\"fp-resizer\"\r\n [class.is-active]=\"resizing() === 'left'\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n (pointerdown)=\"startResize('left', $event)\"\r\n (pointermove)=\"onResizeMove($event)\"\r\n (pointerup)=\"endResize($event)\"\r\n (pointercancel)=\"endResize($event)\"\r\n >\r\n <span class=\"fp-resizer-grip\" aria-hidden=\"true\"></span>\r\n </div>\r\n }\r\n\r\n <!-- --------------------------- CONFIGURATION --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 min-w-0 flex-1 flex-col bg-(--p-content-background)\"\r\n >\r\n @if (selectionKind() === \"trigger\" && selectedTrigger(); as trigger) {\r\n <fp-automation-smart-editor [trigger]=\"trigger\" mode=\"modal\" />\r\n } @else {\r\n <fp-inspector-shell\r\n mode=\"modal\"\r\n (stepChange)=\"onStepChange($event)\"\r\n (connectionChange)=\"onConnectionChange($event)\"\r\n (workflowChange)=\"onWorkflowChange($event)\"\r\n />\r\n }\r\n </section>\r\n\r\n <!-- resize handle: CONFIG | OUTPUT -->\r\n <div\r\n class=\"fp-resizer\"\r\n [class.is-active]=\"resizing() === 'right'\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n (pointerdown)=\"startResize('right', $event)\"\r\n (pointermove)=\"onResizeMove($event)\"\r\n (pointerup)=\"endResize($event)\"\r\n (pointercancel)=\"endResize($event)\"\r\n >\r\n <span class=\"fp-resizer-grip\" aria-hidden=\"true\"></span>\r\n </div>\r\n\r\n <!-- --------------------------- OUTPUT --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 shrink-0 flex-col bg-(--p-surface-50)\"\r\n [style.width.px]=\"rightWidth()\"\r\n >\r\n <div\r\n class=\"shrink-0 border-b border-(--p-content-border-color) px-5 py-4\"\r\n >\r\n @if (selectionKind() === \"step\" && mockEditorOpen()) {\r\n <!-- EDIT OUTPUT replaces the OUTPUT header while pinning mock data -->\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p class=\"text-[13.5px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.editOutput\" | transloco }}\r\n </p>\r\n <div class=\"flex items-center gap-1.5\">\r\n <mt-button\r\n variant=\"outlined\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.cancel' | transloco\"\r\n (onClick)=\"cancelMockEditor()\"\r\n />\r\n <mt-button\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.save' | transloco\"\r\n (onClick)=\"saveMock()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p\r\n class=\"text-[15px] font-bold uppercase tracking-[0.04em] text-(--p-text-color)\"\r\n >\r\n {{\r\n selectionKind() === \"trigger\"\r\n ? \"Trigger runtime\"\r\n : (\"flowplus.inspector.modal.outputsTitle\" | transloco)\r\n }}\r\n </p>\r\n @if (selectionKind() === \"step\") {\r\n <div class=\"flex shrink-0 items-center gap-2\">\r\n @if (hasOutputData()) {\r\n <div\r\n class=\"inline-flex rounded-lg bg-(--p-surface-100) p-0.5 text-[11px] font-semibold\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.viewSchema' | transloco\r\n \"\r\n [styleClass]=\"\r\n outputView() === 'schema'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"outputView.set('schema')\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.viewJson' | transloco\r\n \"\r\n [styleClass]=\"\r\n outputView() === 'json'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"outputView.set('json')\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-y-auto\">\r\n @if (selectionKind() === \"step\") {\r\n @if (mockEditorOpen()) {\r\n <!-- ----- mock data editor (header lives in the panel header) ----- -->\r\n <div class=\"flex h-full min-h-0 flex-col gap-2 p-4\">\r\n <div\r\n class=\"fp-code-frame flex min-h-0 flex-1 overflow-hidden rounded-xl border bg-(--p-surface-100) p-2 transition\"\r\n [style.borderColor]=\"\r\n mockError() ? '#f43f5e' : 'var(--p-content-border-color)'\r\n \"\r\n >\r\n <mt-textarea-field\r\n class=\"min-h-0 flex-1 [&_textarea]:fp-scroll [&_textarea]:min-h-[18rem] [&_textarea]:resize-none [&_textarea]:bg-transparent [&_textarea]:font-mono [&_textarea]:text-[12px] [&_textarea]:leading-5\"\r\n [field]=\"false\"\r\n [ngModel]=\"mockText()\"\r\n (ngModelChange)=\"mockText.set($event ?? '')\"\r\n [pInputs]=\"mockTextareaInputs\"\r\n [maxLength]=\"null\"\r\n rows=\"18\"\r\n />\r\n </div>\r\n @if (mockError()) {\r\n <p\r\n class=\"flex items-center gap-1.5 text-[11.5px] text-rose-600\"\r\n >\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3.5 w-3.5 fill-none stroke-current stroke-[2]\"\r\n >\r\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\r\n <path d=\"M12 8v4M12 16h.01\" stroke-linecap=\"round\" />\r\n </svg>\r\n {{ mockError() }}\r\n </p>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"flex min-h-full flex-col gap-3 px-5 py-5\">\r\n <div class=\"flex shrink-0 justify-end\">\r\n <mt-button\r\n severity=\"primary\"\r\n size=\"small\"\r\n icon=\"general.play\"\r\n [label]=\"'flowplus.inspector.modal.executeStep' | transloco\"\r\n [loading]=\"runningTest()\"\r\n [disabled]=\"runningTest() || !supportsTestRun()\"\r\n (onClick)=\"executeStep()\"\r\n />\r\n </div>\r\n @if (hasOutputData()) {\r\n <!-- ----- output payload ----- -->\r\n <div class=\"space-y-3\">\r\n @if (outputIsStale()) {\r\n <p\r\n class=\"m-0 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-2 text-[11.5px] leading-5 text-amber-800\"\r\n >\r\n Output may change because this step configuration\r\n changed. Execute again to refresh it.\r\n </p>\r\n }\r\n @if (outputIsMock()) {\r\n <div\r\n class=\"flex items-center justify-between gap-2 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-2.5 py-1.5\"\r\n >\r\n <span\r\n class=\"flex items-center gap-1.5 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[18px]\"\r\n visualKey=\"state:pinned-output\"\r\n />\r\n {{\r\n \"flowplus.inspector.modal.mockPinned\" | transloco\r\n }}\r\n </span>\r\n <div class=\"flex items-center gap-1\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.edit' | transloco\r\n \"\r\n (onClick)=\"openMockEditor()\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.clear' | transloco\r\n \"\r\n (onClick)=\"clearMock()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (outputFileResults().length > 0) {\r\n <div class=\"space-y-2\">\r\n @for (file of outputFileResults(); track file.key) {\r\n <fp-automation-file-card\r\n [file]=\"file\"\r\n [inspectable]=\"canInspectOutputFile(file)\"\r\n [downloadable]=\"canDownloadOutputFile(file)\"\r\n (inspect)=\"inspectSelectedNodeData(false)\"\r\n (download)=\"downloadOutputFile(file)\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @if (outputView() === \"json\") {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg border border-(--p-content-border-color) bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ outputJson() }}</pre\r\n >\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n outputPickerTree;\r\n context: { nodes: outputPickerNodes(), level: 0 }\r\n \"\r\n />\r\n }\r\n </div>\r\n } @else {\r\n <!-- ----- empty state ----- -->\r\n <div\r\n class=\"flex flex-col items-center justify-center gap-4 rounded-md border border-surface-200 bg-surface-0 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"grid h-14 w-14 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[42px]\"\r\n visualKey=\"state:output-empty\"\r\n />\r\n </span>\r\n <div class=\"space-y-1\">\r\n <p\r\n class=\"text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ emptyOutputTitle() }}\r\n </p>\r\n <p\r\n class=\"text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyOutputHint() }}\r\n </p>\r\n </div>\r\n <div class=\"flex flex-col items-center gap-2\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.setMockData' | transloco\r\n \"\r\n (onClick)=\"openMockEditor()\"\r\n />\r\n </div>\r\n @if (!supportsTestRun()) {\r\n <p\r\n class=\"max-w-[16rem] rounded-lg bg-(--p-surface-100) px-3 py-2 text-[11px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ testRunUnavailableHint() }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n } @else if (\r\n selectionKind() === \"trigger\" && selectedTrigger();\r\n as trigger\r\n ) {\r\n <div class=\"space-y-3 px-5 py-5\">\r\n <section\r\n class=\"rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <header\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <h3\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Latest run\r\n </h3>\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.link-external-02\"\r\n [tooltip]=\"'Open full run'\"\r\n [disabled]=\"!selectedTriggerRuntimeState()?.executionId\"\r\n (onClick)=\"openSelectedRuntimeRun()\"\r\n />\r\n </header>\r\n <div class=\"p-3\">\r\n @if (selectedTriggerRuntimeState(); as state) {\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2 py-1 text-[11.5px] font-semibold text-(--p-text-color)\"\r\n >{{ state.status }}</span\r\n >\r\n @if (state.executionId) {\r\n <span\r\n class=\"font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n #{{ state.executionId }}\r\n </span>\r\n }\r\n @if (\r\n state.durationMs !== null &&\r\n state.durationMs !== undefined\r\n ) {\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ state.durationMs }} ms\r\n </span>\r\n }\r\n </div>\r\n @if (state.errorMessage) {\r\n <p\r\n class=\"mt-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ state.errorMessage }}\r\n </p>\r\n }\r\n } @else {\r\n <p\r\n class=\"m-0 text-[12.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No runtime state is loaded for this trigger yet. Execute a\r\n real run or open the executions page for history.\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else if (selectionKind() === \"connection\") {\r\n <div class=\"px-4 py-4\">\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.tabs.routing\" | transloco }}\r\n </h3>\r\n <div\r\n class=\"space-y-2 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n @if (selectedConnectionActionsLabel()) {\r\n <div class=\"font-mono text-[11.5px] text-(--p-text-color)\">\r\n {{ selectedConnectionActionsLabel() }}\r\n </div>\r\n } @else {\r\n <div class=\"text-[12.5px] text-(--p-text-muted-color)\">\r\n {{\r\n \"flowplus.inspector.modal.noConfiguredOutputs\"\r\n | transloco\r\n }}\r\n </div>\r\n }\r\n @if (selectedConnection()!.expressionText) {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedConnection()!.expressionText }}</pre\r\n >\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else if (selectionKind() === \"workflow\") {\r\n <div class=\"px-4 py-4\">\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.workflowHealth\" | transloco }}\r\n </h3>\r\n <div class=\"grid grid-cols-3 gap-2\">\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.errorsLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-xl font-semibold text-(--p-text-color)\"\r\n >\r\n {{ workflowHealth().errors }}\r\n </div>\r\n </div>\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.warningsLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-xl font-semibold text-(--p-text-color)\"\r\n >\r\n {{ workflowHealth().warnings }}\r\n </div>\r\n </div>\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.publishLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-[13px] font-semibold\"\r\n [class.text-emerald-600]=\"workflowHealth().canPublish\"\r\n [class.text-amber-600]=\"!workflowHealth().canPublish\"\r\n >\r\n {{\r\n (workflowHealth().canPublish\r\n ? \"flowplus.inspector.modal.publishReady\"\r\n : \"flowplus.inspector.modal.publishBlocked\"\r\n ) | transloco\r\n }}\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n </div>\r\n } @else {\r\n <div class=\"px-4 py-4\">\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.noSelectionHint\" | transloco }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n\r\n <!-- Connected-node avatars \u2014 straddle the modal border (half in / half out),\r\n vertically centered. Rendered outside the clipping card so they protrude. -->\r\n @if (selectionKind() === \"step\") {\r\n @if (previousNeighbours().length > 0) {\r\n <div\r\n class=\"pointer-events-none absolute inset-y-0 start-0 z-50 flex -translate-x-1/2 flex-col items-center justify-center gap-3 rtl:translate-x-1/2\"\r\n >\r\n @for (n of previousNeighbours(); track n.stepId) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n edgeTab;\r\n context: { $implicit: n, side: 'start' }\r\n \"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (nextNeighbours().length > 0) {\r\n <div\r\n class=\"pointer-events-none absolute inset-y-0 end-0 z-50 flex translate-x-1/2 flex-col items-center justify-center gap-3 rtl:-translate-x-1/2\"\r\n >\r\n @for (n of nextNeighbours(); track n.stepId) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n edgeTab;\r\n context: { $implicit: n, side: 'end' }\r\n \"\r\n />\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: InspectorShellComponent, selector: "fp-inspector-shell", inputs: ["mode"], outputs: ["stepChange", "connectionChange", "workflowChange", "closeRequested"] }, { kind: "component", type: AutomationSmartEditorComponent, selector: "fp-automation-smart-editor", inputs: ["step", "trigger", "mode", "view"] }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "component", type: AutomationFileCardComponent, selector: "fp-automation-file-card", inputs: ["file", "inspectable", "downloadable"], outputs: ["inspect", "download"] }, { kind: "directive", type: DragDataDirective, selector: "[fpDragData]", inputs: ["fpDragData"] }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
|
|
23652
23655
|
}
|
|
23653
23656
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: InspectorModalBodyComponent, decorators: [{
|
|
23654
23657
|
type: Component,
|
|
@@ -23666,7 +23669,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
23666
23669
|
AutomationNodeIconComponent,
|
|
23667
23670
|
AutomationFileCardComponent,
|
|
23668
23671
|
...DATA_PILL_DRAG,
|
|
23669
|
-
], host: { class: 'block h-full' }, template: "<!-- A connected node action sitting on the modal's outer edge.\r\n The same shared automation icon renderer used by canvas/picker is used\r\n here so modal-side node shortcuts keep one visual source of truth.\r\n `side` = 'start' for upstream nodes that feed in, 'end' for downstream\r\n nodes this step feeds. -->\r\n<ng-template #edgeTab let-n>\r\n <span\r\n class=\"fp-edge-tab pointer-events-auto\"\r\n [style.--fp-edge-tab-color]=\"n.color\"\r\n [style.--fp-edge-tab-bg]=\"n.bg\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"fp-edge-tab-button\"\r\n [attr.aria-label]=\"n.label\"\r\n [mtTooltip]=\"n.label\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n (click)=\"navigateToStep(n.stepId)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[39px]\"\r\n [type]=\"n.type\"\r\n [itemKey]=\"n.key\"\r\n [displayName]=\"n.label\"\r\n [metadata]=\"n.metadata\"\r\n />\r\n </button>\r\n </span>\r\n</ng-template>\r\n\r\n<!-- Type glyph badge for a context value (string / number / date / \u2026). -->\r\n<ng-template #typeBadge let-kind>\r\n <span\r\n class=\"flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-1.5 font-mono text-[12px] font-bold leading-none text-(--p-text-color) shadow-sm\"\r\n >\r\n @switch (kind) {\r\n @case (\"string\") {\r\n T\r\n }\r\n @case (\"number\") {\r\n #\r\n }\r\n @case (\"object\") {\r\n {}\r\n }\r\n @case (\"array\") {\r\n [ ]\r\n }\r\n @case (\"boolean\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2.4]\"\r\n >\r\n <rect x=\"3\" y=\"7\" width=\"18\" height=\"10\" rx=\"5\" />\r\n <circle cx=\"9\" cy=\"12\" r=\"2\" fill=\"currentColor\" stroke=\"none\" />\r\n </svg>\r\n }\r\n @case (\"date\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <rect x=\"4\" y=\"5\" width=\"16\" height=\"16\" rx=\"2.5\" />\r\n <path d=\"M4 9h16M8 3v4M16 3v4\" stroke-linecap=\"round\" />\r\n </svg>\r\n }\r\n @case (\"file\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <path\r\n d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z\"\r\n />\r\n <path d=\"M14 3v5h5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n </svg>\r\n }\r\n @case (\"secret\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <rect x=\"5\" y=\"11\" width=\"14\" height=\"9\" rx=\"2\" />\r\n <path d=\"M8 11V8a4 4 0 0 1 8 0v3\" stroke-linecap=\"round\" />\r\n </svg>\r\n }\r\n @case (\"reference\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <path\r\n d=\"M7 17 17 7M9 7h8v8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n }\r\n @default {\r\n \u2022\r\n }\r\n }\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #inputPickerTree let-nodes=\"nodes\" let-level=\"level\">\r\n <ul class=\"border-t border-(--p-content-border-color)\">\r\n @for (node of nodes; track node.id) {\r\n <li class=\"border-b border-(--p-content-border-color) last:border-b-0\">\r\n <div\r\n [fpDragData]=\"node.rawExpression\"\r\n [mtTooltip]=\"pickerNodeTooltip(node)\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n class=\"fp-data-row group flex cursor-grab items-center gap-1.5 px-2.5 py-2.5 transition duration-200 ease-out hover:bg-(--p-surface-50) active:cursor-grabbing\"\r\n [style.padding-inline-start.rem]=\"0.45 + level * 0.5\"\r\n >\r\n @if (node.children.length > 0) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isInputPickerNodeExpanded(node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n [tooltip]=\"\r\n isInputPickerNodeExpanded(node) ? 'Collapse' : 'Expand'\r\n \"\r\n styleClass=\"fp-modal-tree-toggle !h-6 !w-6\"\r\n (onClick)=\"toggleInputPickerNode(node)\"\r\n />\r\n } @else {\r\n <span class=\"h-6 w-6 shrink-0\"></span>\r\n }\r\n\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n <span\r\n class=\"min-w-0 truncate text-[14px] font-semibold leading-5 text-(--p-text-color)\"\r\n [title]=\"node.label\"\r\n >{{ node.label }}</span\r\n >\r\n @if (node.availability === \"masked\") {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-error))]/12 px-2 py-0.5 text-[10.5px] font-semibold text-[rgb(var(--fp-error))]\"\r\n >\r\n secret\r\n </span>\r\n }\r\n </div>\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [tooltip]=\"\r\n copiedPickerNodeId() === node.id ? 'Copied' : 'Copy expression'\r\n \"\r\n styleClass=\"!h-6 !w-6 shrink-0 opacity-0 group-hover:opacity-100 focus:opacity-100\"\r\n (onClick)=\"copyPickerNode(node, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isInputPickerNodeExpanded(node)) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n inputPickerTree;\r\n context: { nodes: node.children, level: level + 1 }\r\n \"\r\n />\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<ng-template #outputPickerTree let-nodes=\"nodes\" let-level=\"level\">\r\n <ul class=\"space-y-1.5\">\r\n @for (node of nodes; track node.id) {\r\n <li>\r\n <div\r\n [mtTooltip]=\"outputNodeTooltip(node)\"\r\n tooltipPosition=\"left\"\r\n appendTo=\"body\"\r\n class=\"group flex min-w-0 items-center gap-1.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-2.5 py-2.5 shadow-sm transition duration-200 hover:bg-(--p-surface-50)\"\r\n [style.margin-inline-start.rem]=\"level * 0.55\"\r\n >\r\n @if (node.children.length > 0) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isOutputPickerNodeExpanded(node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n [tooltip]=\"\r\n isOutputPickerNodeExpanded(node) ? 'Collapse' : 'Expand'\r\n \"\r\n styleClass=\"fp-modal-tree-toggle !h-6 !w-6\"\r\n (onClick)=\"toggleOutputPickerNode(node)\"\r\n />\r\n } @else {\r\n <span class=\"h-6 w-6 shrink-0\"></span>\r\n }\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n typeBadge;\r\n context: { $implicit: fieldKind(node.type) }\r\n \"\r\n />\r\n\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-[13.5px] font-semibold leading-5 text-(--p-text-color)\"\r\n [title]=\"node.label\"\r\n >\r\n {{ node.label }}\r\n </span>\r\n @if (outputNodePreview(node)) {\r\n <span\r\n class=\"min-w-0 max-w-[45%] truncate font-mono text-[12px] text-(--p-text-muted-color)\"\r\n [title]=\"outputNodePreview(node)\"\r\n >\r\n {{ outputNodePreview(node) }}\r\n </span>\r\n }\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [tooltip]=\"\r\n copiedPickerNodeId() === node.id ? 'Copied' : 'Copy expression'\r\n \"\r\n styleClass=\"!h-6 !w-6 shrink-0 opacity-0 group-hover:opacity-100 focus:opacity-100\"\r\n (onClick)=\"copyPickerNode(node, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isOutputPickerNodeExpanded(node)) {\r\n <div class=\"mt-1.5\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n outputPickerTree;\r\n context: { nodes: node.children, level: level + 1 }\r\n \"\r\n />\r\n </div>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<!-- Outer wrapper: positioning context only (overflow visible) so the connected-\r\n node avatars can straddle the modal border. The inner card clips its own\r\n rounded corners + carries the shadow. -->\r\n<div class=\"relative h-full w-full text-(--p-text-color)\">\r\n <div\r\n class=\"flex h-full min-h-0 flex-col overflow-hidden rounded-2xl bg-(--p-content-background) shadow-2xl shadow-slate-950/25\"\r\n >\r\n <!-- ============================ HEADER ============================ -->\r\n <header\r\n class=\"flex shrink-0 items-center gap-3.5 border-b border-(--p-content-border-color) px-6 py-4\"\r\n >\r\n @if (currentChip(); as chip) {\r\n <span\r\n class=\"grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-sm ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"chip.color\"\r\n [style.--fp-avatar-bg]=\"chip.bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n [type]=\"chip.type\"\r\n [itemKey]=\"chip.key\"\r\n [displayName]=\"chip.label\"\r\n [metadata]=\"chip.metadata\"\r\n />\r\n </span>\r\n } @else {\r\n <span\r\n class=\"grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color) shadow-sm ring-1 ring-(--p-content-border-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [visualKey]=\"\r\n selectionKind() === 'connection'\r\n ? 'state:connection'\r\n : 'state:settings'\r\n \"\r\n />\r\n </span>\r\n }\r\n\r\n <div class=\"min-w-0 flex-1\">\r\n <h2\r\n class=\"truncate text-[17px] font-semibold tracking-tight text-(--p-text-color)\"\r\n >\r\n {{ activeDescriptorLabel() }}\r\n </h2>\r\n <p\r\n class=\"m-0 mt-1 line-clamp-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ activeDescriptorSubtitle() }}\r\n </p>\r\n </div>\r\n\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'flowplus.inspector.modal.close' | transloco\"\r\n (onClick)=\"close()\"\r\n />\r\n </header>\r\n\r\n <!-- ===================== BODY: INPUT | CONFIG | OUTPUT ===================== -->\r\n <div\r\n #resizeHost\r\n class=\"relative flex min-h-0 flex-1\"\r\n [class.select-none]=\"resizing()\"\r\n [style.cursor]=\"resizing() ? 'col-resize' : null\"\r\n >\r\n <!-- Navigation loading beat \u2014 a slim primary progress sweep across the top\r\n while the modal re-derives for a newly selected (connected) step. -->\r\n @if (navigating()) {\r\n <div\r\n class=\"pointer-events-none absolute inset-x-0 top-0 z-40 h-[3px] overflow-hidden\"\r\n >\r\n <div\r\n class=\"h-full w-2/5 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_0.9s_ease-in-out_infinite]\"\r\n ></div>\r\n </div>\r\n }\r\n\r\n @if (selectionKind() !== \"trigger\") {\r\n <!-- --------------------------- INPUT --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 shrink-0 flex-col bg-(--p-surface-50)\"\r\n [style.width.px]=\"leftWidth()\"\r\n >\r\n <div\r\n class=\"shrink-0 border-b border-(--p-content-border-color) px-5 py-4\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p\r\n class=\"text-[15px] font-bold uppercase tracking-[0.04em] text-(--p-text-color)\"\r\n >\r\n {{\r\n selectionKind() === \"trigger\"\r\n ? \"Trigger payload\"\r\n : (\"flowplus.inspector.modal.inputsTitle\" | transloco)\r\n }}\r\n </p>\r\n @if (selectionKind() === \"step\") {\r\n <div class=\"flex items-center gap-1.5\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.search-md\"\r\n [tooltip]=\"'flowplus.context.pickerPlaceholder' | transloco\"\r\n [styleClass]=\"\r\n contextSearchOpen()\r\n ? 'fp-modal-icon-button is-active'\r\n : 'fp-modal-icon-button'\r\n \"\r\n (onClick)=\"toggleContextSearch()\"\r\n />\r\n <div\r\n class=\"inline-flex rounded-lg bg-(--p-surface-100) p-0.5 text-[11px] font-semibold\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.viewSchema' | transloco\r\n \"\r\n [styleClass]=\"\r\n inputView() === 'schema'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"inputView.set('schema')\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.viewJson' | transloco\"\r\n [styleClass]=\"\r\n inputView() === 'json'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"inputView.set('json')\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (selectionKind() === \"step\" && contextSearchOpen()) {\r\n <div class=\"mt-3\">\r\n <mt-text-field\r\n [ngModel]=\"contextSearch()\"\r\n (ngModelChange)=\"contextSearch.set($event)\"\r\n [placeholder]=\"\r\n 'flowplus.context.pickerPlaceholder' | transloco\r\n \"\r\n icon=\"general.search-md\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-y-auto px-5 py-4\">\r\n @if (selectionKind() === \"step\") {\r\n @if (inputView() === \"schema\") {\r\n <div class=\"space-y-3\">\r\n <section class=\"space-y-2.5\">\r\n @if (filteredInputPickerGroups().length > 0) {\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <h3\r\n class=\"flex items-center gap-2 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.availableData\" | transloco\r\n }}\r\n </h3>\r\n </div>\r\n }\r\n\r\n @if (filteredInputPickerGroups().length === 0) {\r\n <div\r\n class=\"flex min-h-[18rem] flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <span\r\n class=\"grid h-14 w-14 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[42px]\"\r\n visualKey=\"state:input-empty\"\r\n />\r\n </span>\r\n <div class=\"space-y-1\">\r\n <p\r\n class=\"text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.noInputData\" | transloco\r\n }}\r\n </p>\r\n <p\r\n class=\"mx-auto max-w-[16rem] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.noAvailableData\"\r\n | transloco\r\n }}\r\n </p>\r\n </div>\r\n </div>\r\n } @else {\r\n <p\r\n class=\"flex items-center gap-1.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"h-3.5 w-3.5 shrink-0 fill-current opacity-70\"\r\n aria-hidden=\"true\"\r\n >\r\n <circle cx=\"5\" cy=\"4\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"4\" r=\"1.1\" />\r\n <circle cx=\"5\" cy=\"8\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"8\" r=\"1.1\" />\r\n <circle cx=\"5\" cy=\"12\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"12\" r=\"1.1\" />\r\n </svg>\r\n {{ \"flowplus.inspector.modal.dragHint\" | transloco }}\r\n </p>\r\n\r\n <div class=\"space-y-1.5\">\r\n @for (\r\n group of filteredInputPickerGroups();\r\n track group.id\r\n ) {\r\n <section\r\n class=\"overflow-hidden rounded-xl border border-(--p-content-border-color) bg-(--p-content-background)\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isGroupCollapsed(group.id)\r\n ? 'arrow.chevron-right'\r\n : 'arrow.chevron-down'\r\n \"\r\n [label]=\"group.label\"\r\n styleClass=\"fp-modal-group-toggle\"\r\n (onClick)=\"toggleGroup(group.id)\"\r\n />\r\n\r\n @if (!isGroupCollapsed(group.id)) {\r\n @if (group.children.length === 0) {\r\n <div\r\n class=\"border-t border-(--p-content-border-color) px-3 py-3 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyPickerGroupMessage(group.id) }}\r\n </div>\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n inputPickerTree;\r\n context: { nodes: group.children, level: 0 }\r\n \"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n } @else {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg border border-(--p-content-border-color) bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ inputJson() }}</pre\r\n >\r\n }\r\n } @else if (\r\n selectionKind() === \"connection\" && connectionEndpoints();\r\n as endpoints\r\n ) {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.connectionSummary\" | transloco }}\r\n </h3>\r\n <div\r\n class=\"space-y-2.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n <div>\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.source\" | transloco }}\r\n </div>\r\n <div class=\"mt-1 text-[13px] text-(--p-text-color)\">\r\n {{ endpoints.source }}\r\n </div>\r\n </div>\r\n <div>\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.target\" | transloco }}\r\n </div>\r\n <div class=\"mt-1 text-[13px] text-(--p-text-color)\">\r\n {{ endpoints.target }}\r\n </div>\r\n </div>\r\n @if (selectedConnection()!.sourcePortKey) {\r\n <div\r\n class=\"font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ selectedConnection()!.sourcePortKey }} ->\r\n {{ selectedConnection()!.targetPortKey || \"in\" }}\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n } @else if (selectionKind() === \"trigger\") {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n Trigger payload and setup\r\n </h3>\r\n <div\r\n class=\"space-y-2.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n @if (selectedTriggerPayloadSchemaJson()) {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Payload schema\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerPayloadSchemaJson() }}</pre\r\n >\r\n </div>\r\n } @else {\r\n <p\r\n class=\"m-0 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n The backend did not return a payload schema for this\r\n trigger.\r\n </p>\r\n }\r\n @if (selectedTriggerPayloadSampleJson()) {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Sample payload\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerPayloadSampleJson() }}</pre\r\n >\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"WebhookTrigger\") {\r\n <div\r\n class=\"space-y-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n <div>\r\n Expected method:\r\n {{ selectedTrigger()?.expectedHttpMethod ?? \"POST\" }}\r\n </div>\r\n <div>\r\n Content types:\r\n {{\r\n (selectedTrigger()?.allowedContentTypes ?? []).join(\r\n \", \"\r\n ) || \"Backend default\"\r\n }}\r\n </div>\r\n @if (selectedTrigger()?.exampleCurl) {\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-28 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTrigger()?.exampleCurl }}</pre\r\n >\r\n }\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"FormSubmitTrigger\") {\r\n <div\r\n class=\"rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n Pinned formVersionId:\r\n <code>{{\r\n selectedTriggerFormVersionId() ?? \"Not selected\"\r\n }}</code>\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"ScheduleTrigger\") {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Schedule config\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerConfigJson() }}</pre\r\n >\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (selectedTriggerIssues().length > 0) {\r\n <div class=\"space-y-1.5\">\r\n @for (issue of selectedTriggerIssues(); track $index) {\r\n <div\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] leading-5 text-amber-800\"\r\n >\r\n {{ resolveTranslatable(issue.message) }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n } @else if (selectionKind() === \"workflow\") {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.workflow.triggers\" | transloco }}\r\n </h3>\r\n @if (store.triggers().length > 0) {\r\n <ul class=\"space-y-1.5\">\r\n @for (trigger of store.triggers(); track trigger.id) {\r\n <li\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2.5\"\r\n >\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div>\r\n <div\r\n class=\"text-[13px] font-medium text-(--p-text-color)\"\r\n >\r\n {{ triggerLabel(trigger) }}\r\n </div>\r\n <div\r\n class=\"mt-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ trigger.type }}\r\n </div>\r\n </div>\r\n <span\r\n class=\"rounded-md px-2 py-0.5 text-[10.5px] font-medium\"\r\n [class.bg-emerald-50]=\"trigger.enabled\"\r\n [class.text-emerald-700]=\"trigger.enabled\"\r\n [class.bg-slate-100]=\"!trigger.enabled\"\r\n [class.text-slate-500]=\"!trigger.enabled\"\r\n >\r\n {{\r\n (trigger.enabled\r\n ? \"flowplus.inspector.workflow.triggerEnabled\"\r\n : \"flowplus.inspector.modal.disabled\"\r\n ) | transloco\r\n }}\r\n </span>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.workflow.noTriggers\" | transloco }}\r\n </p>\r\n }\r\n </section>\r\n } @else {\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.noSelectionHint\" | transloco }}\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n\r\n <!-- resize handle: INPUT | CONFIG -->\r\n <div\r\n class=\"fp-resizer\"\r\n [class.is-active]=\"resizing() === 'left'\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n (pointerdown)=\"startResize('left', $event)\"\r\n (pointermove)=\"onResizeMove($event)\"\r\n (pointerup)=\"endResize($event)\"\r\n (pointercancel)=\"endResize($event)\"\r\n >\r\n <span class=\"fp-resizer-grip\" aria-hidden=\"true\"></span>\r\n </div>\r\n }\r\n\r\n <!-- --------------------------- CONFIGURATION --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 min-w-0 flex-1 flex-col bg-(--p-content-background)\"\r\n >\r\n @if (selectionKind() === \"trigger\" && selectedTrigger(); as trigger) {\r\n <fp-automation-smart-editor [trigger]=\"trigger\" mode=\"modal\" />\r\n } @else {\r\n <fp-inspector-shell\r\n mode=\"modal\"\r\n (stepChange)=\"onStepChange($event)\"\r\n (connectionChange)=\"onConnectionChange($event)\"\r\n (workflowChange)=\"onWorkflowChange($event)\"\r\n />\r\n }\r\n </section>\r\n\r\n <!-- resize handle: CONFIG | OUTPUT -->\r\n <div\r\n class=\"fp-resizer\"\r\n [class.is-active]=\"resizing() === 'right'\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n (pointerdown)=\"startResize('right', $event)\"\r\n (pointermove)=\"onResizeMove($event)\"\r\n (pointerup)=\"endResize($event)\"\r\n (pointercancel)=\"endResize($event)\"\r\n >\r\n <span class=\"fp-resizer-grip\" aria-hidden=\"true\"></span>\r\n </div>\r\n\r\n <!-- --------------------------- OUTPUT --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 shrink-0 flex-col bg-(--p-surface-50)\"\r\n [style.width.px]=\"rightWidth()\"\r\n >\r\n <div\r\n class=\"shrink-0 border-b border-(--p-content-border-color) px-5 py-4\"\r\n >\r\n @if (selectionKind() === \"step\" && mockEditorOpen()) {\r\n <!-- EDIT OUTPUT replaces the OUTPUT header while pinning mock data -->\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p class=\"text-[13.5px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.editOutput\" | transloco }}\r\n </p>\r\n <div class=\"flex items-center gap-1.5\">\r\n <mt-button\r\n variant=\"outlined\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.cancel' | transloco\"\r\n (onClick)=\"cancelMockEditor()\"\r\n />\r\n <mt-button\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.save' | transloco\"\r\n (onClick)=\"saveMock()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p\r\n class=\"text-[15px] font-bold uppercase tracking-[0.04em] text-(--p-text-color)\"\r\n >\r\n {{\r\n selectionKind() === \"trigger\"\r\n ? \"Trigger runtime\"\r\n : (\"flowplus.inspector.modal.outputsTitle\" | transloco)\r\n }}\r\n </p>\n @if (selectionKind() === \"step\") {\n <div class=\"flex shrink-0 items-center gap-2\">\n @if (hasOutputData()) {\n <div\n class=\"inline-flex rounded-lg bg-(--p-surface-100) p-0.5 text-[11px] font-semibold\"\n >\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"\n 'flowplus.inspector.modal.viewSchema' | transloco\n \"\n [styleClass]=\"\n outputView() === 'schema'\n ? 'fp-modal-segment-button is-active'\n : 'fp-modal-segment-button'\n \"\n (onClick)=\"outputView.set('schema')\"\n />\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"'flowplus.inspector.modal.viewJson' | transloco\"\n [styleClass]=\"\n outputView() === 'json'\n ? 'fp-modal-segment-button is-active'\n : 'fp-modal-segment-button'\n \"\n (onClick)=\"outputView.set('json')\"\n />\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-y-auto\">\r\n @if (selectionKind() === \"step\") {\r\n @if (mockEditorOpen()) {\r\n <!-- ----- mock data editor (header lives in the panel header) ----- -->\r\n <div class=\"flex h-full min-h-0 flex-col gap-2 p-4\">\r\n <div\r\n class=\"fp-code-frame flex min-h-0 flex-1 overflow-hidden rounded-xl border bg-(--p-surface-100) p-2 transition\"\r\n [style.borderColor]=\"\r\n mockError() ? '#f43f5e' : 'var(--p-content-border-color)'\r\n \"\r\n >\r\n <mt-textarea-field\r\n class=\"min-h-0 flex-1 [&_textarea]:fp-scroll [&_textarea]:min-h-[18rem] [&_textarea]:resize-none [&_textarea]:bg-transparent [&_textarea]:font-mono [&_textarea]:text-[12px] [&_textarea]:leading-5\"\r\n [field]=\"false\"\r\n [ngModel]=\"mockText()\"\r\n (ngModelChange)=\"mockText.set($event ?? '')\"\r\n [pInputs]=\"mockTextareaInputs\"\r\n [maxLength]=\"null\"\r\n rows=\"18\"\r\n />\r\n </div>\r\n @if (mockError()) {\r\n <p\r\n class=\"flex items-center gap-1.5 text-[11.5px] text-rose-600\"\r\n >\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3.5 w-3.5 fill-none stroke-current stroke-[2]\"\r\n >\r\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\r\n <path d=\"M12 8v4M12 16h.01\" stroke-linecap=\"round\" />\r\n </svg>\r\n {{ mockError() }}\r\n </p>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"flex min-h-full flex-col gap-3 px-5 py-5\">\n <div class=\"flex shrink-0 justify-end\">\n <mt-button\n severity=\"primary\"\n size=\"small\"\n icon=\"general.play\"\n [label]=\"'flowplus.inspector.modal.executeStep' | transloco\"\n [loading]=\"runningTest()\"\n [disabled]=\"runningTest() || !supportsTestRun()\"\n (onClick)=\"executeStep()\"\n />\n </div>\n @if (hasOutputData()) {\n <!-- ----- output payload ----- -->\n <div class=\"space-y-3\">\n @if (outputIsStale()) {\n <p\n class=\"m-0 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-2 text-[11.5px] leading-5 text-amber-800\"\n >\n Output may change because this step configuration\n changed. Execute again to refresh it.\n </p>\n }\n @if (outputIsMock()) {\n <div\n class=\"flex items-center justify-between gap-2 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-2.5 py-1.5\"\n >\r\n <span\r\n class=\"flex items-center gap-1.5 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[18px]\"\r\n visualKey=\"state:pinned-output\"\r\n />\r\n {{\r\n \"flowplus.inspector.modal.mockPinned\" | transloco\r\n }}\r\n </span>\r\n <div class=\"flex items-center gap-1\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.edit' | transloco\r\n \"\r\n (onClick)=\"openMockEditor()\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.clear' | transloco\r\n \"\r\n (onClick)=\"clearMock()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (outputFileResults().length > 0) {\r\n <div class=\"space-y-2\">\r\n @for (file of outputFileResults(); track file.key) {\r\n <fp-automation-file-card\r\n [file]=\"file\"\r\n [inspectable]=\"canInspectOutputFile(file)\"\r\n [downloadable]=\"canDownloadOutputFile(file)\"\r\n (inspect)=\"inspectSelectedNodeData(false)\"\r\n (download)=\"downloadOutputFile(file)\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @if (outputView() === \"json\") {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg border border-(--p-content-border-color) bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ outputJson() }}</pre\r\n >\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n outputPickerTree;\r\n context: { nodes: outputPickerNodes(), level: 0 }\r\n \"\r\n />\r\n }\r\n </div>\r\n } @else {\r\n <!-- ----- empty state ----- -->\n <div\n class=\"flex flex-col items-center justify-center gap-4 rounded-md border border-surface-200 bg-surface-0 px-6 py-8 text-center\"\n >\n <span\r\n class=\"grid h-14 w-14 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[42px]\"\r\n visualKey=\"state:output-empty\"\r\n />\r\n </span>\r\n <div class=\"space-y-1\">\r\n <p\r\n class=\"text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ emptyOutputTitle() }}\r\n </p>\r\n <p\r\n class=\"text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyOutputHint() }}\r\n </p>\r\n </div>\r\n <div class=\"flex flex-col items-center gap-2\">\n <mt-button\n variant=\"text\"\n severity=\"primary\"\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.setMockData' | transloco\r\n \"\r\n (onClick)=\"openMockEditor()\"\r\n />\r\n </div>\r\n @if (!supportsTestRun()) {\n <p\n class=\"max-w-[16rem] rounded-lg bg-(--p-surface-100) px-3 py-2 text-[11px] leading-5 text-(--p-text-muted-color)\"\n >\n {{ testRunUnavailableHint() }}\n </p>\n }\n </div>\r\n }\r\n </div>\r\n }\r\n } @else if (\r\n selectionKind() === \"trigger\" && selectedTrigger();\r\n as trigger\r\n ) {\r\n <div class=\"space-y-3 px-5 py-5\">\r\n <section\r\n class=\"rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <header\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <h3\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Latest run\r\n </h3>\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.link-external-02\"\r\n [tooltip]=\"'Open full run'\"\r\n [disabled]=\"!selectedTriggerRuntimeState()?.executionId\"\r\n (onClick)=\"openSelectedRuntimeRun()\"\r\n />\r\n </header>\r\n <div class=\"p-3\">\r\n @if (selectedTriggerRuntimeState(); as state) {\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2 py-1 text-[11.5px] font-semibold text-(--p-text-color)\"\r\n >{{ state.status }}</span\r\n >\r\n @if (state.executionId) {\r\n <span\r\n class=\"font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n #{{ state.executionId }}\r\n </span>\r\n }\r\n @if (\r\n state.durationMs !== null &&\r\n state.durationMs !== undefined\r\n ) {\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ state.durationMs }} ms\r\n </span>\r\n }\r\n </div>\r\n @if (state.errorMessage) {\r\n <p\r\n class=\"mt-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ state.errorMessage }}\r\n </p>\r\n }\r\n } @else {\r\n <p\r\n class=\"m-0 text-[12.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No runtime state is loaded for this trigger yet. Execute a\r\n real run or open the executions page for history.\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else if (selectionKind() === \"connection\") {\r\n <div class=\"px-4 py-4\">\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.tabs.routing\" | transloco }}\r\n </h3>\r\n <div\r\n class=\"space-y-2 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n @if (selectedConnectionActionsLabel()) {\r\n <div class=\"font-mono text-[11.5px] text-(--p-text-color)\">\r\n {{ selectedConnectionActionsLabel() }}\r\n </div>\r\n } @else {\r\n <div class=\"text-[12.5px] text-(--p-text-muted-color)\">\r\n {{\r\n \"flowplus.inspector.modal.noConfiguredOutputs\"\r\n | transloco\r\n }}\r\n </div>\r\n }\r\n @if (selectedConnection()!.expressionText) {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedConnection()!.expressionText }}</pre\r\n >\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else if (selectionKind() === \"workflow\") {\r\n <div class=\"px-4 py-4\">\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.workflowHealth\" | transloco }}\r\n </h3>\r\n <div class=\"grid grid-cols-3 gap-2\">\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.errorsLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-xl font-semibold text-(--p-text-color)\"\r\n >\r\n {{ workflowHealth().errors }}\r\n </div>\r\n </div>\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.warningsLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-xl font-semibold text-(--p-text-color)\"\r\n >\r\n {{ workflowHealth().warnings }}\r\n </div>\r\n </div>\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.publishLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-[13px] font-semibold\"\r\n [class.text-emerald-600]=\"workflowHealth().canPublish\"\r\n [class.text-amber-600]=\"!workflowHealth().canPublish\"\r\n >\r\n {{\r\n (workflowHealth().canPublish\r\n ? \"flowplus.inspector.modal.publishReady\"\r\n : \"flowplus.inspector.modal.publishBlocked\"\r\n ) | transloco\r\n }}\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n </div>\r\n } @else {\r\n <div class=\"px-4 py-4\">\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.noSelectionHint\" | transloco }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n\r\n <!-- Connected-node avatars \u2014 straddle the modal border (half in / half out),\r\n vertically centered. Rendered outside the clipping card so they protrude. -->\r\n @if (selectionKind() === \"step\") {\r\n @if (previousNeighbours().length > 0) {\r\n <div\r\n class=\"pointer-events-none absolute inset-y-0 start-0 z-50 flex -translate-x-1/2 flex-col items-center justify-center gap-3 rtl:translate-x-1/2\"\r\n >\r\n @for (n of previousNeighbours(); track n.stepId) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n edgeTab;\r\n context: { $implicit: n, side: 'start' }\r\n \"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (nextNeighbours().length > 0) {\r\n <div\r\n class=\"pointer-events-none absolute inset-y-0 end-0 z-50 flex translate-x-1/2 flex-col items-center justify-center gap-3 rtl:-translate-x-1/2\"\r\n >\r\n @for (n of nextNeighbours(); track n.stepId) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n edgeTab;\r\n context: { $implicit: n, side: 'end' }\r\n \"\r\n />\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n</div>\r\n" }]
|
|
23672
|
+
], host: { class: 'block h-full' }, template: "<!-- A connected node action sitting on the modal's outer edge.\r\n The same shared automation icon renderer used by canvas/picker is used\r\n here so modal-side node shortcuts keep one visual source of truth.\r\n `side` = 'start' for upstream nodes that feed in, 'end' for downstream\r\n nodes this step feeds. -->\r\n<ng-template #edgeTab let-n>\r\n <span\r\n class=\"fp-edge-tab pointer-events-auto\"\r\n [style.--fp-edge-tab-color]=\"n.color\"\r\n [style.--fp-edge-tab-bg]=\"n.bg\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"fp-edge-tab-button\"\r\n [attr.aria-label]=\"n.label\"\r\n [mtTooltip]=\"n.label\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n (click)=\"navigateToStep(n.stepId)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[39px]\"\r\n [type]=\"n.type\"\r\n [itemKey]=\"n.key\"\r\n [displayName]=\"n.label\"\r\n [metadata]=\"n.metadata\"\r\n />\r\n </button>\r\n </span>\r\n</ng-template>\r\n\r\n<!-- Type glyph badge for a context value (string / number / date / \u2026). -->\r\n<ng-template #typeBadge let-kind>\r\n <span\r\n class=\"flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-content-background) px-1.5 font-mono text-[12px] font-bold leading-none text-(--p-text-color) shadow-sm\"\r\n >\r\n @switch (kind) {\r\n @case (\"string\") {\r\n T\r\n }\r\n @case (\"number\") {\r\n #\r\n }\r\n @case (\"object\") {\r\n {}\r\n }\r\n @case (\"array\") {\r\n [ ]\r\n }\r\n @case (\"boolean\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2.4]\"\r\n >\r\n <rect x=\"3\" y=\"7\" width=\"18\" height=\"10\" rx=\"5\" />\r\n <circle cx=\"9\" cy=\"12\" r=\"2\" fill=\"currentColor\" stroke=\"none\" />\r\n </svg>\r\n }\r\n @case (\"date\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <rect x=\"4\" y=\"5\" width=\"16\" height=\"16\" rx=\"2.5\" />\r\n <path d=\"M4 9h16M8 3v4M16 3v4\" stroke-linecap=\"round\" />\r\n </svg>\r\n }\r\n @case (\"file\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <path\r\n d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z\"\r\n />\r\n <path d=\"M14 3v5h5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\r\n </svg>\r\n }\r\n @case (\"secret\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <rect x=\"5\" y=\"11\" width=\"14\" height=\"9\" rx=\"2\" />\r\n <path d=\"M8 11V8a4 4 0 0 1 8 0v3\" stroke-linecap=\"round\" />\r\n </svg>\r\n }\r\n @case (\"reference\") {\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3 w-3 fill-none stroke-current stroke-[2]\"\r\n >\r\n <path\r\n d=\"M7 17 17 7M9 7h8v8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n }\r\n @default {\r\n \u2022\r\n }\r\n }\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #inputPickerTree let-nodes=\"nodes\" let-level=\"level\">\r\n <ul class=\"border-t border-(--p-content-border-color)\">\r\n @for (node of nodes; track node.id) {\r\n <li class=\"border-b border-(--p-content-border-color) last:border-b-0\">\r\n <div\r\n [fpDragData]=\"node.rawExpression\"\r\n [mtTooltip]=\"pickerNodeTooltip(node)\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n class=\"fp-data-row group flex cursor-grab items-center gap-1.5 px-2.5 py-2.5 transition duration-200 ease-out hover:bg-(--p-surface-50) active:cursor-grabbing\"\r\n [style.padding-inline-start.rem]=\"0.45 + level * 0.5\"\r\n >\r\n @if (node.children.length > 0) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isInputPickerNodeExpanded(node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n [tooltip]=\"\r\n isInputPickerNodeExpanded(node) ? 'Collapse' : 'Expand'\r\n \"\r\n styleClass=\"fp-modal-tree-toggle !h-6 !w-6\"\r\n (onClick)=\"toggleInputPickerNode(node)\"\r\n />\r\n } @else {\r\n <span class=\"h-6 w-6 shrink-0\"></span>\r\n }\r\n\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n <span\r\n class=\"min-w-0 truncate text-[14px] font-semibold leading-5 text-(--p-text-color)\"\r\n [title]=\"node.label\"\r\n >{{ node.label }}</span\r\n >\r\n @if (node.availability === \"masked\") {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-error))]/12 px-2 py-0.5 text-[10.5px] font-semibold text-[rgb(var(--fp-error))]\"\r\n >\r\n secret\r\n </span>\r\n }\r\n </div>\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [tooltip]=\"\r\n copiedPickerNodeId() === node.id ? 'Copied' : 'Copy expression'\r\n \"\r\n styleClass=\"!h-6 !w-6 shrink-0 opacity-0 group-hover:opacity-100 focus:opacity-100\"\r\n (onClick)=\"copyPickerNode(node, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isInputPickerNodeExpanded(node)) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n inputPickerTree;\r\n context: { nodes: node.children, level: level + 1 }\r\n \"\r\n />\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<ng-template #outputPickerTree let-nodes=\"nodes\" let-level=\"level\">\r\n <ul class=\"space-y-1.5\">\r\n @for (node of nodes; track node.id) {\r\n <li>\r\n <div\r\n [mtTooltip]=\"outputNodeTooltip(node)\"\r\n tooltipPosition=\"left\"\r\n appendTo=\"body\"\r\n class=\"group flex min-w-0 items-center gap-1.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-2.5 py-2.5 shadow-sm transition duration-200 hover:bg-(--p-surface-50)\"\r\n [style.margin-inline-start.rem]=\"level * 0.55\"\r\n >\r\n @if (node.children.length > 0) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isOutputPickerNodeExpanded(node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n [tooltip]=\"\r\n isOutputPickerNodeExpanded(node) ? 'Collapse' : 'Expand'\r\n \"\r\n styleClass=\"fp-modal-tree-toggle !h-6 !w-6\"\r\n (onClick)=\"toggleOutputPickerNode(node)\"\r\n />\r\n } @else {\r\n <span class=\"h-6 w-6 shrink-0\"></span>\r\n }\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n typeBadge;\r\n context: { $implicit: fieldKind(node.type) }\r\n \"\r\n />\r\n\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-[13.5px] font-semibold leading-5 text-(--p-text-color)\"\r\n [title]=\"node.label\"\r\n >\r\n {{ node.label }}\r\n </span>\r\n @if (outputNodePreview(node)) {\r\n <span\r\n class=\"min-w-0 max-w-[45%] truncate font-mono text-[12px] text-(--p-text-muted-color)\"\r\n [title]=\"outputNodePreview(node)\"\r\n >\r\n {{ outputNodePreview(node) }}\r\n </span>\r\n }\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [tooltip]=\"\r\n copiedPickerNodeId() === node.id ? 'Copied' : 'Copy expression'\r\n \"\r\n styleClass=\"!h-6 !w-6 shrink-0 opacity-0 group-hover:opacity-100 focus:opacity-100\"\r\n (onClick)=\"copyPickerNode(node, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isOutputPickerNodeExpanded(node)) {\r\n <div class=\"mt-1.5\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n outputPickerTree;\r\n context: { nodes: node.children, level: level + 1 }\r\n \"\r\n />\r\n </div>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n\r\n<!-- Outer wrapper: positioning context only (overflow visible) so the connected-\r\n node avatars can straddle the modal border. The inner card clips its own\r\n rounded corners + carries the shadow. -->\r\n<div class=\"relative h-full w-full text-(--p-text-color)\">\r\n <div\r\n class=\"flex h-full min-h-0 flex-col overflow-hidden rounded-2xl bg-(--p-content-background) shadow-2xl shadow-slate-950/25\"\r\n >\r\n <!-- ============================ HEADER ============================ -->\r\n <header\r\n class=\"flex shrink-0 items-center gap-3.5 border-b border-(--p-content-border-color) px-6 py-4\"\r\n >\r\n @if (currentChip(); as chip) {\r\n <span\r\n class=\"grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-sm ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"chip.color\"\r\n [style.--fp-avatar-bg]=\"chip.bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n [type]=\"chip.type\"\r\n [itemKey]=\"chip.key\"\r\n [displayName]=\"chip.label\"\r\n [metadata]=\"chip.metadata\"\r\n />\r\n </span>\r\n } @else {\r\n <span\r\n class=\"grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color) shadow-sm ring-1 ring-(--p-content-border-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [visualKey]=\"\r\n selectionKind() === 'connection'\r\n ? 'state:connection'\r\n : 'state:settings'\r\n \"\r\n />\r\n </span>\r\n }\r\n\r\n <div class=\"min-w-0 flex-1\">\r\n <h2\r\n class=\"truncate text-[17px] font-semibold tracking-tight text-(--p-text-color)\"\r\n >\r\n {{ activeDescriptorLabel() }}\r\n </h2>\r\n <p\r\n class=\"m-0 mt-1 line-clamp-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ activeDescriptorSubtitle() }}\r\n </p>\r\n </div>\r\n\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'flowplus.inspector.modal.close' | transloco\"\r\n (onClick)=\"close()\"\r\n />\r\n </header>\r\n\r\n <!-- ===================== BODY: INPUT | CONFIG | OUTPUT ===================== -->\r\n <div\r\n #resizeHost\r\n class=\"relative flex min-h-0 flex-1\"\r\n [class.select-none]=\"resizing()\"\r\n [style.cursor]=\"resizing() ? 'col-resize' : null\"\r\n >\r\n <!-- Navigation loading beat \u2014 a slim primary progress sweep across the top\r\n while the modal re-derives for a newly selected (connected) step. -->\r\n @if (navigating()) {\r\n <div\r\n class=\"pointer-events-none absolute inset-x-0 top-0 z-40 h-[3px] overflow-hidden\"\r\n >\r\n <div\r\n class=\"h-full w-2/5 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_0.9s_ease-in-out_infinite]\"\r\n ></div>\r\n </div>\r\n }\r\n\r\n @if (selectionKind() !== \"trigger\") {\r\n <!-- --------------------------- INPUT --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 shrink-0 flex-col bg-(--p-surface-50)\"\r\n [style.width.px]=\"leftWidth()\"\r\n >\r\n <div\r\n class=\"shrink-0 border-b border-(--p-content-border-color) px-5 py-4\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p\r\n class=\"text-[15px] font-bold uppercase tracking-[0.04em] text-(--p-text-color)\"\r\n >\r\n {{\r\n selectionKind() === \"trigger\"\r\n ? \"Trigger payload\"\r\n : (\"flowplus.inspector.modal.inputsTitle\" | transloco)\r\n }}\r\n </p>\r\n @if (selectionKind() === \"step\") {\r\n <div class=\"flex items-center gap-1.5\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.search-md\"\r\n [tooltip]=\"'flowplus.context.pickerPlaceholder' | transloco\"\r\n [styleClass]=\"\r\n contextSearchOpen()\r\n ? 'fp-modal-icon-button is-active'\r\n : 'fp-modal-icon-button'\r\n \"\r\n (onClick)=\"toggleContextSearch()\"\r\n />\r\n <div\r\n class=\"inline-flex rounded-lg bg-(--p-surface-100) p-0.5 text-[11px] font-semibold\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.viewSchema' | transloco\r\n \"\r\n [styleClass]=\"\r\n inputView() === 'schema'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"inputView.set('schema')\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.viewJson' | transloco\"\r\n [styleClass]=\"\r\n inputView() === 'json'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"inputView.set('json')\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (selectionKind() === \"step\" && contextSearchOpen()) {\r\n <div class=\"mt-3\">\r\n <mt-text-field\r\n [ngModel]=\"contextSearch()\"\r\n (ngModelChange)=\"contextSearch.set($event)\"\r\n [placeholder]=\"\r\n 'flowplus.context.pickerPlaceholder' | transloco\r\n \"\r\n icon=\"general.search-md\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-y-auto px-5 py-4\">\r\n @if (selectionKind() === \"step\") {\r\n @if (inputView() === \"schema\") {\r\n <div class=\"space-y-3\">\r\n <section class=\"space-y-2.5\">\r\n @if (filteredInputPickerGroups().length > 0) {\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <h3\r\n class=\"flex items-center gap-2 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.availableData\" | transloco\r\n }}\r\n </h3>\r\n </div>\r\n }\r\n\r\n @if (filteredInputPickerGroups().length === 0) {\r\n <div\r\n class=\"flex min-h-[18rem] flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <span\r\n class=\"grid h-14 w-14 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[42px]\"\r\n visualKey=\"state:input-empty\"\r\n />\r\n </span>\r\n <div class=\"space-y-1\">\r\n <p\r\n class=\"text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.noInputData\" | transloco\r\n }}\r\n </p>\r\n <p\r\n class=\"mx-auto max-w-[16rem] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{\r\n \"flowplus.inspector.modal.noAvailableData\"\r\n | transloco\r\n }}\r\n </p>\r\n </div>\r\n </div>\r\n } @else {\r\n <p\r\n class=\"flex items-center gap-1.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"h-3.5 w-3.5 shrink-0 fill-current opacity-70\"\r\n aria-hidden=\"true\"\r\n >\r\n <circle cx=\"5\" cy=\"4\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"4\" r=\"1.1\" />\r\n <circle cx=\"5\" cy=\"8\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"8\" r=\"1.1\" />\r\n <circle cx=\"5\" cy=\"12\" r=\"1.1\" />\r\n <circle cx=\"9\" cy=\"12\" r=\"1.1\" />\r\n </svg>\r\n {{ \"flowplus.inspector.modal.dragHint\" | transloco }}\r\n </p>\r\n\r\n <div class=\"space-y-1.5\">\r\n @for (\r\n group of filteredInputPickerGroups();\r\n track group.id\r\n ) {\r\n <section\r\n class=\"overflow-hidden rounded-xl border border-(--p-content-border-color) bg-(--p-content-background)\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n isGroupCollapsed(group.id)\r\n ? 'arrow.chevron-right'\r\n : 'arrow.chevron-down'\r\n \"\r\n [label]=\"group.label\"\r\n styleClass=\"fp-modal-group-toggle\"\r\n (onClick)=\"toggleGroup(group.id)\"\r\n />\r\n\r\n @if (!isGroupCollapsed(group.id)) {\r\n @if (group.children.length === 0) {\r\n <div\r\n class=\"border-t border-(--p-content-border-color) px-3 py-3 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyPickerGroupMessage(group.id) }}\r\n </div>\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n inputPickerTree;\r\n context: { nodes: group.children, level: 0 }\r\n \"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n } @else {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg border border-(--p-content-border-color) bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ inputJson() }}</pre\r\n >\r\n }\r\n } @else if (\r\n selectionKind() === \"connection\" && connectionEndpoints();\r\n as endpoints\r\n ) {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.connectionSummary\" | transloco }}\r\n </h3>\r\n <div\r\n class=\"space-y-2.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n <div>\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.source\" | transloco }}\r\n </div>\r\n <div class=\"mt-1 text-[13px] text-(--p-text-color)\">\r\n {{ endpoints.source }}\r\n </div>\r\n </div>\r\n <div>\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.target\" | transloco }}\r\n </div>\r\n <div class=\"mt-1 text-[13px] text-(--p-text-color)\">\r\n {{ endpoints.target }}\r\n </div>\r\n </div>\r\n @if (selectedConnection()!.sourcePortKey) {\r\n <div\r\n class=\"font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ selectedConnection()!.sourcePortKey }} ->\r\n {{ selectedConnection()!.targetPortKey || \"in\" }}\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n } @else if (selectionKind() === \"trigger\") {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n Trigger payload and setup\r\n </h3>\r\n <div\r\n class=\"space-y-2.5 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n @if (selectedTriggerPayloadSchemaJson()) {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Payload schema\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerPayloadSchemaJson() }}</pre\r\n >\r\n </div>\r\n } @else {\r\n <p\r\n class=\"m-0 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n The backend did not return a payload schema for this\r\n trigger.\r\n </p>\r\n }\r\n @if (selectedTriggerPayloadSampleJson()) {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Sample payload\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerPayloadSampleJson() }}</pre\r\n >\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"WebhookTrigger\") {\r\n <div\r\n class=\"space-y-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n <div>\r\n Expected method:\r\n {{ selectedTrigger()?.expectedHttpMethod ?? \"POST\" }}\r\n </div>\r\n <div>\r\n Content types:\r\n {{\r\n (selectedTrigger()?.allowedContentTypes ?? []).join(\r\n \", \"\r\n ) || \"Backend default\"\r\n }}\r\n </div>\r\n @if (selectedTrigger()?.exampleCurl) {\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-28 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTrigger()?.exampleCurl }}</pre\r\n >\r\n }\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"FormSubmitTrigger\") {\r\n <div\r\n class=\"rounded-lg bg-(--p-surface-100) px-3 py-2 text-[12px] text-(--p-text-muted-color)\"\r\n >\r\n Pinned formVersionId:\r\n <code>{{\r\n selectedTriggerFormVersionId() ?? \"Not selected\"\r\n }}</code>\r\n </div>\r\n }\r\n @if (selectedTrigger()?.type === \"ScheduleTrigger\") {\r\n <div>\r\n <div\r\n class=\"text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Schedule config\r\n </div>\r\n <pre\r\n class=\"fp-scroll mt-1 max-h-36 overflow-auto rounded-lg bg-(--p-surface-100) p-2.5 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedTriggerConfigJson() }}</pre\r\n >\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (selectedTriggerIssues().length > 0) {\r\n <div class=\"space-y-1.5\">\r\n @for (issue of selectedTriggerIssues(); track $index) {\r\n <div\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] leading-5 text-amber-800\"\r\n >\r\n {{ resolveTranslatable(issue.message) }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n } @else if (selectionKind() === \"workflow\") {\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.workflow.triggers\" | transloco }}\r\n </h3>\r\n @if (store.triggers().length > 0) {\r\n <ul class=\"space-y-1.5\">\r\n @for (trigger of store.triggers(); track trigger.id) {\r\n <li\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-2.5\"\r\n >\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div>\r\n <div\r\n class=\"text-[13px] font-medium text-(--p-text-color)\"\r\n >\r\n {{ triggerLabel(trigger) }}\r\n </div>\r\n <div\r\n class=\"mt-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ trigger.type }}\r\n </div>\r\n </div>\r\n <span\r\n class=\"rounded-md px-2 py-0.5 text-[10.5px] font-medium\"\r\n [class.bg-emerald-50]=\"trigger.enabled\"\r\n [class.text-emerald-700]=\"trigger.enabled\"\r\n [class.bg-slate-100]=\"!trigger.enabled\"\r\n [class.text-slate-500]=\"!trigger.enabled\"\r\n >\r\n {{\r\n (trigger.enabled\r\n ? \"flowplus.inspector.workflow.triggerEnabled\"\r\n : \"flowplus.inspector.modal.disabled\"\r\n ) | transloco\r\n }}\r\n </span>\r\n </div>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.workflow.noTriggers\" | transloco }}\r\n </p>\r\n }\r\n </section>\r\n } @else {\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.noSelectionHint\" | transloco }}\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n\r\n <!-- resize handle: INPUT | CONFIG -->\r\n <div\r\n class=\"fp-resizer\"\r\n [class.is-active]=\"resizing() === 'left'\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n (pointerdown)=\"startResize('left', $event)\"\r\n (pointermove)=\"onResizeMove($event)\"\r\n (pointerup)=\"endResize($event)\"\r\n (pointercancel)=\"endResize($event)\"\r\n >\r\n <span class=\"fp-resizer-grip\" aria-hidden=\"true\"></span>\r\n </div>\r\n }\r\n\r\n <!-- --------------------------- CONFIGURATION --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 min-w-0 flex-1 flex-col bg-(--p-content-background)\"\r\n >\r\n @if (selectionKind() === \"trigger\" && selectedTrigger(); as trigger) {\r\n <fp-automation-smart-editor [trigger]=\"trigger\" mode=\"modal\" />\r\n } @else {\r\n <fp-inspector-shell\r\n mode=\"modal\"\r\n (stepChange)=\"onStepChange($event)\"\r\n (connectionChange)=\"onConnectionChange($event)\"\r\n (workflowChange)=\"onWorkflowChange($event)\"\r\n />\r\n }\r\n </section>\r\n\r\n <!-- resize handle: CONFIG | OUTPUT -->\r\n <div\r\n class=\"fp-resizer\"\r\n [class.is-active]=\"resizing() === 'right'\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n (pointerdown)=\"startResize('right', $event)\"\r\n (pointermove)=\"onResizeMove($event)\"\r\n (pointerup)=\"endResize($event)\"\r\n (pointercancel)=\"endResize($event)\"\r\n >\r\n <span class=\"fp-resizer-grip\" aria-hidden=\"true\"></span>\r\n </div>\r\n\r\n <!-- --------------------------- OUTPUT --------------------------- -->\r\n <section\r\n class=\"flex h-full min-h-0 shrink-0 flex-col bg-(--p-surface-50)\"\r\n [style.width.px]=\"rightWidth()\"\r\n >\r\n <div\r\n class=\"shrink-0 border-b border-(--p-content-border-color) px-5 py-4\"\r\n >\r\n @if (selectionKind() === \"step\" && mockEditorOpen()) {\r\n <!-- EDIT OUTPUT replaces the OUTPUT header while pinning mock data -->\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p class=\"text-[13.5px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.editOutput\" | transloco }}\r\n </p>\r\n <div class=\"flex items-center gap-1.5\">\r\n <mt-button\r\n variant=\"outlined\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.cancel' | transloco\"\r\n (onClick)=\"cancelMockEditor()\"\r\n />\r\n <mt-button\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"'flowplus.inspector.modal.save' | transloco\"\r\n (onClick)=\"saveMock()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <p\r\n class=\"text-[15px] font-bold uppercase tracking-[0.04em] text-(--p-text-color)\"\r\n >\r\n {{\r\n selectionKind() === \"trigger\"\r\n ? \"Trigger runtime\"\r\n : (\"flowplus.inspector.modal.outputsTitle\" | transloco)\r\n }}\r\n </p>\r\n @if (selectionKind() === \"step\") {\r\n <div class=\"flex shrink-0 items-center gap-2\">\r\n @if (hasOutputData()) {\r\n <div\r\n class=\"inline-flex rounded-lg bg-(--p-surface-100) p-0.5 text-[11px] font-semibold\"\r\n >\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.viewSchema' | transloco\r\n \"\r\n [styleClass]=\"\r\n outputView() === 'schema'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"outputView.set('schema')\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.viewJson' | transloco\r\n \"\r\n [styleClass]=\"\r\n outputView() === 'json'\r\n ? 'fp-modal-segment-button is-active'\r\n : 'fp-modal-segment-button'\r\n \"\r\n (onClick)=\"outputView.set('json')\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-y-auto\">\r\n @if (selectionKind() === \"step\") {\r\n @if (mockEditorOpen()) {\r\n <!-- ----- mock data editor (header lives in the panel header) ----- -->\r\n <div class=\"flex h-full min-h-0 flex-col gap-2 p-4\">\r\n <div\r\n class=\"fp-code-frame flex min-h-0 flex-1 overflow-hidden rounded-xl border bg-(--p-surface-100) p-2 transition\"\r\n [style.borderColor]=\"\r\n mockError() ? '#f43f5e' : 'var(--p-content-border-color)'\r\n \"\r\n >\r\n <mt-textarea-field\r\n class=\"min-h-0 flex-1 [&_textarea]:fp-scroll [&_textarea]:min-h-[18rem] [&_textarea]:resize-none [&_textarea]:bg-transparent [&_textarea]:font-mono [&_textarea]:text-[12px] [&_textarea]:leading-5\"\r\n [field]=\"false\"\r\n [ngModel]=\"mockText()\"\r\n (ngModelChange)=\"mockText.set($event ?? '')\"\r\n [pInputs]=\"mockTextareaInputs\"\r\n [maxLength]=\"null\"\r\n rows=\"18\"\r\n />\r\n </div>\r\n @if (mockError()) {\r\n <p\r\n class=\"flex items-center gap-1.5 text-[11.5px] text-rose-600\"\r\n >\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n class=\"h-3.5 w-3.5 fill-none stroke-current stroke-[2]\"\r\n >\r\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\r\n <path d=\"M12 8v4M12 16h.01\" stroke-linecap=\"round\" />\r\n </svg>\r\n {{ mockError() }}\r\n </p>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"flex min-h-full flex-col gap-3 px-5 py-5\">\r\n <div class=\"flex shrink-0 justify-end\">\r\n <mt-button\r\n severity=\"primary\"\r\n size=\"small\"\r\n icon=\"general.play\"\r\n [label]=\"'flowplus.inspector.modal.executeStep' | transloco\"\r\n [loading]=\"runningTest()\"\r\n [disabled]=\"runningTest() || !supportsTestRun()\"\r\n (onClick)=\"executeStep()\"\r\n />\r\n </div>\r\n @if (hasOutputData()) {\r\n <!-- ----- output payload ----- -->\r\n <div class=\"space-y-3\">\r\n @if (outputIsStale()) {\r\n <p\r\n class=\"m-0 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-2 text-[11.5px] leading-5 text-amber-800\"\r\n >\r\n Output may change because this step configuration\r\n changed. Execute again to refresh it.\r\n </p>\r\n }\r\n @if (outputIsMock()) {\r\n <div\r\n class=\"flex items-center justify-between gap-2 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-2.5 py-1.5\"\r\n >\r\n <span\r\n class=\"flex items-center gap-1.5 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[18px]\"\r\n visualKey=\"state:pinned-output\"\r\n />\r\n {{\r\n \"flowplus.inspector.modal.mockPinned\" | transloco\r\n }}\r\n </span>\r\n <div class=\"flex items-center gap-1\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.edit' | transloco\r\n \"\r\n (onClick)=\"openMockEditor()\"\r\n />\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.clear' | transloco\r\n \"\r\n (onClick)=\"clearMock()\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (outputFileResults().length > 0) {\r\n <div class=\"space-y-2\">\r\n @for (file of outputFileResults(); track file.key) {\r\n <fp-automation-file-card\r\n [file]=\"file\"\r\n [inspectable]=\"canInspectOutputFile(file)\"\r\n [downloadable]=\"canDownloadOutputFile(file)\"\r\n (inspect)=\"inspectSelectedNodeData(false)\"\r\n (download)=\"downloadOutputFile(file)\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @if (outputView() === \"json\") {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg border border-(--p-content-border-color) bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ outputJson() }}</pre\r\n >\r\n } @else {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n outputPickerTree;\r\n context: { nodes: outputPickerNodes(), level: 0 }\r\n \"\r\n />\r\n }\r\n </div>\r\n } @else {\r\n <!-- ----- empty state ----- -->\r\n <div\r\n class=\"flex flex-col items-center justify-center gap-4 rounded-md border border-surface-200 bg-surface-0 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"grid h-14 w-14 place-items-center rounded-2xl bg-(--p-surface-100) text-(--p-text-muted-color)\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[42px]\"\r\n visualKey=\"state:output-empty\"\r\n />\r\n </span>\r\n <div class=\"space-y-1\">\r\n <p\r\n class=\"text-[14px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ emptyOutputTitle() }}\r\n </p>\r\n <p\r\n class=\"text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ emptyOutputHint() }}\r\n </p>\r\n </div>\r\n <div class=\"flex flex-col items-center gap-2\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [label]=\"\r\n 'flowplus.inspector.modal.setMockData' | transloco\r\n \"\r\n (onClick)=\"openMockEditor()\"\r\n />\r\n </div>\r\n @if (!supportsTestRun()) {\r\n <p\r\n class=\"max-w-[16rem] rounded-lg bg-(--p-surface-100) px-3 py-2 text-[11px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ testRunUnavailableHint() }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n } @else if (\r\n selectionKind() === \"trigger\" && selectedTrigger();\r\n as trigger\r\n ) {\r\n <div class=\"space-y-3 px-5 py-5\">\r\n <section\r\n class=\"rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <header\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <h3\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Latest run\r\n </h3>\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.link-external-02\"\r\n [tooltip]=\"'Open full run'\"\r\n [disabled]=\"!selectedTriggerRuntimeState()?.executionId\"\r\n (onClick)=\"openSelectedRuntimeRun()\"\r\n />\r\n </header>\r\n <div class=\"p-3\">\r\n @if (selectedTriggerRuntimeState(); as state) {\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2 py-1 text-[11.5px] font-semibold text-(--p-text-color)\"\r\n >{{ state.status }}</span\r\n >\r\n @if (state.executionId) {\r\n <span\r\n class=\"font-mono text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n #{{ state.executionId }}\r\n </span>\r\n }\r\n @if (\r\n state.durationMs !== null &&\r\n state.durationMs !== undefined\r\n ) {\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ state.durationMs }} ms\r\n </span>\r\n }\r\n </div>\r\n @if (state.errorMessage) {\r\n <p\r\n class=\"mt-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ state.errorMessage }}\r\n </p>\r\n }\r\n } @else {\r\n <p\r\n class=\"m-0 text-[12.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No runtime state is loaded for this trigger yet. Execute a\r\n real run or open the executions page for history.\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else if (selectionKind() === \"connection\") {\r\n <div class=\"px-4 py-4\">\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.tabs.routing\" | transloco }}\r\n </h3>\r\n <div\r\n class=\"space-y-2 rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) p-3\"\r\n >\r\n @if (selectedConnectionActionsLabel()) {\r\n <div class=\"font-mono text-[11.5px] text-(--p-text-color)\">\r\n {{ selectedConnectionActionsLabel() }}\r\n </div>\r\n } @else {\r\n <div class=\"text-[12.5px] text-(--p-text-muted-color)\">\r\n {{\r\n \"flowplus.inspector.modal.noConfiguredOutputs\"\r\n | transloco\r\n }}\r\n </div>\r\n }\r\n @if (selectedConnection()!.expressionText) {\r\n <pre\r\n class=\"fp-scroll overflow-auto rounded-lg bg-(--p-surface-100) p-3 font-mono text-[11px] leading-5 text-(--p-text-color)\"\r\n >{{ selectedConnection()!.expressionText }}</pre\r\n >\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else if (selectionKind() === \"workflow\") {\r\n <div class=\"px-4 py-4\">\r\n <section class=\"space-y-3\">\r\n <h3 class=\"text-[13px] font-semibold text-(--p-text-color)\">\r\n {{ \"flowplus.inspector.modal.workflowHealth\" | transloco }}\r\n </h3>\r\n <div class=\"grid grid-cols-3 gap-2\">\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.errorsLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-xl font-semibold text-(--p-text-color)\"\r\n >\r\n {{ workflowHealth().errors }}\r\n </div>\r\n </div>\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.warningsLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-xl font-semibold text-(--p-text-color)\"\r\n >\r\n {{ workflowHealth().warnings }}\r\n </div>\r\n </div>\r\n <div\r\n class=\"rounded-lg border border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-center\"\r\n >\r\n <div\r\n class=\"text-[11.5px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.modal.publishLabel\" | transloco }}\r\n </div>\r\n <div\r\n class=\"mt-1.5 text-[13px] font-semibold\"\r\n [class.text-emerald-600]=\"workflowHealth().canPublish\"\r\n [class.text-amber-600]=\"!workflowHealth().canPublish\"\r\n >\r\n {{\r\n (workflowHealth().canPublish\r\n ? \"flowplus.inspector.modal.publishReady\"\r\n : \"flowplus.inspector.modal.publishBlocked\"\r\n ) | transloco\r\n }}\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n </div>\r\n } @else {\r\n <div class=\"px-4 py-4\">\r\n <p\r\n class=\"rounded-lg border border-dashed border-(--p-content-border-color) bg-(--p-content-background) px-3 py-3 text-[12.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ \"flowplus.inspector.noSelectionHint\" | transloco }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n\r\n <!-- Connected-node avatars \u2014 straddle the modal border (half in / half out),\r\n vertically centered. Rendered outside the clipping card so they protrude. -->\r\n @if (selectionKind() === \"step\") {\r\n @if (previousNeighbours().length > 0) {\r\n <div\r\n class=\"pointer-events-none absolute inset-y-0 start-0 z-50 flex -translate-x-1/2 flex-col items-center justify-center gap-3 rtl:translate-x-1/2\"\r\n >\r\n @for (n of previousNeighbours(); track n.stepId) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n edgeTab;\r\n context: { $implicit: n, side: 'start' }\r\n \"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (nextNeighbours().length > 0) {\r\n <div\r\n class=\"pointer-events-none absolute inset-y-0 end-0 z-50 flex translate-x-1/2 flex-col items-center justify-center gap-3 rtl:-translate-x-1/2\"\r\n >\r\n @for (n of nextNeighbours(); track n.stepId) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n edgeTab;\r\n context: { $implicit: n, side: 'end' }\r\n \"\r\n />\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n</div>\r\n" }]
|
|
23670
23673
|
}], ctorParameters: () => [], propDecorators: { resizeHost: [{ type: i0.ViewChild, args: ['resizeHost', { isSignal: true }] }] } });
|
|
23671
23674
|
function nearestUpstreamLoopStep(steps, connections, selectedStep) {
|
|
23672
23675
|
if (!selectedStep || selectedStep.type === 'LoopOverItems')
|
|
@@ -26667,7 +26670,7 @@ class TestRunPanelComponent {
|
|
|
26667
26670
|
return !translated || translated === key ? fallback : translated;
|
|
26668
26671
|
}
|
|
26669
26672
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TestRunPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
26670
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TestRunPanelComponent, isStandalone: true, selector: "fp-test-run-panel", viewQueries: [{ propertyName: "nodeRunNodeCellTpl", first: true, predicate: ["nodeRunNodeCellTpl"], descendants: true, isSignal: true }, { propertyName: "nodeRunStatusCellTpl", first: true, predicate: ["nodeRunStatusCellTpl"], descendants: true, isSignal: true }, { propertyName: "nodeRunDetailCellTpl", first: true, predicate: ["nodeRunDetailCellTpl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\r\n @if (triggerOptions().length === 0) {\r\n <section\r\n class=\"flex min-h-[180px] items-center justify-center rounded-md border border-surface-200 bg-surface-0 px-6 py-8 text-center\"\r\n >\r\n <div class=\"max-w-[460px]\">\r\n <span\r\n class=\"mx-auto mb-3 inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.zap-fast\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No executable trigger\r\n </p>\r\n <p class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\">\r\n {{ helperText() }}\r\n </p>\r\n </div>\r\n </section>\r\n } @else {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n aria-label=\"Run workflow\"\r\n >\r\n <header\r\n class=\"flex flex-none flex-col border-b border-surface-200 bg-surface-50\"\r\n >\r\n <div class=\"flex flex-none flex-wrap items-center gap-2 px-3 py-2.5\">\r\n <div class=\"min-w-0 flex-1\">\r\n <div class=\"flex min-w-0 flex-wrap items-center gap-2\">\r\n @if (\r\n triggerOptions().length === 1 && selectedTrigger();\r\n as trigger\r\n ) {\r\n <span\r\n class=\"max-w-[20rem] truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n [title]=\"cleanTriggerLabel(trigger)\"\r\n >\r\n {{ cleanTriggerLabel(trigger) }}\r\n </span>\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ triggerTypeLabel(trigger.type) }}\r\n </span>\r\n @if (trigger.startNodeKey) {\r\n <span\r\n class=\"max-w-[12rem] truncate text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"'Starts at ' + trigger.startNodeKey\"\r\n >\r\n Starts at {{ trigger.startNodeKey }}\r\n </span>\r\n }\r\n } @else {\r\n <div class=\"min-w-[16rem] max-w-[28rem] flex-1\">\r\n <mt-select-field\r\n [ngModel]=\"selectedTriggerId()\"\r\n (ngModelChange)=\"onTriggerChange($event)\"\r\n label=\"Trigger\"\r\n [options]=\"triggerOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n }\r\n\r\n <span\r\n [class]=\"\r\n 'inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-[11px] font-semibold ' +\r\n triggerStatusTone()\r\n \"\r\n >\r\n {{\r\n statusLabel(\r\n runtimeStatus() || selectedTrigger()?.executionStatus\r\n )\r\n }}\r\n </span>\r\n\r\n @if (runtimeAccepted()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-color)\"\r\n >\r\n Accepted\r\n </span>\r\n }\r\n @if (routeOutputSummary()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-color)\"\r\n >\r\n {{ routeOutputSummary() }}\r\n </span>\r\n }\r\n @if (runDurationLabel()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ runDurationLabel() }}\r\n </span>\r\n }\r\n @for (fact of compactRunFacts(); track fact) {\r\n <span\r\n class=\"max-w-[18rem] truncate rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n [title]=\"fact\"\r\n >\r\n {{ fact }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <p\r\n class=\"m-0 mt-1 truncate text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n [title]=\"helperText()\"\r\n >\r\n {{ helperText() }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"flex flex-none flex-wrap items-center gap-2\">\r\n @if (selectedTriggerExecutable()) {\n <mt-button\n severity=\"primary\"\n size=\"small\"\n icon=\"media.play\"\n [label]=\"runtimeRunning() ? 'Executing...' : 'Execute workflow'\"\n [loading]=\"runtimeRunning()\"\n [disabled]=\"runtimeRunning()\"\n [tooltip]=\"helperText()\"\n (onClick)=\"execute()\"\n />\n @if (canStopExecution()) {\n <mt-button\n variant=\"outlined\"\n severity=\"danger\"\n size=\"small\"\n icon=\"media.stop-circle\"\n [label]=\"\n runtimeCanceling() ? 'Stopping...' : 'Stop execution'\n \"\n [loading]=\"runtimeCanceling()\"\n [disabled]=\"runtimeCanceling()\"\n [tooltip]=\"'Cancel the active builder execution'\"\n (onClick)=\"stopExecution()\"\n />\n }\n } @else {\n <mt-button\n variant=\"outlined\"\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n label=\"Fix trigger\"\r\n [tooltip]=\"helperText()\"\r\n (onClick)=\"openTriggerSetup()\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (runtimeDisconnected()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n Runtime disconnected. The canvas animation was stopped because the\r\n backend status could not be refreshed.\r\n </div>\r\n } @else if (runtimeError()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ runtimeError() }}\r\n </div>\r\n } @else if (lastError()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ lastError() }}\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"flex flex-none items-center border-t border-surface-200 bg-(--p-content-background) px-3\"\r\n >\r\n <div class=\"min-w-0 flex-1 overflow-hidden\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [fluid]=\"true\"\r\n [options]=\"runPanelTabOptions()\"\r\n [active]=\"runPanelTab()\"\r\n (onChange)=\"setRunPanelTab($event)\"\r\n />\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-auto p-3\">\r\n @switch (runPanelTab()) {\r\n @case (\"timeline\") {\r\n @if (timelineRows().length === 0) {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"layout.list\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No timeline yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Execute the trigger to see the runtime path and ordered engine\r\n events.\r\n </p>\r\n </div>\r\n } @else {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Execution timeline\r\n </p>\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ timelineRows().length }} events\r\n </span>\r\n </div>\r\n <ol class=\"m-0 list-none space-y-2 p-3\">\r\n @for (row of timelineRows(); track row.key) {\r\n <li class=\"flex gap-2\">\r\n <span\r\n [class]=\"\r\n 'mt-1.5 size-2 rounded-full ring-4 ring-(--p-surface-100) ' +\r\n timelineToneClass(row.tone)\r\n \"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div class=\"flex min-w-0 items-center gap-1.5\">\r\n <mt-icon\r\n [icon]=\"row.icon\"\r\n class=\"shrink-0 text-(--p-text-muted-color) [&_svg]:size-3.5\"\r\n />\r\n <span\r\n class=\"truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.time) {\r\n <span\r\n class=\"shrink-0 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ row.time | date: \"shortTime\" }}\r\n </span>\r\n }\r\n </div>\r\n @if (row.detail) {\r\n <p\r\n class=\"m-0 truncate text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"row.detail\"\r\n >\r\n {{ row.detail }}\r\n </p>\r\n }\r\n </div>\r\n </li>\r\n }\r\n </ol>\r\n </section>\r\n }\r\n }\r\n\r\n @case (\"nodes\") {\r\n @if (nodeRunRows().length === 0) {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"layout.table\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No node results yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Node status, duration, selected route, and failures appear\r\n here after execution detail is loaded.\r\n </p>\r\n </div>\r\n } @else {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Node results\r\n </p>\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ nodeRunRows().length }} nodes\r\n </span>\r\n </div>\r\n <div class=\"p-2\">\r\n <mt-table\r\n [data]=\"nodeRunRows()\"\r\n [columns]=\"nodeRunColumns()\"\r\n dataKey=\"key\"\r\n size=\"small\"\r\n [noCard]=\"true\"\r\n [tableLayout]=\"'fixed'\"\r\n [pageSize]=\"10\"\r\n [rowsPerPageOptions]=\"[10, 20, 50]\"\r\n [alwaysShowPaginator]=\"nodeRunRows().length > 10\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n\r\n @case (\"output\") {\r\n @if (outputPreview()) {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Output preview\r\n </p>\r\n @if (runStartedLabel(); as startedAt) {\r\n <span\r\n class=\"max-w-[18rem] truncate text-[11px] text-(--p-text-muted-color)\"\r\n [title]=\"startedAt\"\r\n >\r\n Started {{ startedAt }}\r\n </span>\r\n }\r\n </div>\r\n @if (createdFileGroups().length) {\r\n <div class=\"space-y-3 border-b border-surface-200 p-3\">\r\n @for (group of createdFileGroups(); track group.key) {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-(--p-content-border-color) bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"group.nodeLabel\"\r\n >\r\n {{ group.nodeLabel }}\r\n </p>\r\n @if (group.nodeKey) {\r\n <p\r\n class=\"m-0 truncate font-mono text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ group.nodeKey }}\r\n </p>\r\n }\r\n </div>\r\n <span\r\n class=\"shrink-0 rounded-md bg-(--p-surface-100) px-2 py-1 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ group.files.length }} file{{\r\n group.files.length === 1 ? \"\" : \"s\"\r\n }}\r\n </span>\r\n </div>\r\n <div class=\"space-y-2 p-3\">\r\n @for (file of group.files; track file.key) {\r\n <fp-automation-file-card\r\n [file]=\"file\"\r\n [downloadable]=\"!!file.downloadUrl\"\r\n (download)=\"downloadFile(file)\"\r\n />\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n @if (fileNodeRunGaps().length) {\r\n <div class=\"space-y-2 border-b border-surface-200 p-3\">\r\n @for (gap of fileNodeRunGaps(); track gap.key) {\r\n <div\r\n class=\"rounded-md border border-[rgb(var(--fp-warning))]/25 bg-[rgb(var(--fp-warning))]/10 px-3 py-2.5\"\r\n >\r\n <p\r\n class=\"m-0 text-[12.5px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ gap.nodeLabel }}\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ gap.message }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n }\r\n <pre\r\n class=\"fp-scroll m-0 max-h-full overflow-auto bg-(--p-surface-100) p-3 font-mono text-[11px] leading-[1.6] text-(--p-text-color)\"\r\n >{{ outputPreview() }}</pre\r\n >\r\n </section>\r\n } @else {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"dev.code-02\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No output preview yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Output appears after execution detail is loaded from the\r\n backend runtime.\r\n </p>\r\n </div>\r\n }\r\n }\r\n\r\n @case (\"input\") {\r\n <div class=\"space-y-3\">\r\n <section\r\n class=\"space-y-3 rounded-md border border-surface-200 bg-(--p-content-background) p-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Trigger input\r\n </p>\r\n <p class=\"m-0 text-[11.5px] text-(--p-text-muted-color)\">\r\n {{ payloadSummary() }}\r\n </p>\r\n </div>\r\n\r\n @if (payloadFields().length > 0) {\r\n <div class=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\r\n @for (field of payloadFields(); track field.key) {\r\n @if (field.enumOptions.length > 0) {\r\n <mt-select-field\r\n [ngModel]=\"schemaTextValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [options]=\"field.enumOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n } @else {\r\n @switch (field.type) {\r\n @case (\"number\") {\r\n <mt-number-field\r\n [ngModel]=\"schemaNumberValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [placeholder]=\"field.placeholder\"\r\n [required]=\"field.required\"\r\n />\r\n }\r\n @case (\"boolean\") {\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"schemaBooleanValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n />\r\n }\r\n @case (\"object\") {\r\n <mt-textarea-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"schemaJsonValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaJsonPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n rows=\"3\"\r\n />\r\n }\r\n @case (\"array\") {\r\n <mt-textarea-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"schemaJsonValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaJsonPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n rows=\"3\"\r\n />\r\n }\r\n @default {\r\n <mt-text-field\r\n [ngModel]=\"schemaTextValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [placeholder]=\"field.placeholder\"\r\n [required]=\"field.required\"\r\n />\r\n }\r\n }\r\n }\r\n }\r\n </div>\r\n } @else {\r\n <p\r\n class=\"m-0 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n This trigger has no input schema. JSON payload is optional.\r\n </p>\r\n }\r\n </section>\r\n\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Advanced JSON\r\n </p>\r\n <p\r\n class=\"m-0 text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Use this only when the backend trigger schema does not\r\n expose all needed values.\r\n </p>\r\n </div>\r\n @if (payloadFields().length > 0) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n payloadOpen()\r\n ? 'arrow.chevron-up'\r\n : 'arrow.chevron-down'\r\n \"\r\n [label]=\"payloadOpen() ? 'Hide JSON' : 'Show JSON'\"\r\n (onClick)=\"togglePayload()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (payloadFields().length === 0 || payloadOpen()) {\r\n <div class=\"space-y-2 p-3\">\r\n <mt-textarea-field\r\n [(ngModel)]=\"payloadText\"\r\n [rows]=\"4\"\r\n label=\"Advanced JSON payload\"\r\n placeholder='{\"amount\": 100}'\r\n />\r\n @if (payloadError()) {\r\n <p\r\n class=\"m-0 text-[11px] font-medium text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ payloadError() }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n\r\n<ng-template #nodeRunNodeCellTpl let-row>\r\n <div class=\"min-w-0\">\r\n <span\r\n class=\"block truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.error) {\r\n <span\r\n class=\"block truncate text-[11.5px] leading-5 text-[rgb(var(--fp-error))]\"\r\n [title]=\"row.error\"\r\n >\r\n {{ row.error }}\r\n </span>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #nodeRunStatusCellTpl let-row>\r\n <span\r\n [class]=\"\r\n 'inline-flex max-w-full truncate rounded-full border px-2 py-0.5 text-[10.5px] font-semibold ' +\r\n row.statusTone\r\n \"\r\n [title]=\"statusLabel(row.status)\"\r\n >\r\n {{ statusLabel(row.status) }}\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #nodeRunDetailCellTpl let-row>\r\n <div\r\n class=\"flex min-w-0 flex-wrap gap-x-3 gap-y-1 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n @if (row.duration) {\r\n <span>{{ row.duration }}</span>\r\n }\r\n @if (row.routeOutput) {\r\n <span>Output {{ row.routeOutput }}</span>\r\n }\r\n @if (row.waitReason) {\r\n <span>Wait {{ row.waitReason }}</span>\r\n }\r\n @if (!row.duration && !row.routeOutput && !row.waitReason) {\r\n <span>-</span>\r\n }\r\n </div>\r\n</ng-template>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "component", type: NumberField, selector: "mt-number-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "pInputs", "format", "useGrouping", "maxFractionDigits", "min", "max", "required"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: AutomationFileCardComponent, selector: "fp-automation-file-card", inputs: ["file", "inspectable", "downloadable"], outputs: ["inspect", "download"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }] });
|
|
26673
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TestRunPanelComponent, isStandalone: true, selector: "fp-test-run-panel", viewQueries: [{ propertyName: "nodeRunNodeCellTpl", first: true, predicate: ["nodeRunNodeCellTpl"], descendants: true, isSignal: true }, { propertyName: "nodeRunStatusCellTpl", first: true, predicate: ["nodeRunStatusCellTpl"], descendants: true, isSignal: true }, { propertyName: "nodeRunDetailCellTpl", first: true, predicate: ["nodeRunDetailCellTpl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\r\n @if (triggerOptions().length === 0) {\r\n <section\r\n class=\"flex min-h-[180px] items-center justify-center rounded-md border border-surface-200 bg-surface-0 px-6 py-8 text-center\"\r\n >\r\n <div class=\"max-w-[460px]\">\r\n <span\r\n class=\"mx-auto mb-3 inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.zap-fast\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No executable trigger\r\n </p>\r\n <p class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\">\r\n {{ helperText() }}\r\n </p>\r\n </div>\r\n </section>\r\n } @else {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n aria-label=\"Run workflow\"\r\n >\r\n <header\r\n class=\"flex flex-none flex-col border-b border-surface-200 bg-surface-50\"\r\n >\r\n <div class=\"flex flex-none flex-wrap items-center gap-2 px-3 py-2.5\">\r\n <div class=\"min-w-0 flex-1\">\r\n <div class=\"flex min-w-0 flex-wrap items-center gap-2\">\r\n @if (\r\n triggerOptions().length === 1 && selectedTrigger();\r\n as trigger\r\n ) {\r\n <span\r\n class=\"max-w-[20rem] truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n [title]=\"cleanTriggerLabel(trigger)\"\r\n >\r\n {{ cleanTriggerLabel(trigger) }}\r\n </span>\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ triggerTypeLabel(trigger.type) }}\r\n </span>\r\n @if (trigger.startNodeKey) {\r\n <span\r\n class=\"max-w-[12rem] truncate text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"'Starts at ' + trigger.startNodeKey\"\r\n >\r\n Starts at {{ trigger.startNodeKey }}\r\n </span>\r\n }\r\n } @else {\r\n <div class=\"min-w-[16rem] max-w-[28rem] flex-1\">\r\n <mt-select-field\r\n [ngModel]=\"selectedTriggerId()\"\r\n (ngModelChange)=\"onTriggerChange($event)\"\r\n label=\"Trigger\"\r\n [options]=\"triggerOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n }\r\n\r\n <span\r\n [class]=\"\r\n 'inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-[11px] font-semibold ' +\r\n triggerStatusTone()\r\n \"\r\n >\r\n {{\r\n statusLabel(\r\n runtimeStatus() || selectedTrigger()?.executionStatus\r\n )\r\n }}\r\n </span>\r\n\r\n @if (runtimeAccepted()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-color)\"\r\n >\r\n Accepted\r\n </span>\r\n }\r\n @if (routeOutputSummary()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-color)\"\r\n >\r\n {{ routeOutputSummary() }}\r\n </span>\r\n }\r\n @if (runDurationLabel()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ runDurationLabel() }}\r\n </span>\r\n }\r\n @for (fact of compactRunFacts(); track fact) {\r\n <span\r\n class=\"max-w-[18rem] truncate rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n [title]=\"fact\"\r\n >\r\n {{ fact }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <p\r\n class=\"m-0 mt-1 truncate text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n [title]=\"helperText()\"\r\n >\r\n {{ helperText() }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"flex flex-none flex-wrap items-center gap-2\">\r\n @if (selectedTriggerExecutable()) {\r\n <mt-button\r\n severity=\"primary\"\r\n size=\"small\"\r\n icon=\"media.play\"\r\n [label]=\"runtimeRunning() ? 'Executing...' : 'Execute workflow'\"\r\n [loading]=\"runtimeRunning()\"\r\n [disabled]=\"runtimeRunning()\"\r\n [tooltip]=\"helperText()\"\r\n (onClick)=\"execute()\"\r\n />\r\n @if (canStopExecution()) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"media.stop-circle\"\r\n [label]=\"\r\n runtimeCanceling() ? 'Stopping...' : 'Stop execution'\r\n \"\r\n [loading]=\"runtimeCanceling()\"\r\n [disabled]=\"runtimeCanceling()\"\r\n [tooltip]=\"'Cancel the active builder execution'\"\r\n (onClick)=\"stopExecution()\"\r\n />\r\n }\r\n } @else {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n label=\"Fix trigger\"\r\n [tooltip]=\"helperText()\"\r\n (onClick)=\"openTriggerSetup()\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (runtimeDisconnected()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n Runtime disconnected. The canvas animation was stopped because the\r\n backend status could not be refreshed.\r\n </div>\r\n } @else if (runtimeError()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ runtimeError() }}\r\n </div>\r\n } @else if (lastError()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ lastError() }}\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"flex flex-none items-center border-t border-surface-200 bg-(--p-content-background) px-3\"\r\n >\r\n <div class=\"min-w-0 flex-1 overflow-hidden\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [fluid]=\"true\"\r\n [options]=\"runPanelTabOptions()\"\r\n [active]=\"runPanelTab()\"\r\n (onChange)=\"setRunPanelTab($event)\"\r\n />\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-auto p-3\">\r\n @switch (runPanelTab()) {\r\n @case (\"timeline\") {\r\n @if (timelineRows().length === 0) {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"layout.list\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No timeline yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Execute the trigger to see the runtime path and ordered engine\r\n events.\r\n </p>\r\n </div>\r\n } @else {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Execution timeline\r\n </p>\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ timelineRows().length }} events\r\n </span>\r\n </div>\r\n <ol class=\"m-0 list-none space-y-2 p-3\">\r\n @for (row of timelineRows(); track row.key) {\r\n <li class=\"flex gap-2\">\r\n <span\r\n [class]=\"\r\n 'mt-1.5 size-2 rounded-full ring-4 ring-(--p-surface-100) ' +\r\n timelineToneClass(row.tone)\r\n \"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div class=\"flex min-w-0 items-center gap-1.5\">\r\n <mt-icon\r\n [icon]=\"row.icon\"\r\n class=\"shrink-0 text-(--p-text-muted-color) [&_svg]:size-3.5\"\r\n />\r\n <span\r\n class=\"truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.time) {\r\n <span\r\n class=\"shrink-0 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ row.time | date: \"shortTime\" }}\r\n </span>\r\n }\r\n </div>\r\n @if (row.detail) {\r\n <p\r\n class=\"m-0 truncate text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"row.detail\"\r\n >\r\n {{ row.detail }}\r\n </p>\r\n }\r\n </div>\r\n </li>\r\n }\r\n </ol>\r\n </section>\r\n }\r\n }\r\n\r\n @case (\"nodes\") {\r\n @if (nodeRunRows().length === 0) {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"layout.table\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No node results yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Node status, duration, selected route, and failures appear\r\n here after execution detail is loaded.\r\n </p>\r\n </div>\r\n } @else {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Node results\r\n </p>\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ nodeRunRows().length }} nodes\r\n </span>\r\n </div>\r\n <div class=\"p-2\">\r\n <mt-table\r\n [data]=\"nodeRunRows()\"\r\n [columns]=\"nodeRunColumns()\"\r\n dataKey=\"key\"\r\n size=\"small\"\r\n [noCard]=\"true\"\r\n [tableLayout]=\"'fixed'\"\r\n [pageSize]=\"10\"\r\n [rowsPerPageOptions]=\"[10, 20, 50]\"\r\n [alwaysShowPaginator]=\"nodeRunRows().length > 10\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n\r\n @case (\"output\") {\r\n @if (outputPreview()) {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Output preview\r\n </p>\r\n @if (runStartedLabel(); as startedAt) {\r\n <span\r\n class=\"max-w-[18rem] truncate text-[11px] text-(--p-text-muted-color)\"\r\n [title]=\"startedAt\"\r\n >\r\n Started {{ startedAt }}\r\n </span>\r\n }\r\n </div>\r\n @if (createdFileGroups().length) {\r\n <div class=\"space-y-3 border-b border-surface-200 p-3\">\r\n @for (group of createdFileGroups(); track group.key) {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-(--p-content-border-color) bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"group.nodeLabel\"\r\n >\r\n {{ group.nodeLabel }}\r\n </p>\r\n @if (group.nodeKey) {\r\n <p\r\n class=\"m-0 truncate font-mono text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ group.nodeKey }}\r\n </p>\r\n }\r\n </div>\r\n <span\r\n class=\"shrink-0 rounded-md bg-(--p-surface-100) px-2 py-1 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ group.files.length }} file{{\r\n group.files.length === 1 ? \"\" : \"s\"\r\n }}\r\n </span>\r\n </div>\r\n <div class=\"space-y-2 p-3\">\r\n @for (file of group.files; track file.key) {\r\n <fp-automation-file-card\r\n [file]=\"file\"\r\n [downloadable]=\"!!file.downloadUrl\"\r\n (download)=\"downloadFile(file)\"\r\n />\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n @if (fileNodeRunGaps().length) {\r\n <div class=\"space-y-2 border-b border-surface-200 p-3\">\r\n @for (gap of fileNodeRunGaps(); track gap.key) {\r\n <div\r\n class=\"rounded-md border border-[rgb(var(--fp-warning))]/25 bg-[rgb(var(--fp-warning))]/10 px-3 py-2.5\"\r\n >\r\n <p\r\n class=\"m-0 text-[12.5px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ gap.nodeLabel }}\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ gap.message }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n }\r\n <pre\r\n class=\"fp-scroll m-0 max-h-full overflow-auto bg-(--p-surface-100) p-3 font-mono text-[11px] leading-[1.6] text-(--p-text-color)\"\r\n >{{ outputPreview() }}</pre\r\n >\r\n </section>\r\n } @else {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"dev.code-02\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No output preview yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Output appears after execution detail is loaded from the\r\n backend runtime.\r\n </p>\r\n </div>\r\n }\r\n }\r\n\r\n @case (\"input\") {\r\n <div class=\"space-y-3\">\r\n <section\r\n class=\"space-y-3 rounded-md border border-surface-200 bg-(--p-content-background) p-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Trigger input\r\n </p>\r\n <p class=\"m-0 text-[11.5px] text-(--p-text-muted-color)\">\r\n {{ payloadSummary() }}\r\n </p>\r\n </div>\r\n\r\n @if (payloadFields().length > 0) {\r\n <div class=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\r\n @for (field of payloadFields(); track field.key) {\r\n @if (field.enumOptions.length > 0) {\r\n <mt-select-field\r\n [ngModel]=\"schemaTextValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [options]=\"field.enumOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n } @else {\r\n @switch (field.type) {\r\n @case (\"number\") {\r\n <mt-number-field\r\n [ngModel]=\"schemaNumberValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [placeholder]=\"field.placeholder\"\r\n [required]=\"field.required\"\r\n />\r\n }\r\n @case (\"boolean\") {\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"schemaBooleanValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n />\r\n }\r\n @case (\"object\") {\r\n <mt-textarea-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"schemaJsonValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaJsonPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n rows=\"3\"\r\n />\r\n }\r\n @case (\"array\") {\r\n <mt-textarea-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"schemaJsonValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaJsonPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n rows=\"3\"\r\n />\r\n }\r\n @default {\r\n <mt-text-field\r\n [ngModel]=\"schemaTextValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [placeholder]=\"field.placeholder\"\r\n [required]=\"field.required\"\r\n />\r\n }\r\n }\r\n }\r\n }\r\n </div>\r\n } @else {\r\n <p\r\n class=\"m-0 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n This trigger has no input schema. JSON payload is optional.\r\n </p>\r\n }\r\n </section>\r\n\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Advanced JSON\r\n </p>\r\n <p\r\n class=\"m-0 text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Use this only when the backend trigger schema does not\r\n expose all needed values.\r\n </p>\r\n </div>\r\n @if (payloadFields().length > 0) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n payloadOpen()\r\n ? 'arrow.chevron-up'\r\n : 'arrow.chevron-down'\r\n \"\r\n [label]=\"payloadOpen() ? 'Hide JSON' : 'Show JSON'\"\r\n (onClick)=\"togglePayload()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (payloadFields().length === 0 || payloadOpen()) {\r\n <div class=\"space-y-2 p-3\">\r\n <mt-textarea-field\r\n [(ngModel)]=\"payloadText\"\r\n [rows]=\"4\"\r\n label=\"Advanced JSON payload\"\r\n placeholder='{\"amount\": 100}'\r\n />\r\n @if (payloadError()) {\r\n <p\r\n class=\"m-0 text-[11px] font-medium text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ payloadError() }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n\r\n<ng-template #nodeRunNodeCellTpl let-row>\r\n <div class=\"min-w-0\">\r\n <span\r\n class=\"block truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.error) {\r\n <span\r\n class=\"block truncate text-[11.5px] leading-5 text-[rgb(var(--fp-error))]\"\r\n [title]=\"row.error\"\r\n >\r\n {{ row.error }}\r\n </span>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #nodeRunStatusCellTpl let-row>\r\n <span\r\n [class]=\"\r\n 'inline-flex max-w-full truncate rounded-full border px-2 py-0.5 text-[10.5px] font-semibold ' +\r\n row.statusTone\r\n \"\r\n [title]=\"statusLabel(row.status)\"\r\n >\r\n {{ statusLabel(row.status) }}\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #nodeRunDetailCellTpl let-row>\r\n <div\r\n class=\"flex min-w-0 flex-wrap gap-x-3 gap-y-1 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n @if (row.duration) {\r\n <span>{{ row.duration }}</span>\r\n }\r\n @if (row.routeOutput) {\r\n <span>Output {{ row.routeOutput }}</span>\r\n }\r\n @if (row.waitReason) {\r\n <span>Wait {{ row.waitReason }}</span>\r\n }\r\n @if (!row.duration && !row.routeOutput && !row.waitReason) {\r\n <span>-</span>\r\n }\r\n </div>\r\n</ng-template>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "component", type: NumberField, selector: "mt-number-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "pInputs", "format", "useGrouping", "maxFractionDigits", "min", "max", "required"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: AutomationFileCardComponent, selector: "fp-automation-file-card", inputs: ["file", "inspectable", "downloadable"], outputs: ["inspect", "download"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }] });
|
|
26671
26674
|
}
|
|
26672
26675
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TestRunPanelComponent, decorators: [{
|
|
26673
26676
|
type: Component,
|
|
@@ -26685,7 +26688,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
26685
26688
|
Tabs,
|
|
26686
26689
|
Icon,
|
|
26687
26690
|
AutomationFileCardComponent,
|
|
26688
|
-
], template: "<div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\r\n @if (triggerOptions().length === 0) {\r\n <section\r\n class=\"flex min-h-[180px] items-center justify-center rounded-md border border-surface-200 bg-surface-0 px-6 py-8 text-center\"\r\n >\r\n <div class=\"max-w-[460px]\">\r\n <span\r\n class=\"mx-auto mb-3 inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.zap-fast\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No executable trigger\r\n </p>\r\n <p class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\">\r\n {{ helperText() }}\r\n </p>\r\n </div>\r\n </section>\r\n } @else {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n aria-label=\"Run workflow\"\r\n >\r\n <header\r\n class=\"flex flex-none flex-col border-b border-surface-200 bg-surface-50\"\r\n >\r\n <div class=\"flex flex-none flex-wrap items-center gap-2 px-3 py-2.5\">\r\n <div class=\"min-w-0 flex-1\">\r\n <div class=\"flex min-w-0 flex-wrap items-center gap-2\">\r\n @if (\r\n triggerOptions().length === 1 && selectedTrigger();\r\n as trigger\r\n ) {\r\n <span\r\n class=\"max-w-[20rem] truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n [title]=\"cleanTriggerLabel(trigger)\"\r\n >\r\n {{ cleanTriggerLabel(trigger) }}\r\n </span>\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ triggerTypeLabel(trigger.type) }}\r\n </span>\r\n @if (trigger.startNodeKey) {\r\n <span\r\n class=\"max-w-[12rem] truncate text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"'Starts at ' + trigger.startNodeKey\"\r\n >\r\n Starts at {{ trigger.startNodeKey }}\r\n </span>\r\n }\r\n } @else {\r\n <div class=\"min-w-[16rem] max-w-[28rem] flex-1\">\r\n <mt-select-field\r\n [ngModel]=\"selectedTriggerId()\"\r\n (ngModelChange)=\"onTriggerChange($event)\"\r\n label=\"Trigger\"\r\n [options]=\"triggerOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n }\r\n\r\n <span\r\n [class]=\"\r\n 'inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-[11px] font-semibold ' +\r\n triggerStatusTone()\r\n \"\r\n >\r\n {{\r\n statusLabel(\r\n runtimeStatus() || selectedTrigger()?.executionStatus\r\n )\r\n }}\r\n </span>\r\n\r\n @if (runtimeAccepted()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-color)\"\r\n >\r\n Accepted\r\n </span>\r\n }\r\n @if (routeOutputSummary()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-color)\"\r\n >\r\n {{ routeOutputSummary() }}\r\n </span>\r\n }\r\n @if (runDurationLabel()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ runDurationLabel() }}\r\n </span>\r\n }\r\n @for (fact of compactRunFacts(); track fact) {\r\n <span\r\n class=\"max-w-[18rem] truncate rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n [title]=\"fact\"\r\n >\r\n {{ fact }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <p\r\n class=\"m-0 mt-1 truncate text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n [title]=\"helperText()\"\r\n >\r\n {{ helperText() }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"flex flex-none flex-wrap items-center gap-2\">\r\n @if (selectedTriggerExecutable()) {\n <mt-button\n severity=\"primary\"\n size=\"small\"\n icon=\"media.play\"\n [label]=\"runtimeRunning() ? 'Executing...' : 'Execute workflow'\"\n [loading]=\"runtimeRunning()\"\n [disabled]=\"runtimeRunning()\"\n [tooltip]=\"helperText()\"\n (onClick)=\"execute()\"\n />\n @if (canStopExecution()) {\n <mt-button\n variant=\"outlined\"\n severity=\"danger\"\n size=\"small\"\n icon=\"media.stop-circle\"\n [label]=\"\n runtimeCanceling() ? 'Stopping...' : 'Stop execution'\n \"\n [loading]=\"runtimeCanceling()\"\n [disabled]=\"runtimeCanceling()\"\n [tooltip]=\"'Cancel the active builder execution'\"\n (onClick)=\"stopExecution()\"\n />\n }\n } @else {\n <mt-button\n variant=\"outlined\"\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n label=\"Fix trigger\"\r\n [tooltip]=\"helperText()\"\r\n (onClick)=\"openTriggerSetup()\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (runtimeDisconnected()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n Runtime disconnected. The canvas animation was stopped because the\r\n backend status could not be refreshed.\r\n </div>\r\n } @else if (runtimeError()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ runtimeError() }}\r\n </div>\r\n } @else if (lastError()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ lastError() }}\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"flex flex-none items-center border-t border-surface-200 bg-(--p-content-background) px-3\"\r\n >\r\n <div class=\"min-w-0 flex-1 overflow-hidden\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [fluid]=\"true\"\r\n [options]=\"runPanelTabOptions()\"\r\n [active]=\"runPanelTab()\"\r\n (onChange)=\"setRunPanelTab($event)\"\r\n />\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-auto p-3\">\r\n @switch (runPanelTab()) {\r\n @case (\"timeline\") {\r\n @if (timelineRows().length === 0) {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"layout.list\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No timeline yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Execute the trigger to see the runtime path and ordered engine\r\n events.\r\n </p>\r\n </div>\r\n } @else {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Execution timeline\r\n </p>\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ timelineRows().length }} events\r\n </span>\r\n </div>\r\n <ol class=\"m-0 list-none space-y-2 p-3\">\r\n @for (row of timelineRows(); track row.key) {\r\n <li class=\"flex gap-2\">\r\n <span\r\n [class]=\"\r\n 'mt-1.5 size-2 rounded-full ring-4 ring-(--p-surface-100) ' +\r\n timelineToneClass(row.tone)\r\n \"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div class=\"flex min-w-0 items-center gap-1.5\">\r\n <mt-icon\r\n [icon]=\"row.icon\"\r\n class=\"shrink-0 text-(--p-text-muted-color) [&_svg]:size-3.5\"\r\n />\r\n <span\r\n class=\"truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.time) {\r\n <span\r\n class=\"shrink-0 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ row.time | date: \"shortTime\" }}\r\n </span>\r\n }\r\n </div>\r\n @if (row.detail) {\r\n <p\r\n class=\"m-0 truncate text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"row.detail\"\r\n >\r\n {{ row.detail }}\r\n </p>\r\n }\r\n </div>\r\n </li>\r\n }\r\n </ol>\r\n </section>\r\n }\r\n }\r\n\r\n @case (\"nodes\") {\r\n @if (nodeRunRows().length === 0) {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"layout.table\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No node results yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Node status, duration, selected route, and failures appear\r\n here after execution detail is loaded.\r\n </p>\r\n </div>\r\n } @else {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Node results\r\n </p>\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ nodeRunRows().length }} nodes\r\n </span>\r\n </div>\r\n <div class=\"p-2\">\r\n <mt-table\r\n [data]=\"nodeRunRows()\"\r\n [columns]=\"nodeRunColumns()\"\r\n dataKey=\"key\"\r\n size=\"small\"\r\n [noCard]=\"true\"\r\n [tableLayout]=\"'fixed'\"\r\n [pageSize]=\"10\"\r\n [rowsPerPageOptions]=\"[10, 20, 50]\"\r\n [alwaysShowPaginator]=\"nodeRunRows().length > 10\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n\r\n @case (\"output\") {\r\n @if (outputPreview()) {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Output preview\r\n </p>\r\n @if (runStartedLabel(); as startedAt) {\r\n <span\r\n class=\"max-w-[18rem] truncate text-[11px] text-(--p-text-muted-color)\"\r\n [title]=\"startedAt\"\r\n >\r\n Started {{ startedAt }}\r\n </span>\r\n }\r\n </div>\r\n @if (createdFileGroups().length) {\r\n <div class=\"space-y-3 border-b border-surface-200 p-3\">\r\n @for (group of createdFileGroups(); track group.key) {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-(--p-content-border-color) bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"group.nodeLabel\"\r\n >\r\n {{ group.nodeLabel }}\r\n </p>\r\n @if (group.nodeKey) {\r\n <p\r\n class=\"m-0 truncate font-mono text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ group.nodeKey }}\r\n </p>\r\n }\r\n </div>\r\n <span\r\n class=\"shrink-0 rounded-md bg-(--p-surface-100) px-2 py-1 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ group.files.length }} file{{\r\n group.files.length === 1 ? \"\" : \"s\"\r\n }}\r\n </span>\r\n </div>\r\n <div class=\"space-y-2 p-3\">\r\n @for (file of group.files; track file.key) {\r\n <fp-automation-file-card\r\n [file]=\"file\"\r\n [downloadable]=\"!!file.downloadUrl\"\r\n (download)=\"downloadFile(file)\"\r\n />\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n @if (fileNodeRunGaps().length) {\r\n <div class=\"space-y-2 border-b border-surface-200 p-3\">\r\n @for (gap of fileNodeRunGaps(); track gap.key) {\r\n <div\r\n class=\"rounded-md border border-[rgb(var(--fp-warning))]/25 bg-[rgb(var(--fp-warning))]/10 px-3 py-2.5\"\r\n >\r\n <p\r\n class=\"m-0 text-[12.5px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ gap.nodeLabel }}\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ gap.message }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n }\r\n <pre\r\n class=\"fp-scroll m-0 max-h-full overflow-auto bg-(--p-surface-100) p-3 font-mono text-[11px] leading-[1.6] text-(--p-text-color)\"\r\n >{{ outputPreview() }}</pre\r\n >\r\n </section>\r\n } @else {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"dev.code-02\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No output preview yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Output appears after execution detail is loaded from the\r\n backend runtime.\r\n </p>\r\n </div>\r\n }\r\n }\r\n\r\n @case (\"input\") {\r\n <div class=\"space-y-3\">\r\n <section\r\n class=\"space-y-3 rounded-md border border-surface-200 bg-(--p-content-background) p-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Trigger input\r\n </p>\r\n <p class=\"m-0 text-[11.5px] text-(--p-text-muted-color)\">\r\n {{ payloadSummary() }}\r\n </p>\r\n </div>\r\n\r\n @if (payloadFields().length > 0) {\r\n <div class=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\r\n @for (field of payloadFields(); track field.key) {\r\n @if (field.enumOptions.length > 0) {\r\n <mt-select-field\r\n [ngModel]=\"schemaTextValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [options]=\"field.enumOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n } @else {\r\n @switch (field.type) {\r\n @case (\"number\") {\r\n <mt-number-field\r\n [ngModel]=\"schemaNumberValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [placeholder]=\"field.placeholder\"\r\n [required]=\"field.required\"\r\n />\r\n }\r\n @case (\"boolean\") {\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"schemaBooleanValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n />\r\n }\r\n @case (\"object\") {\r\n <mt-textarea-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"schemaJsonValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaJsonPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n rows=\"3\"\r\n />\r\n }\r\n @case (\"array\") {\r\n <mt-textarea-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"schemaJsonValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaJsonPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n rows=\"3\"\r\n />\r\n }\r\n @default {\r\n <mt-text-field\r\n [ngModel]=\"schemaTextValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [placeholder]=\"field.placeholder\"\r\n [required]=\"field.required\"\r\n />\r\n }\r\n }\r\n }\r\n }\r\n </div>\r\n } @else {\r\n <p\r\n class=\"m-0 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n This trigger has no input schema. JSON payload is optional.\r\n </p>\r\n }\r\n </section>\r\n\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Advanced JSON\r\n </p>\r\n <p\r\n class=\"m-0 text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Use this only when the backend trigger schema does not\r\n expose all needed values.\r\n </p>\r\n </div>\r\n @if (payloadFields().length > 0) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n payloadOpen()\r\n ? 'arrow.chevron-up'\r\n : 'arrow.chevron-down'\r\n \"\r\n [label]=\"payloadOpen() ? 'Hide JSON' : 'Show JSON'\"\r\n (onClick)=\"togglePayload()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (payloadFields().length === 0 || payloadOpen()) {\r\n <div class=\"space-y-2 p-3\">\r\n <mt-textarea-field\r\n [(ngModel)]=\"payloadText\"\r\n [rows]=\"4\"\r\n label=\"Advanced JSON payload\"\r\n placeholder='{\"amount\": 100}'\r\n />\r\n @if (payloadError()) {\r\n <p\r\n class=\"m-0 text-[11px] font-medium text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ payloadError() }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n\r\n<ng-template #nodeRunNodeCellTpl let-row>\r\n <div class=\"min-w-0\">\r\n <span\r\n class=\"block truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.error) {\r\n <span\r\n class=\"block truncate text-[11.5px] leading-5 text-[rgb(var(--fp-error))]\"\r\n [title]=\"row.error\"\r\n >\r\n {{ row.error }}\r\n </span>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #nodeRunStatusCellTpl let-row>\r\n <span\r\n [class]=\"\r\n 'inline-flex max-w-full truncate rounded-full border px-2 py-0.5 text-[10.5px] font-semibold ' +\r\n row.statusTone\r\n \"\r\n [title]=\"statusLabel(row.status)\"\r\n >\r\n {{ statusLabel(row.status) }}\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #nodeRunDetailCellTpl let-row>\r\n <div\r\n class=\"flex min-w-0 flex-wrap gap-x-3 gap-y-1 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n @if (row.duration) {\r\n <span>{{ row.duration }}</span>\r\n }\r\n @if (row.routeOutput) {\r\n <span>Output {{ row.routeOutput }}</span>\r\n }\r\n @if (row.waitReason) {\r\n <span>Wait {{ row.waitReason }}</span>\r\n }\r\n @if (!row.duration && !row.routeOutput && !row.waitReason) {\r\n <span>-</span>\r\n }\r\n </div>\r\n</ng-template>\r\n" }]
|
|
26691
|
+
], template: "<div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\r\n @if (triggerOptions().length === 0) {\r\n <section\r\n class=\"flex min-h-[180px] items-center justify-center rounded-md border border-surface-200 bg-surface-0 px-6 py-8 text-center\"\r\n >\r\n <div class=\"max-w-[460px]\">\r\n <span\r\n class=\"mx-auto mb-3 inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.zap-fast\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No executable trigger\r\n </p>\r\n <p class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\">\r\n {{ helperText() }}\r\n </p>\r\n </div>\r\n </section>\r\n } @else {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n aria-label=\"Run workflow\"\r\n >\r\n <header\r\n class=\"flex flex-none flex-col border-b border-surface-200 bg-surface-50\"\r\n >\r\n <div class=\"flex flex-none flex-wrap items-center gap-2 px-3 py-2.5\">\r\n <div class=\"min-w-0 flex-1\">\r\n <div class=\"flex min-w-0 flex-wrap items-center gap-2\">\r\n @if (\r\n triggerOptions().length === 1 && selectedTrigger();\r\n as trigger\r\n ) {\r\n <span\r\n class=\"max-w-[20rem] truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n [title]=\"cleanTriggerLabel(trigger)\"\r\n >\r\n {{ cleanTriggerLabel(trigger) }}\r\n </span>\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2 py-0.5 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ triggerTypeLabel(trigger.type) }}\r\n </span>\r\n @if (trigger.startNodeKey) {\r\n <span\r\n class=\"max-w-[12rem] truncate text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"'Starts at ' + trigger.startNodeKey\"\r\n >\r\n Starts at {{ trigger.startNodeKey }}\r\n </span>\r\n }\r\n } @else {\r\n <div class=\"min-w-[16rem] max-w-[28rem] flex-1\">\r\n <mt-select-field\r\n [ngModel]=\"selectedTriggerId()\"\r\n (ngModelChange)=\"onTriggerChange($event)\"\r\n label=\"Trigger\"\r\n [options]=\"triggerOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n }\r\n\r\n <span\r\n [class]=\"\r\n 'inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-[11px] font-semibold ' +\r\n triggerStatusTone()\r\n \"\r\n >\r\n {{\r\n statusLabel(\r\n runtimeStatus() || selectedTrigger()?.executionStatus\r\n )\r\n }}\r\n </span>\r\n\r\n @if (runtimeAccepted()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-color)\"\r\n >\r\n Accepted\r\n </span>\r\n }\r\n @if (routeOutputSummary()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-color)\"\r\n >\r\n {{ routeOutputSummary() }}\r\n </span>\r\n }\r\n @if (runDurationLabel()) {\r\n <span\r\n class=\"rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n >\r\n {{ runDurationLabel() }}\r\n </span>\r\n }\r\n @for (fact of compactRunFacts(); track fact) {\r\n <span\r\n class=\"max-w-[18rem] truncate rounded-full border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11px] font-medium text-(--p-text-muted-color)\"\r\n [title]=\"fact\"\r\n >\r\n {{ fact }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <p\r\n class=\"m-0 mt-1 truncate text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n [title]=\"helperText()\"\r\n >\r\n {{ helperText() }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"flex flex-none flex-wrap items-center gap-2\">\r\n @if (selectedTriggerExecutable()) {\r\n <mt-button\r\n severity=\"primary\"\r\n size=\"small\"\r\n icon=\"media.play\"\r\n [label]=\"runtimeRunning() ? 'Executing...' : 'Execute workflow'\"\r\n [loading]=\"runtimeRunning()\"\r\n [disabled]=\"runtimeRunning()\"\r\n [tooltip]=\"helperText()\"\r\n (onClick)=\"execute()\"\r\n />\r\n @if (canStopExecution()) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"media.stop-circle\"\r\n [label]=\"\r\n runtimeCanceling() ? 'Stopping...' : 'Stop execution'\r\n \"\r\n [loading]=\"runtimeCanceling()\"\r\n [disabled]=\"runtimeCanceling()\"\r\n [tooltip]=\"'Cancel the active builder execution'\"\r\n (onClick)=\"stopExecution()\"\r\n />\r\n }\r\n } @else {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n label=\"Fix trigger\"\r\n [tooltip]=\"helperText()\"\r\n (onClick)=\"openTriggerSetup()\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (runtimeDisconnected()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n Runtime disconnected. The canvas animation was stopped because the\r\n backend status could not be refreshed.\r\n </div>\r\n } @else if (runtimeError()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ runtimeError() }}\r\n </div>\r\n } @else if (lastError()) {\r\n <div\r\n class=\"mx-3 mb-2 rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10 px-3 py-2 text-[12px] leading-5 text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ lastError() }}\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"flex flex-none items-center border-t border-surface-200 bg-(--p-content-background) px-3\"\r\n >\r\n <div class=\"min-w-0 flex-1 overflow-hidden\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [fluid]=\"true\"\r\n [options]=\"runPanelTabOptions()\"\r\n [active]=\"runPanelTab()\"\r\n (onChange)=\"setRunPanelTab($event)\"\r\n />\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-auto p-3\">\r\n @switch (runPanelTab()) {\r\n @case (\"timeline\") {\r\n @if (timelineRows().length === 0) {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"layout.list\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No timeline yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Execute the trigger to see the runtime path and ordered engine\r\n events.\r\n </p>\r\n </div>\r\n } @else {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Execution timeline\r\n </p>\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ timelineRows().length }} events\r\n </span>\r\n </div>\r\n <ol class=\"m-0 list-none space-y-2 p-3\">\r\n @for (row of timelineRows(); track row.key) {\r\n <li class=\"flex gap-2\">\r\n <span\r\n [class]=\"\r\n 'mt-1.5 size-2 rounded-full ring-4 ring-(--p-surface-100) ' +\r\n timelineToneClass(row.tone)\r\n \"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div class=\"flex min-w-0 items-center gap-1.5\">\r\n <mt-icon\r\n [icon]=\"row.icon\"\r\n class=\"shrink-0 text-(--p-text-muted-color) [&_svg]:size-3.5\"\r\n />\r\n <span\r\n class=\"truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.time) {\r\n <span\r\n class=\"shrink-0 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ row.time | date: \"shortTime\" }}\r\n </span>\r\n }\r\n </div>\r\n @if (row.detail) {\r\n <p\r\n class=\"m-0 truncate text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"row.detail\"\r\n >\r\n {{ row.detail }}\r\n </p>\r\n }\r\n </div>\r\n </li>\r\n }\r\n </ol>\r\n </section>\r\n }\r\n }\r\n\r\n @case (\"nodes\") {\r\n @if (nodeRunRows().length === 0) {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"layout.table\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No node results yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Node status, duration, selected route, and failures appear\r\n here after execution detail is loaded.\r\n </p>\r\n </div>\r\n } @else {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Node results\r\n </p>\r\n <span class=\"text-[11px] text-(--p-text-muted-color)\">\r\n {{ nodeRunRows().length }} nodes\r\n </span>\r\n </div>\r\n <div class=\"p-2\">\r\n <mt-table\r\n [data]=\"nodeRunRows()\"\r\n [columns]=\"nodeRunColumns()\"\r\n dataKey=\"key\"\r\n size=\"small\"\r\n [noCard]=\"true\"\r\n [tableLayout]=\"'fixed'\"\r\n [pageSize]=\"10\"\r\n [rowsPerPageOptions]=\"[10, 20, 50]\"\r\n [alwaysShowPaginator]=\"nodeRunRows().length > 10\"\r\n />\r\n </div>\r\n </section>\r\n }\r\n }\r\n\r\n @case (\"output\") {\r\n @if (outputPreview()) {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Output preview\r\n </p>\r\n @if (runStartedLabel(); as startedAt) {\r\n <span\r\n class=\"max-w-[18rem] truncate text-[11px] text-(--p-text-muted-color)\"\r\n [title]=\"startedAt\"\r\n >\r\n Started {{ startedAt }}\r\n </span>\r\n }\r\n </div>\r\n @if (createdFileGroups().length) {\r\n <div class=\"space-y-3 border-b border-surface-200 p-3\">\r\n @for (group of createdFileGroups(); track group.key) {\r\n <section\r\n class=\"overflow-hidden rounded-md border border-(--p-content-border-color) bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"group.nodeLabel\"\r\n >\r\n {{ group.nodeLabel }}\r\n </p>\r\n @if (group.nodeKey) {\r\n <p\r\n class=\"m-0 truncate font-mono text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ group.nodeKey }}\r\n </p>\r\n }\r\n </div>\r\n <span\r\n class=\"shrink-0 rounded-md bg-(--p-surface-100) px-2 py-1 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n {{ group.files.length }} file{{\r\n group.files.length === 1 ? \"\" : \"s\"\r\n }}\r\n </span>\r\n </div>\r\n <div class=\"space-y-2 p-3\">\r\n @for (file of group.files; track file.key) {\r\n <fp-automation-file-card\r\n [file]=\"file\"\r\n [downloadable]=\"!!file.downloadUrl\"\r\n (download)=\"downloadFile(file)\"\r\n />\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n @if (fileNodeRunGaps().length) {\r\n <div class=\"space-y-2 border-b border-surface-200 p-3\">\r\n @for (gap of fileNodeRunGaps(); track gap.key) {\r\n <div\r\n class=\"rounded-md border border-[rgb(var(--fp-warning))]/25 bg-[rgb(var(--fp-warning))]/10 px-3 py-2.5\"\r\n >\r\n <p\r\n class=\"m-0 text-[12.5px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ gap.nodeLabel }}\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ gap.message }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n }\r\n <pre\r\n class=\"fp-scroll m-0 max-h-full overflow-auto bg-(--p-surface-100) p-3 font-mono text-[11px] leading-[1.6] text-(--p-text-color)\"\r\n >{{ outputPreview() }}</pre\r\n >\r\n </section>\r\n } @else {\r\n <div\r\n class=\"flex min-h-[160px] flex-col items-center justify-center gap-2 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-6 py-8 text-center\"\r\n >\r\n <mt-icon\r\n icon=\"dev.code-02\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No output preview yet\r\n </p>\r\n <p\r\n class=\"m-0 max-w-[440px] text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Output appears after execution detail is loaded from the\r\n backend runtime.\r\n </p>\r\n </div>\r\n }\r\n }\r\n\r\n @case (\"input\") {\r\n <div class=\"space-y-3\">\r\n <section\r\n class=\"space-y-3 rounded-md border border-surface-200 bg-(--p-content-background) p-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Trigger input\r\n </p>\r\n <p class=\"m-0 text-[11.5px] text-(--p-text-muted-color)\">\r\n {{ payloadSummary() }}\r\n </p>\r\n </div>\r\n\r\n @if (payloadFields().length > 0) {\r\n <div class=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\r\n @for (field of payloadFields(); track field.key) {\r\n @if (field.enumOptions.length > 0) {\r\n <mt-select-field\r\n [ngModel]=\"schemaTextValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [options]=\"field.enumOptions\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n } @else {\r\n @switch (field.type) {\r\n @case (\"number\") {\r\n <mt-number-field\r\n [ngModel]=\"schemaNumberValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [placeholder]=\"field.placeholder\"\r\n [required]=\"field.required\"\r\n />\r\n }\r\n @case (\"boolean\") {\r\n <mt-toggle-field\r\n size=\"small\"\r\n labelPosition=\"end\"\r\n [ngModel]=\"schemaBooleanValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n />\r\n }\r\n @case (\"object\") {\r\n <mt-textarea-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"schemaJsonValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaJsonPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n rows=\"3\"\r\n />\r\n }\r\n @case (\"array\") {\r\n <mt-textarea-field\r\n class=\"md:col-span-2\"\r\n [ngModel]=\"schemaJsonValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaJsonPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n rows=\"3\"\r\n />\r\n }\r\n @default {\r\n <mt-text-field\r\n [ngModel]=\"schemaTextValue(field.key)\"\r\n (ngModelChange)=\"\r\n setSchemaPayloadValue(field.key, $event)\r\n \"\r\n [label]=\"field.label\"\r\n [placeholder]=\"field.placeholder\"\r\n [required]=\"field.required\"\r\n />\r\n }\r\n }\r\n }\r\n }\r\n </div>\r\n } @else {\r\n <p\r\n class=\"m-0 rounded-md border border-dashed border-(--p-content-border-color) bg-(--p-surface-50) px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n This trigger has no input schema. JSON payload is optional.\r\n </p>\r\n }\r\n </section>\r\n\r\n <section\r\n class=\"overflow-hidden rounded-md border border-surface-200 bg-(--p-content-background)\"\r\n >\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n Advanced JSON\r\n </p>\r\n <p\r\n class=\"m-0 text-[11.5px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Use this only when the backend trigger schema does not\r\n expose all needed values.\r\n </p>\r\n </div>\r\n @if (payloadFields().length > 0) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"\r\n payloadOpen()\r\n ? 'arrow.chevron-up'\r\n : 'arrow.chevron-down'\r\n \"\r\n [label]=\"payloadOpen() ? 'Hide JSON' : 'Show JSON'\"\r\n (onClick)=\"togglePayload()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (payloadFields().length === 0 || payloadOpen()) {\r\n <div class=\"space-y-2 p-3\">\r\n <mt-textarea-field\r\n [(ngModel)]=\"payloadText\"\r\n [rows]=\"4\"\r\n label=\"Advanced JSON payload\"\r\n placeholder='{\"amount\": 100}'\r\n />\r\n @if (payloadError()) {\r\n <p\r\n class=\"m-0 text-[11px] font-medium text-[rgb(var(--fp-error))]\"\r\n >\r\n {{ payloadError() }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n\r\n<ng-template #nodeRunNodeCellTpl let-row>\r\n <div class=\"min-w-0\">\r\n <span\r\n class=\"block truncate text-[12.5px] font-semibold text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.error) {\r\n <span\r\n class=\"block truncate text-[11.5px] leading-5 text-[rgb(var(--fp-error))]\"\r\n [title]=\"row.error\"\r\n >\r\n {{ row.error }}\r\n </span>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #nodeRunStatusCellTpl let-row>\r\n <span\r\n [class]=\"\r\n 'inline-flex max-w-full truncate rounded-full border px-2 py-0.5 text-[10.5px] font-semibold ' +\r\n row.statusTone\r\n \"\r\n [title]=\"statusLabel(row.status)\"\r\n >\r\n {{ statusLabel(row.status) }}\r\n </span>\r\n</ng-template>\r\n\r\n<ng-template #nodeRunDetailCellTpl let-row>\r\n <div\r\n class=\"flex min-w-0 flex-wrap gap-x-3 gap-y-1 text-[11px] text-(--p-text-muted-color)\"\r\n >\r\n @if (row.duration) {\r\n <span>{{ row.duration }}</span>\r\n }\r\n @if (row.routeOutput) {\r\n <span>Output {{ row.routeOutput }}</span>\r\n }\r\n @if (row.waitReason) {\r\n <span>Wait {{ row.waitReason }}</span>\r\n }\r\n @if (!row.duration && !row.routeOutput && !row.waitReason) {\r\n <span>-</span>\r\n }\r\n </div>\r\n</ng-template>\r\n" }]
|
|
26689
26692
|
}], ctorParameters: () => [], propDecorators: { nodeRunNodeCellTpl: [{ type: i0.ViewChild, args: ['nodeRunNodeCellTpl', { isSignal: true }] }], nodeRunStatusCellTpl: [{ type: i0.ViewChild, args: ['nodeRunStatusCellTpl', { isSignal: true }] }], nodeRunDetailCellTpl: [{ type: i0.ViewChild, args: ['nodeRunDetailCellTpl', { isSignal: true }] }] } });
|
|
26690
26693
|
function schemaFieldsForTrigger(trigger, lang) {
|
|
26691
26694
|
const schema = triggerSchema(trigger);
|
|
@@ -28341,7 +28344,7 @@ class BottomPanelComponent {
|
|
|
28341
28344
|
return Math.min(Math.max(Math.round(safeHeight), minHeight), this.maxPanelHeightPx());
|
|
28342
28345
|
}
|
|
28343
28346
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BottomPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
28344
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: BottomPanelComponent, isStandalone: true, selector: "fp-bottom-panel", outputs: { focus: "focus" }, host: { properties: { "style.height.px": "panelHeightPx()", "class.select-none": "resizing()", "class.transition-none": "resizing()" }, classAttribute: "absolute start-11 end-0 bottom-0 z-[5] flex min-h-10 flex-col bg-(--p-content-background) border-t border-(--p-content-border-color) transition-[height] duration-200" }, viewQueries: [{ propertyName: "contextLabelCellTpl", first: true, predicate: ["contextLabelCellTpl"], descendants: true, isSignal: true }, { propertyName: "contextExpressionCellTpl", first: true, predicate: ["contextExpressionCellTpl"], descendants: true, isSignal: true }, { propertyName: "contextSampleCellTpl", first: true, predicate: ["contextSampleCellTpl"], descendants: true, isSignal: true }, { propertyName: "contextSourceCellTpl", first: true, predicate: ["contextSourceCellTpl"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (store.ui().bottomPanelOpen) {\r\n <div\r\n class=\"group relative h-2 flex-none cursor-ns-resize touch-none outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)/35\"\r\n role=\"separator\"\r\n tabindex=\"0\"\r\n aria-orientation=\"horizontal\"\r\n aria-label=\"Resize bottom panel\"\r\n title=\"Drag to resize. Drag down to hide. Double-click to reset.\"\r\n [attr.aria-valuemin]=\"minPanelHeightPx\"\r\n [attr.aria-valuemax]=\"maxPanelHeightPx()\"\r\n [attr.aria-valuenow]=\"panelHeightPx()\"\r\n (pointerdown)=\"onResizeHandlePointerDown($event)\"\r\n (keydown)=\"onResizeHandleKeydown($event)\"\r\n (dblclick)=\"resetPanelHeight()\"\r\n >\r\n <span\r\n class=\"absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-(--p-content-border-color) transition-colors group-hover:bg-(--p-primary-color)/60\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"absolute left-1/2 top-1/2 h-1.5 w-12 -translate-x-1/2 -translate-y-1/2 rounded-full border border-(--p-content-border-color) bg-(--p-content-background) shadow-[0_1px_4px_rgb(15_23_42/0.10)] transition-colors group-hover:border-(--p-primary-color)/50\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n </div>\r\n}\r\n\r\n<ng-template #contextLabelCellTpl let-row>\r\n <div class=\"min-w-0\" [style.padding-inline-start.px]=\"row.depth * 16\">\r\n <div class=\"flex min-w-0 items-center gap-1.5\">\r\n <span\r\n class=\"truncate font-medium text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.isNested) {\r\n <span class=\"text-[10.5px] text-(--p-text-muted-color)\">field</span>\r\n }\r\n @if (row.isSecret) {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-error))]/12 px-2 py-0.5 text-[10.5px] font-medium text-[rgb(var(--fp-error))]\"\r\n >\r\n sensitive\r\n </span>\r\n }\r\n @if (row.isUnavailable) {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-warning))]/15 px-2 py-0.5 text-[10.5px] font-medium text-[rgb(146_91_0)]\"\r\n >\r\n later\r\n </span>\r\n }\r\n </div>\r\n @if (row.description) {\r\n <p class=\"m-0 truncate text-[11px] text-(--p-text-muted-color)\">\r\n {{ row.description }}\r\n </p>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #contextSampleCellTpl let-row>\r\n <div class=\"flex min-w-0 flex-wrap items-center gap-1.5\">\r\n <span\r\n [class]=\"\r\n 'inline-flex max-w-full truncate rounded-full border px-2 py-0.5 text-[11px] font-medium ' +\r\n contextStatusTone(row)\r\n \"\r\n [title]=\"contextStatusLabel(row)\"\r\n >\r\n {{ contextStatusLabel(row) }}\r\n </span>\r\n @if (row.isWritable && !row.isNested) {\r\n <span\r\n class=\"inline-flex rounded-full border border-(--p-content-border-color) bg-(--p-content-background) px-2 py-0.5 text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n writable\r\n </span>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #contextExpressionCellTpl let-row>\r\n <code\r\n class=\"block min-w-0 truncate rounded bg-(--p-surface-100) px-2 py-1 font-mono text-[11.5px] text-(--p-text-color)\"\r\n [title]=\"row.expression\"\r\n >\r\n {{ row.expression }}\r\n </code>\r\n</ng-template>\r\n\r\n<ng-template #contextSourceCellTpl let-row>\r\n <div class=\"min-w-0\" [title]=\"contextSourceTitle(row)\">\r\n <span class=\"block truncate text-[12px] font-medium text-(--p-text-color)\">\r\n {{ row.sourceLabel }}\r\n </span>\r\n <code\r\n class=\"mt-1 inline-flex max-w-full truncate rounded bg-(--p-surface-100) px-1.5 py-0.5 font-mono text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ row.type || \"unknown\" }}\r\n </code>\r\n </div>\r\n</ng-template>\r\n\r\n<div\r\n class=\"flex h-10 flex-none items-center gap-2 border-b border-(--p-content-border-color) bg-(--p-surface-50) px-3\"\r\n>\r\n <div class=\"min-w-0 flex-1 overflow-hidden\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [options]=\"tabOptions()\"\r\n [active]=\"tab()\"\r\n (onChange)=\"onTabChange($event)\"\r\n />\r\n </div>\r\n\r\n @if (showBottomToolbarActions()) {\r\n <div\r\n class=\"flex flex-none items-center gap-1 border-s border-(--p-content-border-color) ps-2 [&_.p-button]:size-8 [&_.p-button_svg]:size-[18px]\"\r\n >\r\n @if (tab() === \"testRun\" || tab() === \"raw\") {\n <mt-button\n variant=\"text\"\n severity=\"secondary\"\n size=\"small\"\n icon=\"general.refresh-cw-05\"\r\n [tooltip]=\"'Refresh runtime overlay'\"\r\n [disabled]=\"store.runtime().loading\"\r\n (onClick)=\"refreshRuntimeOverlay()\"\n />\n }\n @if (showStopCurrentRun()) {\n <mt-button\n variant=\"text\"\n severity=\"danger\"\n size=\"small\"\n icon=\"media.stop-circle\"\n [tooltip]=\"\n store.runtimeCanceling()\n ? 'Stopping current execution'\n : 'Stop current execution'\n \"\n [loading]=\"store.runtimeCanceling()\"\n [disabled]=\"store.runtimeCanceling()\"\n (onClick)=\"stopCurrentRun()\"\n />\n }\n @if (hasRuntimeOverlay()) {\n <mt-button\n variant=\"text\"\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'Clear runtime overlay'\"\r\n (onClick)=\"clearReplay()\"\r\n />\r\n }\r\n @if (canOpenRuns()) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"layout.list\"\r\n [tooltip]=\"'Open executions'\"\r\n (onClick)=\"openRuns()\"\r\n />\r\n }\r\n @if (store.runtime().activeExecutionId) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"layout.maximize-01\"\r\n [tooltip]=\"'Open current run'\"\r\n (onClick)=\"openCurrentRun()\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <span\r\n class=\"h-5 w-px flex-none bg-(--p-content-border-color)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n\r\n @if (store.ui().bottomPanelOpen) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'flowplus.bottom.close' | transloco\"\r\n (onClick)=\"store.toggleBottomPanel(false)\"\r\n />\r\n } @else {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"arrow.chevron-up\"\r\n [tooltip]=\"'flowplus.bottom.expand' | transloco\"\r\n (onClick)=\"store.toggleBottomPanel(true)\"\r\n />\r\n }\r\n</div>\r\n\r\n@if (store.ui().bottomPanelOpen) {\r\n <div class=\"min-h-0 flex-1 overflow-hidden px-4 py-3\">\r\n @switch (tab()) {\r\n @case (\"problems\") {\r\n <fp-problems-panel (focus)=\"focus.emit($event)\" />\r\n }\r\n @case (\"testRun\") {\r\n <fp-test-run-panel />\r\n }\r\n @case (\"context\") {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Data explorer\r\n </h3>\r\n\r\n @if (store.contextCatalogLoading() && contextRows().length === 0) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.refresh-cw-05\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[440px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Loading available workflow data\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Reading expression namespaces, trigger payload paths, previous\r\n step outputs, and runtime values from the backend catalog.\r\n </p>\r\n </div>\r\n </div>\r\n } @else if (store.contextCatalogError()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10\"\r\n >\r\n <mt-icon\r\n icon=\"alert.alert-circle\"\r\n class=\"text-[rgb(var(--fp-error))] [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[520px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Context catalog could not load\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ store.contextCatalogError() }}\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Reload context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else if (!store.contextCatalog()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"layout.list\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[520px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Load workflow data catalog\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n This shows business values available to mappings and\r\n expressions: trigger input, current item, previous nodes,\r\n workflow variables, execution, context, and system data.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Load context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else if (contextRows().length === 0) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"layout.grid-01\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[540px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No context is available yet\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No data is available until a trigger or node schema is\r\n configured, or until an execution sample is loaded.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Reload context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-auto p-3\">\r\n <div class=\"flex flex-col gap-3\">\r\n @for (group of contextGroups(); track group.key) {\r\n <section\r\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <header\r\n class=\"flex items-start gap-3 border-b border-surface-200 bg-surface-50 px-3 py-2.5\"\r\n >\r\n <mt-icon\r\n [icon]=\"group.icon\"\r\n class=\"mt-0.5 text-(--p-primary-color) [&_svg]:size-4\"\r\n />\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ group.title }}\r\n <span class=\"font-normal text-(--p-text-muted-color)\">\r\n {{ group.rows.length }}\r\n </span>\r\n </p>\r\n <p\r\n class=\"m-0 mt-0.5 text-[11.5px] leading-4 text-(--p-text-muted-color)\"\r\n >\r\n {{ group.description }}\r\n </p>\r\n </div>\r\n </header>\r\n\r\n @if (group.rows.length === 0) {\r\n <div\r\n class=\"px-3 py-3 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No workflow variables yet. Open Workflow variables from\r\n the left rail to add reusable values.\r\n </div>\r\n } @else {\r\n <mt-table\r\n [data]=\"group.rows\"\r\n [columns]=\"contextColumns()\"\r\n [rowActions]=\"contextRowActions()\"\r\n dataKey=\"key\"\r\n size=\"small\"\r\n [noCard]=\"true\"\r\n [tableLayout]=\"'fixed'\"\r\n [pageSize]=\"50\"\r\n [rowsPerPageOptions]=\"[25, 50, 100]\"\r\n [alwaysShowPaginator]=\"group.rows.length > 50\"\r\n />\r\n }\r\n </section>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n }\r\n @case (\"raw\") {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Debug payloads\r\n </h3>\r\n\r\n <div\r\n class=\"flex flex-none flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-(--p-content-background) px-3 py-2\"\r\n >\r\n <mt-tabs\r\n mode=\"underline\"\r\n [fluid]=\"true\"\r\n [options]=\"rawTabOptions()\"\r\n [active]=\"rawTab()\"\r\n (onChange)=\"setRawTab($event)\"\r\n />\r\n <div class=\"flex flex-none items-center gap-1\">\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Refresh\"\r\n (onClick)=\"\r\n rawTab() === 'runtime'\r\n ? refreshRuntimeOverlay()\r\n : reloadContextCatalog()\r\n \"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"dev.code-02\"\r\n [label]=\"rawFullPayload() ? 'Compact JSON' : 'Full JSON'\"\r\n [tooltip]=\"\r\n rawFullPayload()\r\n ? 'Show compact support snapshot'\r\n : 'Show full raw DTOs'\r\n \"\r\n (onClick)=\"toggleRawPayloadMode()\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [label]=\"rawCopyLabel()\"\r\n (onClick)=\"copyRaw()\"\r\n />\r\n </div>\r\n </div>\r\n\r\n @if (rawTab() === \"graph\") {\r\n <div\r\n class=\"flex flex-none flex-wrap items-end gap-2 border-b border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <div class=\"min-w-[15rem] max-w-[30rem] flex-1\">\r\n <mt-select-field\r\n [ngModel]=\"rawGraphScopeValue()\"\r\n (ngModelChange)=\"setRawGraphScope($event)\"\r\n label=\"Debug scope\"\r\n [options]=\"rawGraphScopeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <span\r\n class=\"mb-1.5 inline-flex min-w-0 max-w-full rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"rawGraphScopeSummary()\"\r\n >\r\n <span class=\"truncate\">{{ rawGraphScopeSummary() }}</span>\r\n </span>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"border-b border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ rawPayloadDescription() }}\r\n </div>\r\n\r\n @if (rawRuntimeEmpty()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.activity\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[480px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No execution loaded yet\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Debug payloads appear after a trigger run or after loading an\r\n execution from the executions page.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger\"\r\n (onClick)=\"onTabChange('testRun')\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.link-external-02\"\r\n label=\"Open executions\"\r\n (onClick)=\"openRuns()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <pre\r\n class=\"fp-scroll m-0 min-h-0 flex-1 overflow-auto bg-(--p-surface-100) p-3.5 font-mono text-[11px] leading-[1.65] text-(--p-text-color)\"\r\n >{{ rawJson() }}</pre\r\n >\r\n }\r\n </section>\r\n }\r\n }\r\n </div>\r\n}\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: ProblemsPanelComponent, selector: "fp-problems-panel", outputs: ["focus"] }, { kind: "component", type: TestRunPanelComponent, selector: "fp-test-run-panel" }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
|
|
28347
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: BottomPanelComponent, isStandalone: true, selector: "fp-bottom-panel", outputs: { focus: "focus" }, host: { properties: { "style.height.px": "panelHeightPx()", "class.select-none": "resizing()", "class.transition-none": "resizing()" }, classAttribute: "absolute start-11 end-0 bottom-0 z-[5] flex min-h-10 flex-col bg-(--p-content-background) border-t border-(--p-content-border-color) transition-[height] duration-200" }, viewQueries: [{ propertyName: "contextLabelCellTpl", first: true, predicate: ["contextLabelCellTpl"], descendants: true, isSignal: true }, { propertyName: "contextExpressionCellTpl", first: true, predicate: ["contextExpressionCellTpl"], descendants: true, isSignal: true }, { propertyName: "contextSampleCellTpl", first: true, predicate: ["contextSampleCellTpl"], descendants: true, isSignal: true }, { propertyName: "contextSourceCellTpl", first: true, predicate: ["contextSourceCellTpl"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (store.ui().bottomPanelOpen) {\r\n <div\r\n class=\"group relative h-2 flex-none cursor-ns-resize touch-none outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)/35\"\r\n role=\"separator\"\r\n tabindex=\"0\"\r\n aria-orientation=\"horizontal\"\r\n aria-label=\"Resize bottom panel\"\r\n title=\"Drag to resize. Drag down to hide. Double-click to reset.\"\r\n [attr.aria-valuemin]=\"minPanelHeightPx\"\r\n [attr.aria-valuemax]=\"maxPanelHeightPx()\"\r\n [attr.aria-valuenow]=\"panelHeightPx()\"\r\n (pointerdown)=\"onResizeHandlePointerDown($event)\"\r\n (keydown)=\"onResizeHandleKeydown($event)\"\r\n (dblclick)=\"resetPanelHeight()\"\r\n >\r\n <span\r\n class=\"absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-(--p-content-border-color) transition-colors group-hover:bg-(--p-primary-color)/60\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"absolute left-1/2 top-1/2 h-1.5 w-12 -translate-x-1/2 -translate-y-1/2 rounded-full border border-(--p-content-border-color) bg-(--p-content-background) shadow-[0_1px_4px_rgb(15_23_42/0.10)] transition-colors group-hover:border-(--p-primary-color)/50\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n </div>\r\n}\r\n\r\n<ng-template #contextLabelCellTpl let-row>\r\n <div class=\"min-w-0\" [style.padding-inline-start.px]=\"row.depth * 16\">\r\n <div class=\"flex min-w-0 items-center gap-1.5\">\r\n <span\r\n class=\"truncate font-medium text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.isNested) {\r\n <span class=\"text-[10.5px] text-(--p-text-muted-color)\">field</span>\r\n }\r\n @if (row.isSecret) {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-error))]/12 px-2 py-0.5 text-[10.5px] font-medium text-[rgb(var(--fp-error))]\"\r\n >\r\n sensitive\r\n </span>\r\n }\r\n @if (row.isUnavailable) {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-warning))]/15 px-2 py-0.5 text-[10.5px] font-medium text-[rgb(146_91_0)]\"\r\n >\r\n later\r\n </span>\r\n }\r\n </div>\r\n @if (row.description) {\r\n <p class=\"m-0 truncate text-[11px] text-(--p-text-muted-color)\">\r\n {{ row.description }}\r\n </p>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #contextSampleCellTpl let-row>\r\n <div class=\"flex min-w-0 flex-wrap items-center gap-1.5\">\r\n <span\r\n [class]=\"\r\n 'inline-flex max-w-full truncate rounded-full border px-2 py-0.5 text-[11px] font-medium ' +\r\n contextStatusTone(row)\r\n \"\r\n [title]=\"contextStatusLabel(row)\"\r\n >\r\n {{ contextStatusLabel(row) }}\r\n </span>\r\n @if (row.isWritable && !row.isNested) {\r\n <span\r\n class=\"inline-flex rounded-full border border-(--p-content-border-color) bg-(--p-content-background) px-2 py-0.5 text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n writable\r\n </span>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #contextExpressionCellTpl let-row>\r\n <code\r\n class=\"block min-w-0 truncate rounded bg-(--p-surface-100) px-2 py-1 font-mono text-[11.5px] text-(--p-text-color)\"\r\n [title]=\"row.expression\"\r\n >\r\n {{ row.expression }}\r\n </code>\r\n</ng-template>\r\n\r\n<ng-template #contextSourceCellTpl let-row>\r\n <div class=\"min-w-0\" [title]=\"contextSourceTitle(row)\">\r\n <span class=\"block truncate text-[12px] font-medium text-(--p-text-color)\">\r\n {{ row.sourceLabel }}\r\n </span>\r\n <code\r\n class=\"mt-1 inline-flex max-w-full truncate rounded bg-(--p-surface-100) px-1.5 py-0.5 font-mono text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ row.type || \"unknown\" }}\r\n </code>\r\n </div>\r\n</ng-template>\r\n\r\n<div\r\n class=\"flex h-10 flex-none items-center gap-2 border-b border-(--p-content-border-color) bg-(--p-surface-50) px-3\"\r\n>\r\n <div class=\"min-w-0 flex-1 overflow-hidden\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [options]=\"tabOptions()\"\r\n [active]=\"tab()\"\r\n (onChange)=\"onTabChange($event)\"\r\n />\r\n </div>\r\n\r\n @if (showBottomToolbarActions()) {\r\n <div\r\n class=\"flex flex-none items-center gap-1 border-s border-(--p-content-border-color) ps-2 [&_.p-button]:size-8 [&_.p-button_svg]:size-[18px]\"\r\n >\r\n @if (tab() === \"testRun\" || tab() === \"raw\") {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.refresh-cw-05\"\r\n [tooltip]=\"'Refresh runtime overlay'\"\r\n [disabled]=\"store.runtime().loading\"\r\n (onClick)=\"refreshRuntimeOverlay()\"\r\n />\r\n }\r\n @if (showStopCurrentRun()) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"media.stop-circle\"\r\n [tooltip]=\"\r\n store.runtimeCanceling()\r\n ? 'Stopping current execution'\r\n : 'Stop current execution'\r\n \"\r\n [loading]=\"store.runtimeCanceling()\"\r\n [disabled]=\"store.runtimeCanceling()\"\r\n (onClick)=\"stopCurrentRun()\"\r\n />\r\n }\r\n @if (hasRuntimeOverlay()) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'Clear runtime overlay'\"\r\n (onClick)=\"clearReplay()\"\r\n />\r\n }\r\n @if (canOpenRuns()) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"layout.list\"\r\n [tooltip]=\"'Open executions'\"\r\n (onClick)=\"openRuns()\"\r\n />\r\n }\r\n @if (store.runtime().activeExecutionId) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"layout.maximize-01\"\r\n [tooltip]=\"'Open current run'\"\r\n (onClick)=\"openCurrentRun()\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <span\r\n class=\"h-5 w-px flex-none bg-(--p-content-border-color)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n\r\n @if (store.ui().bottomPanelOpen) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'flowplus.bottom.close' | transloco\"\r\n (onClick)=\"store.toggleBottomPanel(false)\"\r\n />\r\n } @else {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"arrow.chevron-up\"\r\n [tooltip]=\"'flowplus.bottom.expand' | transloco\"\r\n (onClick)=\"store.toggleBottomPanel(true)\"\r\n />\r\n }\r\n</div>\r\n\r\n@if (store.ui().bottomPanelOpen) {\r\n <div class=\"min-h-0 flex-1 overflow-hidden px-4 py-3\">\r\n @switch (tab()) {\r\n @case (\"problems\") {\r\n <fp-problems-panel (focus)=\"focus.emit($event)\" />\r\n }\r\n @case (\"testRun\") {\r\n <fp-test-run-panel />\r\n }\r\n @case (\"context\") {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Data explorer\r\n </h3>\r\n\r\n @if (store.contextCatalogLoading() && contextRows().length === 0) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.refresh-cw-05\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[440px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Loading available workflow data\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Reading expression namespaces, trigger payload paths, previous\r\n step outputs, and runtime values from the backend catalog.\r\n </p>\r\n </div>\r\n </div>\r\n } @else if (store.contextCatalogError()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10\"\r\n >\r\n <mt-icon\r\n icon=\"alert.alert-circle\"\r\n class=\"text-[rgb(var(--fp-error))] [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[520px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Context catalog could not load\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ store.contextCatalogError() }}\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Reload context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else if (!store.contextCatalog()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"layout.list\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[520px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Load workflow data catalog\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n This shows business values available to mappings and\r\n expressions: trigger input, current item, previous nodes,\r\n workflow variables, execution, context, and system data.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Load context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else if (contextRows().length === 0) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"layout.grid-01\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[540px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No context is available yet\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No data is available until a trigger or node schema is\r\n configured, or until an execution sample is loaded.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Reload context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-auto p-3\">\r\n <div class=\"flex flex-col gap-3\">\r\n @for (group of contextGroups(); track group.key) {\r\n <section\r\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <header\r\n class=\"flex items-start gap-3 border-b border-surface-200 bg-surface-50 px-3 py-2.5\"\r\n >\r\n <mt-icon\r\n [icon]=\"group.icon\"\r\n class=\"mt-0.5 text-(--p-primary-color) [&_svg]:size-4\"\r\n />\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ group.title }}\r\n <span class=\"font-normal text-(--p-text-muted-color)\">\r\n {{ group.rows.length }}\r\n </span>\r\n </p>\r\n <p\r\n class=\"m-0 mt-0.5 text-[11.5px] leading-4 text-(--p-text-muted-color)\"\r\n >\r\n {{ group.description }}\r\n </p>\r\n </div>\r\n </header>\r\n\r\n @if (group.rows.length === 0) {\r\n <div\r\n class=\"px-3 py-3 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No workflow variables yet. Open Workflow variables from\r\n the left rail to add reusable values.\r\n </div>\r\n } @else {\r\n <mt-table\r\n [data]=\"group.rows\"\r\n [columns]=\"contextColumns()\"\r\n [rowActions]=\"contextRowActions()\"\r\n dataKey=\"key\"\r\n size=\"small\"\r\n [noCard]=\"true\"\r\n [tableLayout]=\"'fixed'\"\r\n [pageSize]=\"50\"\r\n [rowsPerPageOptions]=\"[25, 50, 100]\"\r\n [alwaysShowPaginator]=\"group.rows.length > 50\"\r\n />\r\n }\r\n </section>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n }\r\n @case (\"raw\") {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Debug payloads\r\n </h3>\r\n\r\n <div\r\n class=\"flex flex-none flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-(--p-content-background) px-3 py-2\"\r\n >\r\n <mt-tabs\r\n mode=\"underline\"\r\n [fluid]=\"true\"\r\n [options]=\"rawTabOptions()\"\r\n [active]=\"rawTab()\"\r\n (onChange)=\"setRawTab($event)\"\r\n />\r\n <div class=\"flex flex-none items-center gap-1\">\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Refresh\"\r\n (onClick)=\"\r\n rawTab() === 'runtime'\r\n ? refreshRuntimeOverlay()\r\n : reloadContextCatalog()\r\n \"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"dev.code-02\"\r\n [label]=\"rawFullPayload() ? 'Compact JSON' : 'Full JSON'\"\r\n [tooltip]=\"\r\n rawFullPayload()\r\n ? 'Show compact support snapshot'\r\n : 'Show full raw DTOs'\r\n \"\r\n (onClick)=\"toggleRawPayloadMode()\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [label]=\"rawCopyLabel()\"\r\n (onClick)=\"copyRaw()\"\r\n />\r\n </div>\r\n </div>\r\n\r\n @if (rawTab() === \"graph\") {\r\n <div\r\n class=\"flex flex-none flex-wrap items-end gap-2 border-b border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <div class=\"min-w-[15rem] max-w-[30rem] flex-1\">\r\n <mt-select-field\r\n [ngModel]=\"rawGraphScopeValue()\"\r\n (ngModelChange)=\"setRawGraphScope($event)\"\r\n label=\"Debug scope\"\r\n [options]=\"rawGraphScopeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <span\r\n class=\"mb-1.5 inline-flex min-w-0 max-w-full rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"rawGraphScopeSummary()\"\r\n >\r\n <span class=\"truncate\">{{ rawGraphScopeSummary() }}</span>\r\n </span>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"border-b border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ rawPayloadDescription() }}\r\n </div>\r\n\r\n @if (rawRuntimeEmpty()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.activity\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[480px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No execution loaded yet\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Debug payloads appear after a trigger run or after loading an\r\n execution from the executions page.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger\"\r\n (onClick)=\"onTabChange('testRun')\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.link-external-02\"\r\n label=\"Open executions\"\r\n (onClick)=\"openRuns()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <pre\r\n class=\"fp-scroll m-0 min-h-0 flex-1 overflow-auto bg-(--p-surface-100) p-3.5 font-mono text-[11px] leading-[1.65] text-(--p-text-color)\"\r\n >{{ rawJson() }}</pre\r\n >\r\n }\r\n </section>\r\n }\r\n }\r\n </div>\r\n}\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: ProblemsPanelComponent, selector: "fp-problems-panel", outputs: ["focus"] }, { kind: "component", type: TestRunPanelComponent, selector: "fp-test-run-panel" }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
|
|
28345
28348
|
}
|
|
28346
28349
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BottomPanelComponent, decorators: [{
|
|
28347
28350
|
type: Component,
|
|
@@ -28361,7 +28364,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
28361
28364
|
'[style.height.px]': 'panelHeightPx()',
|
|
28362
28365
|
'[class.select-none]': 'resizing()',
|
|
28363
28366
|
'[class.transition-none]': 'resizing()',
|
|
28364
|
-
}, template: "@if (store.ui().bottomPanelOpen) {\r\n <div\r\n class=\"group relative h-2 flex-none cursor-ns-resize touch-none outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)/35\"\r\n role=\"separator\"\r\n tabindex=\"0\"\r\n aria-orientation=\"horizontal\"\r\n aria-label=\"Resize bottom panel\"\r\n title=\"Drag to resize. Drag down to hide. Double-click to reset.\"\r\n [attr.aria-valuemin]=\"minPanelHeightPx\"\r\n [attr.aria-valuemax]=\"maxPanelHeightPx()\"\r\n [attr.aria-valuenow]=\"panelHeightPx()\"\r\n (pointerdown)=\"onResizeHandlePointerDown($event)\"\r\n (keydown)=\"onResizeHandleKeydown($event)\"\r\n (dblclick)=\"resetPanelHeight()\"\r\n >\r\n <span\r\n class=\"absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-(--p-content-border-color) transition-colors group-hover:bg-(--p-primary-color)/60\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"absolute left-1/2 top-1/2 h-1.5 w-12 -translate-x-1/2 -translate-y-1/2 rounded-full border border-(--p-content-border-color) bg-(--p-content-background) shadow-[0_1px_4px_rgb(15_23_42/0.10)] transition-colors group-hover:border-(--p-primary-color)/50\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n </div>\r\n}\r\n\r\n<ng-template #contextLabelCellTpl let-row>\r\n <div class=\"min-w-0\" [style.padding-inline-start.px]=\"row.depth * 16\">\r\n <div class=\"flex min-w-0 items-center gap-1.5\">\r\n <span\r\n class=\"truncate font-medium text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.isNested) {\r\n <span class=\"text-[10.5px] text-(--p-text-muted-color)\">field</span>\r\n }\r\n @if (row.isSecret) {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-error))]/12 px-2 py-0.5 text-[10.5px] font-medium text-[rgb(var(--fp-error))]\"\r\n >\r\n sensitive\r\n </span>\r\n }\r\n @if (row.isUnavailable) {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-warning))]/15 px-2 py-0.5 text-[10.5px] font-medium text-[rgb(146_91_0)]\"\r\n >\r\n later\r\n </span>\r\n }\r\n </div>\r\n @if (row.description) {\r\n <p class=\"m-0 truncate text-[11px] text-(--p-text-muted-color)\">\r\n {{ row.description }}\r\n </p>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #contextSampleCellTpl let-row>\r\n <div class=\"flex min-w-0 flex-wrap items-center gap-1.5\">\r\n <span\r\n [class]=\"\r\n 'inline-flex max-w-full truncate rounded-full border px-2 py-0.5 text-[11px] font-medium ' +\r\n contextStatusTone(row)\r\n \"\r\n [title]=\"contextStatusLabel(row)\"\r\n >\r\n {{ contextStatusLabel(row) }}\r\n </span>\r\n @if (row.isWritable && !row.isNested) {\r\n <span\r\n class=\"inline-flex rounded-full border border-(--p-content-border-color) bg-(--p-content-background) px-2 py-0.5 text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n writable\r\n </span>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #contextExpressionCellTpl let-row>\r\n <code\r\n class=\"block min-w-0 truncate rounded bg-(--p-surface-100) px-2 py-1 font-mono text-[11.5px] text-(--p-text-color)\"\r\n [title]=\"row.expression\"\r\n >\r\n {{ row.expression }}\r\n </code>\r\n</ng-template>\r\n\r\n<ng-template #contextSourceCellTpl let-row>\r\n <div class=\"min-w-0\" [title]=\"contextSourceTitle(row)\">\r\n <span class=\"block truncate text-[12px] font-medium text-(--p-text-color)\">\r\n {{ row.sourceLabel }}\r\n </span>\r\n <code\r\n class=\"mt-1 inline-flex max-w-full truncate rounded bg-(--p-surface-100) px-1.5 py-0.5 font-mono text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ row.type || \"unknown\" }}\r\n </code>\r\n </div>\r\n</ng-template>\r\n\r\n<div\r\n class=\"flex h-10 flex-none items-center gap-2 border-b border-(--p-content-border-color) bg-(--p-surface-50) px-3\"\r\n>\r\n <div class=\"min-w-0 flex-1 overflow-hidden\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [options]=\"tabOptions()\"\r\n [active]=\"tab()\"\r\n (onChange)=\"onTabChange($event)\"\r\n />\r\n </div>\r\n\r\n @if (showBottomToolbarActions()) {\r\n <div\r\n class=\"flex flex-none items-center gap-1 border-s border-(--p-content-border-color) ps-2 [&_.p-button]:size-8 [&_.p-button_svg]:size-[18px]\"\r\n >\r\n @if (tab() === \"testRun\" || tab() === \"raw\") {\n <mt-button\n variant=\"text\"\n severity=\"secondary\"\n size=\"small\"\n icon=\"general.refresh-cw-05\"\r\n [tooltip]=\"'Refresh runtime overlay'\"\r\n [disabled]=\"store.runtime().loading\"\r\n (onClick)=\"refreshRuntimeOverlay()\"\n />\n }\n @if (showStopCurrentRun()) {\n <mt-button\n variant=\"text\"\n severity=\"danger\"\n size=\"small\"\n icon=\"media.stop-circle\"\n [tooltip]=\"\n store.runtimeCanceling()\n ? 'Stopping current execution'\n : 'Stop current execution'\n \"\n [loading]=\"store.runtimeCanceling()\"\n [disabled]=\"store.runtimeCanceling()\"\n (onClick)=\"stopCurrentRun()\"\n />\n }\n @if (hasRuntimeOverlay()) {\n <mt-button\n variant=\"text\"\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'Clear runtime overlay'\"\r\n (onClick)=\"clearReplay()\"\r\n />\r\n }\r\n @if (canOpenRuns()) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"layout.list\"\r\n [tooltip]=\"'Open executions'\"\r\n (onClick)=\"openRuns()\"\r\n />\r\n }\r\n @if (store.runtime().activeExecutionId) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"layout.maximize-01\"\r\n [tooltip]=\"'Open current run'\"\r\n (onClick)=\"openCurrentRun()\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <span\r\n class=\"h-5 w-px flex-none bg-(--p-content-border-color)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n\r\n @if (store.ui().bottomPanelOpen) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'flowplus.bottom.close' | transloco\"\r\n (onClick)=\"store.toggleBottomPanel(false)\"\r\n />\r\n } @else {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"arrow.chevron-up\"\r\n [tooltip]=\"'flowplus.bottom.expand' | transloco\"\r\n (onClick)=\"store.toggleBottomPanel(true)\"\r\n />\r\n }\r\n</div>\r\n\r\n@if (store.ui().bottomPanelOpen) {\r\n <div class=\"min-h-0 flex-1 overflow-hidden px-4 py-3\">\r\n @switch (tab()) {\r\n @case (\"problems\") {\r\n <fp-problems-panel (focus)=\"focus.emit($event)\" />\r\n }\r\n @case (\"testRun\") {\r\n <fp-test-run-panel />\r\n }\r\n @case (\"context\") {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Data explorer\r\n </h3>\r\n\r\n @if (store.contextCatalogLoading() && contextRows().length === 0) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.refresh-cw-05\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[440px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Loading available workflow data\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Reading expression namespaces, trigger payload paths, previous\r\n step outputs, and runtime values from the backend catalog.\r\n </p>\r\n </div>\r\n </div>\r\n } @else if (store.contextCatalogError()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10\"\r\n >\r\n <mt-icon\r\n icon=\"alert.alert-circle\"\r\n class=\"text-[rgb(var(--fp-error))] [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[520px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Context catalog could not load\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ store.contextCatalogError() }}\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Reload context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else if (!store.contextCatalog()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"layout.list\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[520px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Load workflow data catalog\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n This shows business values available to mappings and\r\n expressions: trigger input, current item, previous nodes,\r\n workflow variables, execution, context, and system data.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Load context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else if (contextRows().length === 0) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"layout.grid-01\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[540px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No context is available yet\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No data is available until a trigger or node schema is\r\n configured, or until an execution sample is loaded.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Reload context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-auto p-3\">\r\n <div class=\"flex flex-col gap-3\">\r\n @for (group of contextGroups(); track group.key) {\r\n <section\r\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <header\r\n class=\"flex items-start gap-3 border-b border-surface-200 bg-surface-50 px-3 py-2.5\"\r\n >\r\n <mt-icon\r\n [icon]=\"group.icon\"\r\n class=\"mt-0.5 text-(--p-primary-color) [&_svg]:size-4\"\r\n />\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ group.title }}\r\n <span class=\"font-normal text-(--p-text-muted-color)\">\r\n {{ group.rows.length }}\r\n </span>\r\n </p>\r\n <p\r\n class=\"m-0 mt-0.5 text-[11.5px] leading-4 text-(--p-text-muted-color)\"\r\n >\r\n {{ group.description }}\r\n </p>\r\n </div>\r\n </header>\r\n\r\n @if (group.rows.length === 0) {\r\n <div\r\n class=\"px-3 py-3 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No workflow variables yet. Open Workflow variables from\r\n the left rail to add reusable values.\r\n </div>\r\n } @else {\r\n <mt-table\r\n [data]=\"group.rows\"\r\n [columns]=\"contextColumns()\"\r\n [rowActions]=\"contextRowActions()\"\r\n dataKey=\"key\"\r\n size=\"small\"\r\n [noCard]=\"true\"\r\n [tableLayout]=\"'fixed'\"\r\n [pageSize]=\"50\"\r\n [rowsPerPageOptions]=\"[25, 50, 100]\"\r\n [alwaysShowPaginator]=\"group.rows.length > 50\"\r\n />\r\n }\r\n </section>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n }\r\n @case (\"raw\") {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Debug payloads\r\n </h3>\r\n\r\n <div\r\n class=\"flex flex-none flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-(--p-content-background) px-3 py-2\"\r\n >\r\n <mt-tabs\r\n mode=\"underline\"\r\n [fluid]=\"true\"\r\n [options]=\"rawTabOptions()\"\r\n [active]=\"rawTab()\"\r\n (onChange)=\"setRawTab($event)\"\r\n />\r\n <div class=\"flex flex-none items-center gap-1\">\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Refresh\"\r\n (onClick)=\"\r\n rawTab() === 'runtime'\r\n ? refreshRuntimeOverlay()\r\n : reloadContextCatalog()\r\n \"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"dev.code-02\"\r\n [label]=\"rawFullPayload() ? 'Compact JSON' : 'Full JSON'\"\r\n [tooltip]=\"\r\n rawFullPayload()\r\n ? 'Show compact support snapshot'\r\n : 'Show full raw DTOs'\r\n \"\r\n (onClick)=\"toggleRawPayloadMode()\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [label]=\"rawCopyLabel()\"\r\n (onClick)=\"copyRaw()\"\r\n />\r\n </div>\r\n </div>\r\n\r\n @if (rawTab() === \"graph\") {\r\n <div\r\n class=\"flex flex-none flex-wrap items-end gap-2 border-b border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <div class=\"min-w-[15rem] max-w-[30rem] flex-1\">\r\n <mt-select-field\r\n [ngModel]=\"rawGraphScopeValue()\"\r\n (ngModelChange)=\"setRawGraphScope($event)\"\r\n label=\"Debug scope\"\r\n [options]=\"rawGraphScopeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <span\r\n class=\"mb-1.5 inline-flex min-w-0 max-w-full rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"rawGraphScopeSummary()\"\r\n >\r\n <span class=\"truncate\">{{ rawGraphScopeSummary() }}</span>\r\n </span>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"border-b border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ rawPayloadDescription() }}\r\n </div>\r\n\r\n @if (rawRuntimeEmpty()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.activity\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[480px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No execution loaded yet\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Debug payloads appear after a trigger run or after loading an\r\n execution from the executions page.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger\"\r\n (onClick)=\"onTabChange('testRun')\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.link-external-02\"\r\n label=\"Open executions\"\r\n (onClick)=\"openRuns()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <pre\r\n class=\"fp-scroll m-0 min-h-0 flex-1 overflow-auto bg-(--p-surface-100) p-3.5 font-mono text-[11px] leading-[1.65] text-(--p-text-color)\"\r\n >{{ rawJson() }}</pre\r\n >\r\n }\r\n </section>\r\n }\r\n }\r\n </div>\r\n}\r\n" }]
|
|
28367
|
+
}, template: "@if (store.ui().bottomPanelOpen) {\r\n <div\r\n class=\"group relative h-2 flex-none cursor-ns-resize touch-none outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)/35\"\r\n role=\"separator\"\r\n tabindex=\"0\"\r\n aria-orientation=\"horizontal\"\r\n aria-label=\"Resize bottom panel\"\r\n title=\"Drag to resize. Drag down to hide. Double-click to reset.\"\r\n [attr.aria-valuemin]=\"minPanelHeightPx\"\r\n [attr.aria-valuemax]=\"maxPanelHeightPx()\"\r\n [attr.aria-valuenow]=\"panelHeightPx()\"\r\n (pointerdown)=\"onResizeHandlePointerDown($event)\"\r\n (keydown)=\"onResizeHandleKeydown($event)\"\r\n (dblclick)=\"resetPanelHeight()\"\r\n >\r\n <span\r\n class=\"absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-(--p-content-border-color) transition-colors group-hover:bg-(--p-primary-color)/60\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"absolute left-1/2 top-1/2 h-1.5 w-12 -translate-x-1/2 -translate-y-1/2 rounded-full border border-(--p-content-border-color) bg-(--p-content-background) shadow-[0_1px_4px_rgb(15_23_42/0.10)] transition-colors group-hover:border-(--p-primary-color)/50\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n </div>\r\n}\r\n\r\n<ng-template #contextLabelCellTpl let-row>\r\n <div class=\"min-w-0\" [style.padding-inline-start.px]=\"row.depth * 16\">\r\n <div class=\"flex min-w-0 items-center gap-1.5\">\r\n <span\r\n class=\"truncate font-medium text-(--p-text-color)\"\r\n [title]=\"row.label\"\r\n >\r\n {{ row.label }}\r\n </span>\r\n @if (row.isNested) {\r\n <span class=\"text-[10.5px] text-(--p-text-muted-color)\">field</span>\r\n }\r\n @if (row.isSecret) {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-error))]/12 px-2 py-0.5 text-[10.5px] font-medium text-[rgb(var(--fp-error))]\"\r\n >\r\n sensitive\r\n </span>\r\n }\r\n @if (row.isUnavailable) {\r\n <span\r\n class=\"rounded-full bg-[rgb(var(--fp-warning))]/15 px-2 py-0.5 text-[10.5px] font-medium text-[rgb(146_91_0)]\"\r\n >\r\n later\r\n </span>\r\n }\r\n </div>\r\n @if (row.description) {\r\n <p class=\"m-0 truncate text-[11px] text-(--p-text-muted-color)\">\r\n {{ row.description }}\r\n </p>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #contextSampleCellTpl let-row>\r\n <div class=\"flex min-w-0 flex-wrap items-center gap-1.5\">\r\n <span\r\n [class]=\"\r\n 'inline-flex max-w-full truncate rounded-full border px-2 py-0.5 text-[11px] font-medium ' +\r\n contextStatusTone(row)\r\n \"\r\n [title]=\"contextStatusLabel(row)\"\r\n >\r\n {{ contextStatusLabel(row) }}\r\n </span>\r\n @if (row.isWritable && !row.isNested) {\r\n <span\r\n class=\"inline-flex rounded-full border border-(--p-content-border-color) bg-(--p-content-background) px-2 py-0.5 text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n writable\r\n </span>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #contextExpressionCellTpl let-row>\r\n <code\r\n class=\"block min-w-0 truncate rounded bg-(--p-surface-100) px-2 py-1 font-mono text-[11.5px] text-(--p-text-color)\"\r\n [title]=\"row.expression\"\r\n >\r\n {{ row.expression }}\r\n </code>\r\n</ng-template>\r\n\r\n<ng-template #contextSourceCellTpl let-row>\r\n <div class=\"min-w-0\" [title]=\"contextSourceTitle(row)\">\r\n <span class=\"block truncate text-[12px] font-medium text-(--p-text-color)\">\r\n {{ row.sourceLabel }}\r\n </span>\r\n <code\r\n class=\"mt-1 inline-flex max-w-full truncate rounded bg-(--p-surface-100) px-1.5 py-0.5 font-mono text-[10.5px] text-(--p-text-muted-color)\"\r\n >\r\n {{ row.type || \"unknown\" }}\r\n </code>\r\n </div>\r\n</ng-template>\r\n\r\n<div\r\n class=\"flex h-10 flex-none items-center gap-2 border-b border-(--p-content-border-color) bg-(--p-surface-50) px-3\"\r\n>\r\n <div class=\"min-w-0 flex-1 overflow-hidden\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [options]=\"tabOptions()\"\r\n [active]=\"tab()\"\r\n (onChange)=\"onTabChange($event)\"\r\n />\r\n </div>\r\n\r\n @if (showBottomToolbarActions()) {\r\n <div\r\n class=\"flex flex-none items-center gap-1 border-s border-(--p-content-border-color) ps-2 [&_.p-button]:size-8 [&_.p-button_svg]:size-[18px]\"\r\n >\r\n @if (tab() === \"testRun\" || tab() === \"raw\") {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.refresh-cw-05\"\r\n [tooltip]=\"'Refresh runtime overlay'\"\r\n [disabled]=\"store.runtime().loading\"\r\n (onClick)=\"refreshRuntimeOverlay()\"\r\n />\r\n }\r\n @if (showStopCurrentRun()) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"media.stop-circle\"\r\n [tooltip]=\"\r\n store.runtimeCanceling()\r\n ? 'Stopping current execution'\r\n : 'Stop current execution'\r\n \"\r\n [loading]=\"store.runtimeCanceling()\"\r\n [disabled]=\"store.runtimeCanceling()\"\r\n (onClick)=\"stopCurrentRun()\"\r\n />\r\n }\r\n @if (hasRuntimeOverlay()) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'Clear runtime overlay'\"\r\n (onClick)=\"clearReplay()\"\r\n />\r\n }\r\n @if (canOpenRuns()) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"layout.list\"\r\n [tooltip]=\"'Open executions'\"\r\n (onClick)=\"openRuns()\"\r\n />\r\n }\r\n @if (store.runtime().activeExecutionId) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"layout.maximize-01\"\r\n [tooltip]=\"'Open current run'\"\r\n (onClick)=\"openCurrentRun()\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <span\r\n class=\"h-5 w-px flex-none bg-(--p-content-border-color)\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n\r\n @if (store.ui().bottomPanelOpen) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"general.x-close\"\r\n [tooltip]=\"'flowplus.bottom.close' | transloco\"\r\n (onClick)=\"store.toggleBottomPanel(false)\"\r\n />\r\n } @else {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n icon=\"arrow.chevron-up\"\r\n [tooltip]=\"'flowplus.bottom.expand' | transloco\"\r\n (onClick)=\"store.toggleBottomPanel(true)\"\r\n />\r\n }\r\n</div>\r\n\r\n@if (store.ui().bottomPanelOpen) {\r\n <div class=\"min-h-0 flex-1 overflow-hidden px-4 py-3\">\r\n @switch (tab()) {\r\n @case (\"problems\") {\r\n <fp-problems-panel (focus)=\"focus.emit($event)\" />\r\n }\r\n @case (\"testRun\") {\r\n <fp-test-run-panel />\r\n }\r\n @case (\"context\") {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Data explorer\r\n </h3>\r\n\r\n @if (store.contextCatalogLoading() && contextRows().length === 0) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.refresh-cw-05\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[440px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Loading available workflow data\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Reading expression namespaces, trigger payload paths, previous\r\n step outputs, and runtime values from the backend catalog.\r\n </p>\r\n </div>\r\n </div>\r\n } @else if (store.contextCatalogError()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-[rgb(var(--fp-error))]/20 bg-[rgb(var(--fp-error))]/10\"\r\n >\r\n <mt-icon\r\n icon=\"alert.alert-circle\"\r\n class=\"text-[rgb(var(--fp-error))] [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[520px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Context catalog could not load\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ store.contextCatalogError() }}\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Reload context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else if (!store.contextCatalog()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"layout.list\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[520px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n Load workflow data catalog\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n This shows business values available to mappings and\r\n expressions: trigger input, current item, previous nodes,\r\n workflow variables, execution, context, and system data.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n severity=\"primary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Load context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else if (contextRows().length === 0) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"layout.grid-01\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[540px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No context is available yet\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No data is available until a trigger or node schema is\r\n configured, or until an execution sample is loaded.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Reload context\"\r\n (onClick)=\"reloadContextCatalog()\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger to capture sample\"\r\n (onClick)=\"openRunPreviewFromContext()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fp-scroll min-h-0 flex-1 overflow-auto p-3\">\r\n <div class=\"flex flex-col gap-3\">\r\n @for (group of contextGroups(); track group.key) {\r\n <section\r\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <header\r\n class=\"flex items-start gap-3 border-b border-surface-200 bg-surface-50 px-3 py-2.5\"\r\n >\r\n <mt-icon\r\n [icon]=\"group.icon\"\r\n class=\"mt-0.5 text-(--p-primary-color) [&_svg]:size-4\"\r\n />\r\n <div class=\"min-w-0\">\r\n <p\r\n class=\"m-0 text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ group.title }}\r\n <span class=\"font-normal text-(--p-text-muted-color)\">\r\n {{ group.rows.length }}\r\n </span>\r\n </p>\r\n <p\r\n class=\"m-0 mt-0.5 text-[11.5px] leading-4 text-(--p-text-muted-color)\"\r\n >\r\n {{ group.description }}\r\n </p>\r\n </div>\r\n </header>\r\n\r\n @if (group.rows.length === 0) {\r\n <div\r\n class=\"px-3 py-3 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n No workflow variables yet. Open Workflow variables from\r\n the left rail to add reusable values.\r\n </div>\r\n } @else {\r\n <mt-table\r\n [data]=\"group.rows\"\r\n [columns]=\"contextColumns()\"\r\n [rowActions]=\"contextRowActions()\"\r\n dataKey=\"key\"\r\n size=\"small\"\r\n [noCard]=\"true\"\r\n [tableLayout]=\"'fixed'\"\r\n [pageSize]=\"50\"\r\n [rowsPerPageOptions]=\"[25, 50, 100]\"\r\n [alwaysShowPaginator]=\"group.rows.length > 50\"\r\n />\r\n }\r\n </section>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n }\r\n @case (\"raw\") {\r\n <section\r\n class=\"flex h-full min-h-0 flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\r\n >\r\n <h3\r\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\r\n >\r\n Debug payloads\r\n </h3>\r\n\r\n <div\r\n class=\"flex flex-none flex-wrap items-center justify-between gap-2 border-b border-surface-200 bg-(--p-content-background) px-3 py-2\"\r\n >\r\n <mt-tabs\r\n mode=\"underline\"\r\n [fluid]=\"true\"\r\n [options]=\"rawTabOptions()\"\r\n [active]=\"rawTab()\"\r\n (onChange)=\"setRawTab($event)\"\r\n />\r\n <div class=\"flex flex-none items-center gap-1\">\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.refresh-cw-05\"\r\n label=\"Refresh\"\r\n (onClick)=\"\r\n rawTab() === 'runtime'\r\n ? refreshRuntimeOverlay()\r\n : reloadContextCatalog()\r\n \"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"dev.code-02\"\r\n [label]=\"rawFullPayload() ? 'Compact JSON' : 'Full JSON'\"\r\n [tooltip]=\"\r\n rawFullPayload()\r\n ? 'Show compact support snapshot'\r\n : 'Show full raw DTOs'\r\n \"\r\n (onClick)=\"toggleRawPayloadMode()\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-01\"\r\n [label]=\"rawCopyLabel()\"\r\n (onClick)=\"copyRaw()\"\r\n />\r\n </div>\r\n </div>\r\n\r\n @if (rawTab() === \"graph\") {\r\n <div\r\n class=\"flex flex-none flex-wrap items-end gap-2 border-b border-surface-200 bg-surface-0 px-3 py-2\"\r\n >\r\n <div class=\"min-w-[15rem] max-w-[30rem] flex-1\">\r\n <mt-select-field\r\n [ngModel]=\"rawGraphScopeValue()\"\r\n (ngModelChange)=\"setRawGraphScope($event)\"\r\n label=\"Debug scope\"\r\n [options]=\"rawGraphScopeOptions()\"\r\n optionValue=\"value\"\r\n optionLabel=\"label\"\r\n />\r\n </div>\r\n <span\r\n class=\"mb-1.5 inline-flex min-w-0 max-w-full rounded-md border border-(--p-content-border-color) bg-(--p-surface-50) px-2.5 py-1 text-[11.5px] text-(--p-text-muted-color)\"\r\n [title]=\"rawGraphScopeSummary()\"\r\n >\r\n <span class=\"truncate\">{{ rawGraphScopeSummary() }}</span>\r\n </span>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"border-b border-surface-200 bg-surface-50 px-3 py-2 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n {{ rawPayloadDescription() }}\r\n </div>\r\n\r\n @if (rawRuntimeEmpty()) {\r\n <div\r\n class=\"flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center\"\r\n >\r\n <span\r\n class=\"inline-flex size-9 items-center justify-center rounded-md border border-(--p-content-border-color) bg-(--p-surface-50)\"\r\n >\r\n <mt-icon\r\n icon=\"general.activity\"\r\n class=\"text-(--p-primary-color) [&_svg]:size-5\"\r\n />\r\n </span>\r\n <div class=\"max-w-[480px]\">\r\n <p class=\"m-0 text-sm font-semibold text-(--p-text-color)\">\r\n No execution loaded yet\r\n </p>\r\n <p\r\n class=\"m-0 mt-1 text-[12px] leading-5 text-(--p-text-muted-color)\"\r\n >\r\n Debug payloads appear after a trigger run or after loading an\r\n execution from the executions page.\r\n </p>\r\n </div>\r\n <div class=\"flex flex-wrap justify-center gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"media.play\"\r\n label=\"Run trigger\"\r\n (onClick)=\"onTabChange('testRun')\"\r\n />\r\n <mt-button\r\n size=\"small\"\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n icon=\"general.link-external-02\"\r\n label=\"Open executions\"\r\n (onClick)=\"openRuns()\"\r\n />\r\n </div>\r\n </div>\r\n } @else {\r\n <pre\r\n class=\"fp-scroll m-0 min-h-0 flex-1 overflow-auto bg-(--p-surface-100) p-3.5 font-mono text-[11px] leading-[1.65] text-(--p-text-color)\"\r\n >{{ rawJson() }}</pre\r\n >\r\n }\r\n </section>\r\n }\r\n }\r\n </div>\r\n}\r\n" }]
|
|
28365
28368
|
}], ctorParameters: () => [], propDecorators: { focus: [{
|
|
28366
28369
|
type: Output
|
|
28367
28370
|
}], contextLabelCellTpl: [{ type: i0.ViewChild, args: ['contextLabelCellTpl', { isSignal: true }] }], contextExpressionCellTpl: [{ type: i0.ViewChild, args: ['contextExpressionCellTpl', { isSignal: true }] }], contextSampleCellTpl: [{ type: i0.ViewChild, args: ['contextSampleCellTpl', { isSignal: true }] }], contextSourceCellTpl: [{ type: i0.ViewChild, args: ['contextSourceCellTpl', { isSignal: true }] }] } });
|
|
@@ -29384,7 +29387,7 @@ class FlowNodeComponent {
|
|
|
29384
29387
|
};
|
|
29385
29388
|
}
|
|
29386
29389
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FlowNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
29387
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: FlowNodeComponent, isStandalone: true, selector: "fp-flow-node", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, connectedOutputKeys: { classPropertyName: "connectedOutputKeys", publicName: "connectedOutputKeys", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { positionChange: "positionChange", nodeClick: "nodeClick", quickAdd: "quickAdd", portPlusClick: "portPlusClick", duplicate: "duplicate", remove: "remove", testStep: "testStep", openChild: "openChild", openDetails: "openDetails" }, ngImport: i0, template: "<!-- The square card (n8n structure). The Foblex `fNode` directive is on the\r\n HOST element (`<fp-flow-node>`, see flow-canvas.html), NOT here \u2014 that\r\n keeps the node a direct child of Foblex's nodes container (required by\r\n the select/move layer-raise). This inner div is just the visual card;\r\n the label, hover toolbar and add-button are ABSOLUTE children so they\r\n float outside the card without inflating the node's hit box. -->\r\n<div\r\n (dblclick)=\"!isPendingCreate() && openDetails.emit({ stepId: step().id })\"\r\n class=\"fp-canvas-node-card group relative grid cursor-grab select-none place-items-center rounded-md border border-[rgb(var(--fp-border-strong))] bg-(--p-content-background) shadow-[0_1px_2px_rgba(15,23,42,0.07)] transition-[box-shadow,border-color] duration-200 hover:border-[color-mix(in_srgb,var(--p-primary-color)_45%,rgb(var(--fp-border-strong)))] hover:shadow-md\"\r\n [class.is-selected]=\"node().isSelected\"\r\n [ngClass]=\"{\r\n '!border-(--p-primary-color)': node().isSelected,\r\n '!cursor-progress !border-[color-mix(in_srgb,var(--p-primary-color)_55%,rgb(var(--fp-border-strong)))] !shadow-[0_0_0_3px_color-mix(in_srgb,var(--p-primary-color)_12%,transparent)]':\r\n isPendingCreate(),\r\n '!border-[rgb(var(--fp-trigger))]': node().runtimeState === 'current',\r\n '!border-[rgb(var(--fp-commit))]': node().runtimeState === 'completed',\r\n '!border-[rgb(var(--fp-error))]': node().runtimeState === 'failed',\r\n }\"\r\n [attr.data-step-type]=\"step().type\"\r\n [attr.aria-label]=\"title()\"\r\n [attr.aria-busy]=\"\r\n isPendingCreate() || runtimeTone() === 'running' ? 'true' : null\r\n \"\r\n [title]=\"title()\"\r\n [style.width.px]=\"cardWidth\"\r\n [style.height.px]=\"cardHeight()\"\r\n>\r\n <!-- Hover / selected action toolbar \u2014 bare floating icons (n8n concept): no\r\n card/box around them, just clean spaced actions above the node. The\r\n `before:` strip bridges the gap down to the card so moving the cursor up\r\n to the actions doesn't leave the hover area (they were vanishing mid-reach). -->\r\n @if (canShowActions()) {\r\n <div\r\n class=\"pointer-events-none absolute -top-[44px] left-1/2 z-20 flex -translate-x-1/2 items-center gap-1.5 opacity-0 transition-opacity duration-150 before:absolute before:inset-x-0 before:top-full before:h-3 before:content-[''] group-hover:pointer-events-auto group-hover:opacity-100 group-[.is-selected]:pointer-events-auto group-[.is-selected]:opacity-100\"\r\n >\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"'flowplus.node.details' | transloco\"\r\n (onClick)=\"openDetails.emit({ stepId: step().id })\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-04\"\r\n [tooltip]=\"'flowplus.node.duplicate' | transloco\"\r\n (onClick)=\"duplicate.emit({ stepId: step().id })\"\r\n />\r\n @if (childWorkflowId(); as wfId) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.link-external-02\"\r\n [tooltip]=\"'flowplus.node.openChild' | transloco\"\r\n (onClick)=\"openChild.emit({ targetWorkflowId: wfId })\"\r\n />\r\n }\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"'flowplus.node.delete' | transloco\"\r\n (onClick)=\"remove.emit({ stepId: step().id })\"\r\n />\r\n </div>\r\n }\r\n\r\n <!-- Centered type icon -->\r\n <fp-automation-node-icon\r\n class=\"fp-canvas-node-icon relative z-[1]\"\r\n [type]=\"step().type\"\r\n [itemKey]=\"step().key\"\r\n [displayName]=\"step().name\"\r\n [metadata]=\"step().metadata\"\r\n [style.color]=\"color()\"\r\n />\r\n\r\n @if (!isPendingCreate() && runtimeTone() === \"running\") {\r\n <span\r\n class=\"pointer-events-none absolute inset-[3px] rounded-[5px] border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_5%,var(--p-content-background))] shadow-[inset_0_0_18px_color-mix(in_srgb,var(--p-primary-color)_10%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"pointer-events-none absolute inset-x-2 top-2 h-1 overflow-hidden rounded-full bg-[color-mix(in_srgb,var(--p-primary-color)_14%,transparent)]\"\r\n aria-hidden=\"true\"\r\n >\r\n <span\r\n class=\"block h-full w-7 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_920ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n } @else if (isPendingCreate()) {\r\n <span\r\n class=\"pointer-events-none absolute inset-x-2 top-2 h-1 overflow-hidden rounded-full bg-[color-mix(in_srgb,var(--p-primary-color)_14%,transparent)]\"\r\n >\r\n <span\r\n class=\"block h-full w-7 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_900ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n <span\r\n class=\"pointer-events-none absolute top-2 right-2 inline-flex h-[18px] w-[18px] items-center justify-center rounded-full bg-(--p-content-background) text-(--p-primary-color) shadow-[0_1px_2px_rgba(15,23,42,0.08)]\"\r\n [title]=\"pendingLabel()\"\r\n >\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-[var(--p-primary-color)] animate-[fp-loading-spin_820ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n }\r\n\r\n <!-- Validation indicator \u2014 small premium rounded warning triangle + \"!\",\r\n sitting INSIDE the card at the bottom-right (n8n style). Color comes\r\n from `currentColor` so it stays crisp at any zoom. -->\r\n @if (badgeCount() > 0) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 top-1.5 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.color]=\"\r\n issueSeverity() === 'error'\r\n ? 'rgb(var(--fp-error))'\r\n : issueSeverity() === 'warning'\r\n ? 'rgb(var(--fp-warning))'\r\n : 'var(--p-primary-color)'\r\n \"\r\n tabindex=\"0\"\r\n role=\"img\"\r\n [attr.aria-label]=\"issueTooltip()\"\r\n [mtTooltip]=\"issueTooltip()\"\r\n [tooltipStyleClass]=\"issueTooltipClass()\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"\r\n $event.stopPropagation(); openDetails.emit({ stepId: step().id })\r\n \"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n }\r\n\r\n <!-- Input lives on the node host (fNodeInput above) \u2014 the connection's\r\n arrow marker is the visual cue, matching the Foblex call-center. -->\r\n\r\n <!-- Output ports \u2014 single dot, or a labeled stack for multi-output -->\r\n @if (!isPendingCreate() && runtimeTone(); as tone) {\r\n @if (tone === \"failed\") {\n <span\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-error))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (tone === \"completed\") {\n <span\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-commit))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"general.check\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else {\n <span\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center rounded-full border bg-(--p-content-background) shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n [ngClass]=\"\n tone === 'running'\n ? 'shadow-[0_0_0_4px_color-mix(in_srgb,var(--p-primary-color)_12%,transparent)]'\n : ''\n \"\r\n [style.borderColor]=\"runtimeColor()\"\r\n [style.color]=\"runtimeColor()\"\r\n tabindex=\"0\"\r\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n @if (tone === \"running\") {\n <span\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-(--p-primary-color) animate-[fp-loading-spin_760ms_linear_infinite]\"\n aria-hidden=\"true\"\n ></span>\n } @else {\n <mt-icon [icon]=\"runtimeIcon()\" class=\"[&_svg]:size-3.5\" />\n }\n </span>\n }\n }\r\n\r\n @if (!isPendingCreate() && node().outputs.length > 1) {\r\n <div\r\n class=\"pointer-events-none absolute top-0 bottom-0 left-[calc(100%-8px)] flex flex-col justify-center rtl:right-[calc(100%-8px)] rtl:left-auto\"\r\n [style.row-gap.px]=\"multiOutputGap\"\r\n >\r\n @for (\r\n port of node().outputs;\r\n track port.id;\r\n let outputIndex = $index;\r\n let outputCount = $count\r\n ) {\r\n <div\r\n class=\"pointer-events-auto relative flex min-w-[168px] items-center gap-2 pe-8 rtl:flex-row-reverse rtl:pe-0 rtl:ps-8\"\r\n >\r\n @if (!outputHasConnection(port.key)) {\r\n <span\r\n class=\"fp-node-output-rail pointer-events-none absolute left-3 right-6 top-1/2 h-0.5 -translate-y-1/2 bg-[rgb(var(--fp-connector))] rtl:left-6 rtl:right-3\"\r\n ></span>\r\n }\r\n <div\r\n #outputRef\r\n fNodeOutput\r\n [fOutputId]=\"port.id\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"port.allowMultiple\"\r\n [fOutputDisabled]=\"!!port.isTerminal\"\r\n class=\"fp-connect-output relative z-[2] flex h-4 w-4 shrink-0 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out hover:ring-4 hover:ring-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)]\"\r\n [class.is-terminal]=\"port.isTerminal\"\r\n [ngClass]=\"\r\n port.isTerminal\r\n ? 'cursor-not-allowed opacity-50 !border-[rgb(var(--fp-text-muted))] !bg-[rgb(var(--fp-text-muted))]'\r\n : ''\r\n \"\r\n [attr.aria-label]=\"resolveLabel(port.label)\"\r\n [title]=\"\r\n port.isTerminal\r\n ? 'Terminal action \u2014 cannot route.'\r\n : resolveLabel(port.label)\r\n \"\r\n ></div>\r\n @if (!outputHasConnection(port.key)) {\r\n <span\r\n class=\"relative z-[1] whitespace-nowrap rounded-full bg-(--p-content-background) px-1 text-[10px] font-medium text-(--p-text-muted-color)\"\r\n [class.line-through]=\"port.isTerminal\"\r\n >{{ resolveLabel(port.label) }}</span\r\n >\r\n }\r\n @if (\r\n !port.isTerminal &&\r\n canShowActions() &&\r\n !outputHasConnection(port.key)\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 right-0 z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md rtl:right-auto rtl:left-0\"\r\n [attr.aria-label]=\"'flowplus.node.addNext' | transloco\"\r\n [title]=\"'flowplus.node.addNext' | transloco\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, outputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, outputRef)\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n portPlusClick.emit({\r\n stepId: step().id,\r\n portKey: port.key,\r\n outputIndex,\r\n outputCount,\r\n })\r\n \"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else if (!isPendingCreate() && node().outputs.length === 1) {\r\n @for (port of node().outputs; track port.id) {\r\n <div\r\n #outputRef\r\n fNodeOutput\r\n [fOutputId]=\"port.id\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"port.allowMultiple\"\r\n [fOutputDisabled]=\"!!port.isTerminal\"\r\n class=\"fp-connect-output absolute top-1/2 -right-2 z-[2] flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out hover:ring-4 hover:ring-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)]\"\r\n [class.is-terminal]=\"port.isTerminal\"\r\n [ngClass]=\"\r\n port.isTerminal\r\n ? 'cursor-not-allowed opacity-50 !border-[rgb(var(--fp-text-muted))] !bg-[rgb(var(--fp-text-muted))]'\r\n : 'rtl:-left-2 rtl:right-auto'\r\n \"\r\n [attr.aria-label]=\"resolveLabel(port.label)\"\r\n [title]=\"\r\n port.isTerminal\r\n ? 'Terminal action \u2014 cannot route.'\r\n : resolveLabel(port.label)\r\n \"\r\n ></div>\r\n <!-- \"+\" quick-add only while the output has NO downstream step yet. Once\r\n connected we drop the \"+\"/line entirely and let the output dot be\r\n the connector (it shows a \"+\" cursor to invite another link). -->\r\n @if (\r\n !port.isTerminal && canShowActions() && !outputHasConnection(port.key)\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 -right-[72px] z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out before:absolute before:top-1/2 before:right-full before:h-0.5 before:w-12 before:-translate-y-1/2 before:bg-[rgb(var(--fp-connector))] before:transition-colors before:duration-200 hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md hover:before:bg-(--p-primary-color) rtl:-left-[72px] rtl:right-auto rtl:before:right-auto rtl:before:left-full\"\r\n [attr.aria-label]=\"'flowplus.node.addNext' | transloco\"\r\n [title]=\"'flowplus.node.addNext' | transloco\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, outputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, outputRef)\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n portPlusClick.emit({\r\n stepId: step().id,\r\n portKey: port.key,\r\n outputIndex: 0,\r\n outputCount: 1,\r\n })\r\n \"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n }\r\n }\r\n\r\n <!-- Label below the node (absolute \u2014 does not inflate the node box) -->\r\n <div\r\n class=\"absolute left-1/2 top-[calc(100%+8px)] w-[128px] -translate-x-1/2 text-center leading-tight\"\r\n >\r\n <div\r\n class=\"line-clamp-2 text-[12px] font-semibold text-(--p-text-color)\"\r\n [title]=\"title()\"\r\n >\r\n {{ title() }}\r\n </div>\r\n @if (runtimeTone(); as tone) {\n <div\n class=\"mt-0.5 flex max-w-full items-center justify-center gap-1 text-[10.5px] font-semibold leading-tight\"\n [style.color]=\"runtimeColor()\"\n [title]=\"loopProgressLabel() ?? runtimeStatusLabel()\"\n >\n @if (tone === \"running\") {\n <span\n class=\"block h-2 w-2 shrink-0 rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-(--p-primary-color) animate-[fp-loading-spin_760ms_linear_infinite]\"\n aria-hidden=\"true\"\n ></span>\n } @else {\n <mt-icon [icon]=\"runtimeIcon()\" class=\"shrink-0 [&_svg]:size-3\" />\n }\n <span class=\"truncate\">{{\n loopProgressLabel() ?? runtimeStatusLabel()\n }}</span>\n </div>\n } @else if (isPendingCreate()) {\n <div\r\n class=\"mt-0.5 truncate text-[10.5px] font-semibold text-(--p-primary-color)\"\r\n >\r\n {{ pendingLabel() }}\r\n </div>\r\n } @else if (step().isInitial) {\r\n <!-- Soft, premium \"initial step\" cue \u2014 a gentle primary-tinted pill so\r\n the workflow's entry point reads at a glance without shouting. -->\r\n <span\r\n class=\"mt-1 inline-flex items-center gap-1 rounded-full bg-(--p-primary-color)/10 px-2 py-0.5 text-[10px] font-semibold text-(--p-primary-color)\"\r\n >\r\n <mt-icon icon=\"map.flag-01\" class=\"[&_svg]:size-3\" />\r\n {{ \"flowplus.node.initial\" | transloco }}\r\n </span>\r\n } @else if (subtitle()) {\r\n <div\r\n class=\"mt-0.5 truncate font-mono text-[10px] font-normal text-(--p-text-muted-color)\"\r\n >\r\n {{ subtitle() }}\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FFlowModule }, { kind: "directive", type: i1$2.FNodeOutputDirective, selector: "[fNodeOutput]", inputs: ["fOutputId", "fOutputMultiple", "fOutputDisabled", "fOutputConnectableSide", "isSelfConnectable", "fCanBeConnectedInputs"], exportAs: ["fNodeOutput"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
|
|
29390
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: FlowNodeComponent, isStandalone: true, selector: "fp-flow-node", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, connectedOutputKeys: { classPropertyName: "connectedOutputKeys", publicName: "connectedOutputKeys", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { positionChange: "positionChange", nodeClick: "nodeClick", quickAdd: "quickAdd", portPlusClick: "portPlusClick", duplicate: "duplicate", remove: "remove", testStep: "testStep", openChild: "openChild", openDetails: "openDetails" }, ngImport: i0, template: "<!-- The square card (n8n structure). The Foblex `fNode` directive is on the\r\n HOST element (`<fp-flow-node>`, see flow-canvas.html), NOT here \u2014 that\r\n keeps the node a direct child of Foblex's nodes container (required by\r\n the select/move layer-raise). This inner div is just the visual card;\r\n the label, hover toolbar and add-button are ABSOLUTE children so they\r\n float outside the card without inflating the node's hit box. -->\r\n<div\r\n (dblclick)=\"!isPendingCreate() && openDetails.emit({ stepId: step().id })\"\r\n class=\"fp-canvas-node-card group relative grid cursor-grab select-none place-items-center rounded-md border border-[rgb(var(--fp-border-strong))] bg-(--p-content-background) shadow-[0_1px_2px_rgba(15,23,42,0.07)] transition-[box-shadow,border-color] duration-200 hover:border-[color-mix(in_srgb,var(--p-primary-color)_45%,rgb(var(--fp-border-strong)))] hover:shadow-md\"\r\n [class.is-selected]=\"node().isSelected\"\r\n [ngClass]=\"{\r\n '!border-(--p-primary-color)': node().isSelected,\r\n '!cursor-progress !border-[color-mix(in_srgb,var(--p-primary-color)_55%,rgb(var(--fp-border-strong)))] !shadow-[0_0_0_3px_color-mix(in_srgb,var(--p-primary-color)_12%,transparent)]':\r\n isPendingCreate(),\r\n '!border-[rgb(var(--fp-trigger))]': node().runtimeState === 'current',\r\n '!border-[rgb(var(--fp-commit))]': node().runtimeState === 'completed',\r\n '!border-[rgb(var(--fp-error))]': node().runtimeState === 'failed',\r\n }\"\r\n [attr.data-step-type]=\"step().type\"\r\n [attr.aria-label]=\"title()\"\r\n [attr.aria-busy]=\"\r\n isPendingCreate() || runtimeTone() === 'running' ? 'true' : null\r\n \"\r\n [title]=\"title()\"\r\n [style.width.px]=\"cardWidth\"\r\n [style.height.px]=\"cardHeight()\"\r\n>\r\n <!-- Hover / selected action toolbar \u2014 bare floating icons (n8n concept): no\r\n card/box around them, just clean spaced actions above the node. The\r\n `before:` strip bridges the gap down to the card so moving the cursor up\r\n to the actions doesn't leave the hover area (they were vanishing mid-reach). -->\r\n @if (canShowActions()) {\r\n <div\r\n class=\"pointer-events-none absolute -top-[44px] left-1/2 z-20 flex -translate-x-1/2 items-center gap-1.5 opacity-0 transition-opacity duration-150 before:absolute before:inset-x-0 before:top-full before:h-3 before:content-[''] group-hover:pointer-events-auto group-hover:opacity-100 group-[.is-selected]:pointer-events-auto group-[.is-selected]:opacity-100\"\r\n >\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"'flowplus.node.details' | transloco\"\r\n (onClick)=\"openDetails.emit({ stepId: step().id })\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-04\"\r\n [tooltip]=\"'flowplus.node.duplicate' | transloco\"\r\n (onClick)=\"duplicate.emit({ stepId: step().id })\"\r\n />\r\n @if (childWorkflowId(); as wfId) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.link-external-02\"\r\n [tooltip]=\"'flowplus.node.openChild' | transloco\"\r\n (onClick)=\"openChild.emit({ targetWorkflowId: wfId })\"\r\n />\r\n }\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"'flowplus.node.delete' | transloco\"\r\n (onClick)=\"remove.emit({ stepId: step().id })\"\r\n />\r\n </div>\r\n }\r\n\r\n <!-- Centered type icon -->\r\n <fp-automation-node-icon\r\n class=\"fp-canvas-node-icon relative z-[1]\"\r\n [type]=\"step().type\"\r\n [itemKey]=\"step().key\"\r\n [displayName]=\"step().name\"\r\n [metadata]=\"step().metadata\"\r\n [style.color]=\"color()\"\r\n />\r\n\r\n @if (!isPendingCreate() && runtimeTone() === \"running\") {\r\n <span\r\n class=\"pointer-events-none absolute inset-[3px] rounded-[5px] border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_5%,var(--p-content-background))] shadow-[inset_0_0_18px_color-mix(in_srgb,var(--p-primary-color)_10%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"pointer-events-none absolute inset-x-2 top-2 h-1 overflow-hidden rounded-full bg-[color-mix(in_srgb,var(--p-primary-color)_14%,transparent)]\"\r\n aria-hidden=\"true\"\r\n >\r\n <span\r\n class=\"block h-full w-7 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_920ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n } @else if (isPendingCreate()) {\r\n <span\r\n class=\"pointer-events-none absolute inset-x-2 top-2 h-1 overflow-hidden rounded-full bg-[color-mix(in_srgb,var(--p-primary-color)_14%,transparent)]\"\r\n >\r\n <span\r\n class=\"block h-full w-7 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_900ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n <span\r\n class=\"pointer-events-none absolute top-2 right-2 inline-flex h-[18px] w-[18px] items-center justify-center rounded-full bg-(--p-content-background) text-(--p-primary-color) shadow-[0_1px_2px_rgba(15,23,42,0.08)]\"\r\n [title]=\"pendingLabel()\"\r\n >\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-[var(--p-primary-color)] animate-[fp-loading-spin_820ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n }\r\n\r\n <!-- Validation indicator \u2014 small premium rounded warning triangle + \"!\",\r\n sitting INSIDE the card at the bottom-right (n8n style). Color comes\r\n from `currentColor` so it stays crisp at any zoom. -->\r\n @if (badgeCount() > 0) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 top-1.5 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.color]=\"\r\n issueSeverity() === 'error'\r\n ? 'rgb(var(--fp-error))'\r\n : issueSeverity() === 'warning'\r\n ? 'rgb(var(--fp-warning))'\r\n : 'var(--p-primary-color)'\r\n \"\r\n tabindex=\"0\"\r\n role=\"img\"\r\n [attr.aria-label]=\"issueTooltip()\"\r\n [mtTooltip]=\"issueTooltip()\"\r\n [tooltipStyleClass]=\"issueTooltipClass()\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"\r\n $event.stopPropagation(); openDetails.emit({ stepId: step().id })\r\n \"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n }\r\n\r\n <!-- Input lives on the node host (fNodeInput above) \u2014 the connection's\r\n arrow marker is the visual cue, matching the Foblex call-center. -->\r\n\r\n <!-- Output ports \u2014 single dot, or a labeled stack for multi-output -->\r\n @if (!isPendingCreate() && runtimeTone(); as tone) {\r\n @if (tone === \"failed\") {\r\n <span\r\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-error))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (tone === \"completed\") {\r\n <span\r\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-commit))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"general.check\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else {\r\n <span\r\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center rounded-full border bg-(--p-content-background) shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n [ngClass]=\"\r\n tone === 'running'\r\n ? 'shadow-[0_0_0_4px_color-mix(in_srgb,var(--p-primary-color)_12%,transparent)]'\r\n : ''\r\n \"\r\n [style.borderColor]=\"runtimeColor()\"\r\n [style.color]=\"runtimeColor()\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n @if (tone === \"running\") {\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-(--p-primary-color) animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n } @else {\r\n <mt-icon [icon]=\"runtimeIcon()\" class=\"[&_svg]:size-3.5\" />\r\n }\r\n </span>\r\n }\r\n }\r\n\r\n @if (!isPendingCreate() && node().outputs.length > 1) {\r\n <div\r\n class=\"pointer-events-none absolute top-0 bottom-0 left-[calc(100%-8px)] flex flex-col justify-center rtl:right-[calc(100%-8px)] rtl:left-auto\"\r\n [style.row-gap.px]=\"multiOutputGap\"\r\n >\r\n @for (\r\n port of node().outputs;\r\n track port.id;\r\n let outputIndex = $index;\r\n let outputCount = $count\r\n ) {\r\n <div\r\n class=\"pointer-events-auto relative flex min-w-[168px] items-center gap-2 pe-8 rtl:flex-row-reverse rtl:pe-0 rtl:ps-8\"\r\n >\r\n @if (!outputHasConnection(port.key)) {\r\n <span\r\n class=\"fp-node-output-rail pointer-events-none absolute left-3 right-6 top-1/2 h-0.5 -translate-y-1/2 bg-[rgb(var(--fp-connector))] rtl:left-6 rtl:right-3\"\r\n ></span>\r\n }\r\n <div\r\n #outputRef\r\n fNodeOutput\r\n [fOutputId]=\"port.id\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"port.allowMultiple\"\r\n [fOutputDisabled]=\"!!port.isTerminal\"\r\n class=\"fp-connect-output relative z-[2] flex h-4 w-4 shrink-0 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out hover:ring-4 hover:ring-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)]\"\r\n [class.is-terminal]=\"port.isTerminal\"\r\n [ngClass]=\"\r\n port.isTerminal\r\n ? 'cursor-not-allowed opacity-50 !border-[rgb(var(--fp-text-muted))] !bg-[rgb(var(--fp-text-muted))]'\r\n : ''\r\n \"\r\n [attr.aria-label]=\"resolveLabel(port.label)\"\r\n [title]=\"\r\n port.isTerminal\r\n ? 'Terminal action \u2014 cannot route.'\r\n : resolveLabel(port.label)\r\n \"\r\n ></div>\r\n @if (!outputHasConnection(port.key)) {\r\n <span\r\n class=\"relative z-[1] whitespace-nowrap rounded-full bg-(--p-content-background) px-1 text-[10px] font-medium text-(--p-text-muted-color)\"\r\n [class.line-through]=\"port.isTerminal\"\r\n >{{ resolveLabel(port.label) }}</span\r\n >\r\n }\r\n @if (\r\n !port.isTerminal &&\r\n canShowActions() &&\r\n !outputHasConnection(port.key)\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 right-0 z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md rtl:right-auto rtl:left-0\"\r\n [attr.aria-label]=\"'flowplus.node.addNext' | transloco\"\r\n [title]=\"'flowplus.node.addNext' | transloco\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, outputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, outputRef)\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n portPlusClick.emit({\r\n stepId: step().id,\r\n portKey: port.key,\r\n outputIndex,\r\n outputCount,\r\n })\r\n \"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else if (!isPendingCreate() && node().outputs.length === 1) {\r\n @for (port of node().outputs; track port.id) {\r\n <div\r\n #outputRef\r\n fNodeOutput\r\n [fOutputId]=\"port.id\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"port.allowMultiple\"\r\n [fOutputDisabled]=\"!!port.isTerminal\"\r\n class=\"fp-connect-output absolute top-1/2 -right-2 z-[2] flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out hover:ring-4 hover:ring-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)]\"\r\n [class.is-terminal]=\"port.isTerminal\"\r\n [ngClass]=\"\r\n port.isTerminal\r\n ? 'cursor-not-allowed opacity-50 !border-[rgb(var(--fp-text-muted))] !bg-[rgb(var(--fp-text-muted))]'\r\n : 'rtl:-left-2 rtl:right-auto'\r\n \"\r\n [attr.aria-label]=\"resolveLabel(port.label)\"\r\n [title]=\"\r\n port.isTerminal\r\n ? 'Terminal action \u2014 cannot route.'\r\n : resolveLabel(port.label)\r\n \"\r\n ></div>\r\n <!-- \"+\" quick-add only while the output has NO downstream step yet. Once\r\n connected we drop the \"+\"/line entirely and let the output dot be\r\n the connector (it shows a \"+\" cursor to invite another link). -->\r\n @if (\r\n !port.isTerminal && canShowActions() && !outputHasConnection(port.key)\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 -right-[72px] z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out before:absolute before:top-1/2 before:right-full before:h-0.5 before:w-12 before:-translate-y-1/2 before:bg-[rgb(var(--fp-connector))] before:transition-colors before:duration-200 hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md hover:before:bg-(--p-primary-color) rtl:-left-[72px] rtl:right-auto rtl:before:right-auto rtl:before:left-full\"\r\n [attr.aria-label]=\"'flowplus.node.addNext' | transloco\"\r\n [title]=\"'flowplus.node.addNext' | transloco\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, outputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, outputRef)\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n portPlusClick.emit({\r\n stepId: step().id,\r\n portKey: port.key,\r\n outputIndex: 0,\r\n outputCount: 1,\r\n })\r\n \"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n }\r\n }\r\n\r\n <!-- Label below the node (absolute \u2014 does not inflate the node box) -->\r\n <div\r\n class=\"absolute left-1/2 top-[calc(100%+8px)] w-[128px] -translate-x-1/2 text-center leading-tight\"\r\n >\r\n <div\r\n class=\"line-clamp-2 text-[12px] font-semibold text-(--p-text-color)\"\r\n [title]=\"title()\"\r\n >\r\n {{ title() }}\r\n </div>\r\n @if (runtimeTone(); as tone) {\r\n <div\r\n class=\"mt-0.5 flex max-w-full items-center justify-center gap-1 text-[10.5px] font-semibold leading-tight\"\r\n [style.color]=\"runtimeColor()\"\r\n [title]=\"loopProgressLabel() ?? runtimeStatusLabel()\"\r\n >\r\n @if (tone === \"running\") {\r\n <span\r\n class=\"block h-2 w-2 shrink-0 rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-(--p-primary-color) animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n } @else {\r\n <mt-icon [icon]=\"runtimeIcon()\" class=\"shrink-0 [&_svg]:size-3\" />\r\n }\r\n <span class=\"truncate\">{{\r\n loopProgressLabel() ?? runtimeStatusLabel()\r\n }}</span>\r\n </div>\r\n } @else if (isPendingCreate()) {\r\n <div\r\n class=\"mt-0.5 truncate text-[10.5px] font-semibold text-(--p-primary-color)\"\r\n >\r\n {{ pendingLabel() }}\r\n </div>\r\n } @else if (step().isInitial) {\r\n <!-- Soft, premium \"initial step\" cue \u2014 a gentle primary-tinted pill so\r\n the workflow's entry point reads at a glance without shouting. -->\r\n <span\r\n class=\"mt-1 inline-flex items-center gap-1 rounded-full bg-(--p-primary-color)/10 px-2 py-0.5 text-[10px] font-semibold text-(--p-primary-color)\"\r\n >\r\n <mt-icon icon=\"map.flag-01\" class=\"[&_svg]:size-3\" />\r\n {{ \"flowplus.node.initial\" | transloco }}\r\n </span>\r\n } @else if (subtitle()) {\r\n <div\r\n class=\"mt-0.5 truncate font-mono text-[10px] font-normal text-(--p-text-muted-color)\"\r\n >\r\n {{ subtitle() }}\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FFlowModule }, { kind: "directive", type: i1$2.FNodeOutputDirective, selector: "[fNodeOutput]", inputs: ["fOutputId", "fOutputMultiple", "fOutputDisabled", "fOutputConnectableSide", "isSelfConnectable", "fCanBeConnectedInputs"], exportAs: ["fNodeOutput"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
|
|
29388
29391
|
}
|
|
29389
29392
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FlowNodeComponent, decorators: [{
|
|
29390
29393
|
type: Component,
|
|
@@ -29396,7 +29399,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
29396
29399
|
Button,
|
|
29397
29400
|
Tooltip,
|
|
29398
29401
|
Icon,
|
|
29399
|
-
], template: "<!-- The square card (n8n structure). The Foblex `fNode` directive is on the\r\n HOST element (`<fp-flow-node>`, see flow-canvas.html), NOT here \u2014 that\r\n keeps the node a direct child of Foblex's nodes container (required by\r\n the select/move layer-raise). This inner div is just the visual card;\r\n the label, hover toolbar and add-button are ABSOLUTE children so they\r\n float outside the card without inflating the node's hit box. -->\r\n<div\r\n (dblclick)=\"!isPendingCreate() && openDetails.emit({ stepId: step().id })\"\r\n class=\"fp-canvas-node-card group relative grid cursor-grab select-none place-items-center rounded-md border border-[rgb(var(--fp-border-strong))] bg-(--p-content-background) shadow-[0_1px_2px_rgba(15,23,42,0.07)] transition-[box-shadow,border-color] duration-200 hover:border-[color-mix(in_srgb,var(--p-primary-color)_45%,rgb(var(--fp-border-strong)))] hover:shadow-md\"\r\n [class.is-selected]=\"node().isSelected\"\r\n [ngClass]=\"{\r\n '!border-(--p-primary-color)': node().isSelected,\r\n '!cursor-progress !border-[color-mix(in_srgb,var(--p-primary-color)_55%,rgb(var(--fp-border-strong)))] !shadow-[0_0_0_3px_color-mix(in_srgb,var(--p-primary-color)_12%,transparent)]':\r\n isPendingCreate(),\r\n '!border-[rgb(var(--fp-trigger))]': node().runtimeState === 'current',\r\n '!border-[rgb(var(--fp-commit))]': node().runtimeState === 'completed',\r\n '!border-[rgb(var(--fp-error))]': node().runtimeState === 'failed',\r\n }\"\r\n [attr.data-step-type]=\"step().type\"\r\n [attr.aria-label]=\"title()\"\r\n [attr.aria-busy]=\"\r\n isPendingCreate() || runtimeTone() === 'running' ? 'true' : null\r\n \"\r\n [title]=\"title()\"\r\n [style.width.px]=\"cardWidth\"\r\n [style.height.px]=\"cardHeight()\"\r\n>\r\n <!-- Hover / selected action toolbar \u2014 bare floating icons (n8n concept): no\r\n card/box around them, just clean spaced actions above the node. The\r\n `before:` strip bridges the gap down to the card so moving the cursor up\r\n to the actions doesn't leave the hover area (they were vanishing mid-reach). -->\r\n @if (canShowActions()) {\r\n <div\r\n class=\"pointer-events-none absolute -top-[44px] left-1/2 z-20 flex -translate-x-1/2 items-center gap-1.5 opacity-0 transition-opacity duration-150 before:absolute before:inset-x-0 before:top-full before:h-3 before:content-[''] group-hover:pointer-events-auto group-hover:opacity-100 group-[.is-selected]:pointer-events-auto group-[.is-selected]:opacity-100\"\r\n >\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"'flowplus.node.details' | transloco\"\r\n (onClick)=\"openDetails.emit({ stepId: step().id })\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-04\"\r\n [tooltip]=\"'flowplus.node.duplicate' | transloco\"\r\n (onClick)=\"duplicate.emit({ stepId: step().id })\"\r\n />\r\n @if (childWorkflowId(); as wfId) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.link-external-02\"\r\n [tooltip]=\"'flowplus.node.openChild' | transloco\"\r\n (onClick)=\"openChild.emit({ targetWorkflowId: wfId })\"\r\n />\r\n }\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"'flowplus.node.delete' | transloco\"\r\n (onClick)=\"remove.emit({ stepId: step().id })\"\r\n />\r\n </div>\r\n }\r\n\r\n <!-- Centered type icon -->\r\n <fp-automation-node-icon\r\n class=\"fp-canvas-node-icon relative z-[1]\"\r\n [type]=\"step().type\"\r\n [itemKey]=\"step().key\"\r\n [displayName]=\"step().name\"\r\n [metadata]=\"step().metadata\"\r\n [style.color]=\"color()\"\r\n />\r\n\r\n @if (!isPendingCreate() && runtimeTone() === \"running\") {\r\n <span\r\n class=\"pointer-events-none absolute inset-[3px] rounded-[5px] border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_5%,var(--p-content-background))] shadow-[inset_0_0_18px_color-mix(in_srgb,var(--p-primary-color)_10%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"pointer-events-none absolute inset-x-2 top-2 h-1 overflow-hidden rounded-full bg-[color-mix(in_srgb,var(--p-primary-color)_14%,transparent)]\"\r\n aria-hidden=\"true\"\r\n >\r\n <span\r\n class=\"block h-full w-7 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_920ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n } @else if (isPendingCreate()) {\r\n <span\r\n class=\"pointer-events-none absolute inset-x-2 top-2 h-1 overflow-hidden rounded-full bg-[color-mix(in_srgb,var(--p-primary-color)_14%,transparent)]\"\r\n >\r\n <span\r\n class=\"block h-full w-7 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_900ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n <span\r\n class=\"pointer-events-none absolute top-2 right-2 inline-flex h-[18px] w-[18px] items-center justify-center rounded-full bg-(--p-content-background) text-(--p-primary-color) shadow-[0_1px_2px_rgba(15,23,42,0.08)]\"\r\n [title]=\"pendingLabel()\"\r\n >\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-[var(--p-primary-color)] animate-[fp-loading-spin_820ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n }\r\n\r\n <!-- Validation indicator \u2014 small premium rounded warning triangle + \"!\",\r\n sitting INSIDE the card at the bottom-right (n8n style). Color comes\r\n from `currentColor` so it stays crisp at any zoom. -->\r\n @if (badgeCount() > 0) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 top-1.5 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.color]=\"\r\n issueSeverity() === 'error'\r\n ? 'rgb(var(--fp-error))'\r\n : issueSeverity() === 'warning'\r\n ? 'rgb(var(--fp-warning))'\r\n : 'var(--p-primary-color)'\r\n \"\r\n tabindex=\"0\"\r\n role=\"img\"\r\n [attr.aria-label]=\"issueTooltip()\"\r\n [mtTooltip]=\"issueTooltip()\"\r\n [tooltipStyleClass]=\"issueTooltipClass()\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"\r\n $event.stopPropagation(); openDetails.emit({ stepId: step().id })\r\n \"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n }\r\n\r\n <!-- Input lives on the node host (fNodeInput above) \u2014 the connection's\r\n arrow marker is the visual cue, matching the Foblex call-center. -->\r\n\r\n <!-- Output ports \u2014 single dot, or a labeled stack for multi-output -->\r\n @if (!isPendingCreate() && runtimeTone(); as tone) {\r\n @if (tone === \"failed\") {\n <span\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-error))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (tone === \"completed\") {\n <span\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-commit))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"general.check\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else {\n <span\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center rounded-full border bg-(--p-content-background) shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\n [ngClass]=\"\n tone === 'running'\n ? 'shadow-[0_0_0_4px_color-mix(in_srgb,var(--p-primary-color)_12%,transparent)]'\n : ''\n \"\r\n [style.borderColor]=\"runtimeColor()\"\r\n [style.color]=\"runtimeColor()\"\r\n tabindex=\"0\"\r\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n @if (tone === \"running\") {\n <span\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-(--p-primary-color) animate-[fp-loading-spin_760ms_linear_infinite]\"\n aria-hidden=\"true\"\n ></span>\n } @else {\n <mt-icon [icon]=\"runtimeIcon()\" class=\"[&_svg]:size-3.5\" />\n }\n </span>\n }\n }\r\n\r\n @if (!isPendingCreate() && node().outputs.length > 1) {\r\n <div\r\n class=\"pointer-events-none absolute top-0 bottom-0 left-[calc(100%-8px)] flex flex-col justify-center rtl:right-[calc(100%-8px)] rtl:left-auto\"\r\n [style.row-gap.px]=\"multiOutputGap\"\r\n >\r\n @for (\r\n port of node().outputs;\r\n track port.id;\r\n let outputIndex = $index;\r\n let outputCount = $count\r\n ) {\r\n <div\r\n class=\"pointer-events-auto relative flex min-w-[168px] items-center gap-2 pe-8 rtl:flex-row-reverse rtl:pe-0 rtl:ps-8\"\r\n >\r\n @if (!outputHasConnection(port.key)) {\r\n <span\r\n class=\"fp-node-output-rail pointer-events-none absolute left-3 right-6 top-1/2 h-0.5 -translate-y-1/2 bg-[rgb(var(--fp-connector))] rtl:left-6 rtl:right-3\"\r\n ></span>\r\n }\r\n <div\r\n #outputRef\r\n fNodeOutput\r\n [fOutputId]=\"port.id\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"port.allowMultiple\"\r\n [fOutputDisabled]=\"!!port.isTerminal\"\r\n class=\"fp-connect-output relative z-[2] flex h-4 w-4 shrink-0 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out hover:ring-4 hover:ring-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)]\"\r\n [class.is-terminal]=\"port.isTerminal\"\r\n [ngClass]=\"\r\n port.isTerminal\r\n ? 'cursor-not-allowed opacity-50 !border-[rgb(var(--fp-text-muted))] !bg-[rgb(var(--fp-text-muted))]'\r\n : ''\r\n \"\r\n [attr.aria-label]=\"resolveLabel(port.label)\"\r\n [title]=\"\r\n port.isTerminal\r\n ? 'Terminal action \u2014 cannot route.'\r\n : resolveLabel(port.label)\r\n \"\r\n ></div>\r\n @if (!outputHasConnection(port.key)) {\r\n <span\r\n class=\"relative z-[1] whitespace-nowrap rounded-full bg-(--p-content-background) px-1 text-[10px] font-medium text-(--p-text-muted-color)\"\r\n [class.line-through]=\"port.isTerminal\"\r\n >{{ resolveLabel(port.label) }}</span\r\n >\r\n }\r\n @if (\r\n !port.isTerminal &&\r\n canShowActions() &&\r\n !outputHasConnection(port.key)\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 right-0 z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md rtl:right-auto rtl:left-0\"\r\n [attr.aria-label]=\"'flowplus.node.addNext' | transloco\"\r\n [title]=\"'flowplus.node.addNext' | transloco\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, outputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, outputRef)\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n portPlusClick.emit({\r\n stepId: step().id,\r\n portKey: port.key,\r\n outputIndex,\r\n outputCount,\r\n })\r\n \"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else if (!isPendingCreate() && node().outputs.length === 1) {\r\n @for (port of node().outputs; track port.id) {\r\n <div\r\n #outputRef\r\n fNodeOutput\r\n [fOutputId]=\"port.id\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"port.allowMultiple\"\r\n [fOutputDisabled]=\"!!port.isTerminal\"\r\n class=\"fp-connect-output absolute top-1/2 -right-2 z-[2] flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out hover:ring-4 hover:ring-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)]\"\r\n [class.is-terminal]=\"port.isTerminal\"\r\n [ngClass]=\"\r\n port.isTerminal\r\n ? 'cursor-not-allowed opacity-50 !border-[rgb(var(--fp-text-muted))] !bg-[rgb(var(--fp-text-muted))]'\r\n : 'rtl:-left-2 rtl:right-auto'\r\n \"\r\n [attr.aria-label]=\"resolveLabel(port.label)\"\r\n [title]=\"\r\n port.isTerminal\r\n ? 'Terminal action \u2014 cannot route.'\r\n : resolveLabel(port.label)\r\n \"\r\n ></div>\r\n <!-- \"+\" quick-add only while the output has NO downstream step yet. Once\r\n connected we drop the \"+\"/line entirely and let the output dot be\r\n the connector (it shows a \"+\" cursor to invite another link). -->\r\n @if (\r\n !port.isTerminal && canShowActions() && !outputHasConnection(port.key)\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 -right-[72px] z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out before:absolute before:top-1/2 before:right-full before:h-0.5 before:w-12 before:-translate-y-1/2 before:bg-[rgb(var(--fp-connector))] before:transition-colors before:duration-200 hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md hover:before:bg-(--p-primary-color) rtl:-left-[72px] rtl:right-auto rtl:before:right-auto rtl:before:left-full\"\r\n [attr.aria-label]=\"'flowplus.node.addNext' | transloco\"\r\n [title]=\"'flowplus.node.addNext' | transloco\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, outputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, outputRef)\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n portPlusClick.emit({\r\n stepId: step().id,\r\n portKey: port.key,\r\n outputIndex: 0,\r\n outputCount: 1,\r\n })\r\n \"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n }\r\n }\r\n\r\n <!-- Label below the node (absolute \u2014 does not inflate the node box) -->\r\n <div\r\n class=\"absolute left-1/2 top-[calc(100%+8px)] w-[128px] -translate-x-1/2 text-center leading-tight\"\r\n >\r\n <div\r\n class=\"line-clamp-2 text-[12px] font-semibold text-(--p-text-color)\"\r\n [title]=\"title()\"\r\n >\r\n {{ title() }}\r\n </div>\r\n @if (runtimeTone(); as tone) {\n <div\n class=\"mt-0.5 flex max-w-full items-center justify-center gap-1 text-[10.5px] font-semibold leading-tight\"\n [style.color]=\"runtimeColor()\"\n [title]=\"loopProgressLabel() ?? runtimeStatusLabel()\"\n >\n @if (tone === \"running\") {\n <span\n class=\"block h-2 w-2 shrink-0 rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-(--p-primary-color) animate-[fp-loading-spin_760ms_linear_infinite]\"\n aria-hidden=\"true\"\n ></span>\n } @else {\n <mt-icon [icon]=\"runtimeIcon()\" class=\"shrink-0 [&_svg]:size-3\" />\n }\n <span class=\"truncate\">{{\n loopProgressLabel() ?? runtimeStatusLabel()\n }}</span>\n </div>\n } @else if (isPendingCreate()) {\n <div\r\n class=\"mt-0.5 truncate text-[10.5px] font-semibold text-(--p-primary-color)\"\r\n >\r\n {{ pendingLabel() }}\r\n </div>\r\n } @else if (step().isInitial) {\r\n <!-- Soft, premium \"initial step\" cue \u2014 a gentle primary-tinted pill so\r\n the workflow's entry point reads at a glance without shouting. -->\r\n <span\r\n class=\"mt-1 inline-flex items-center gap-1 rounded-full bg-(--p-primary-color)/10 px-2 py-0.5 text-[10px] font-semibold text-(--p-primary-color)\"\r\n >\r\n <mt-icon icon=\"map.flag-01\" class=\"[&_svg]:size-3\" />\r\n {{ \"flowplus.node.initial\" | transloco }}\r\n </span>\r\n } @else if (subtitle()) {\r\n <div\r\n class=\"mt-0.5 truncate font-mono text-[10px] font-normal text-(--p-text-muted-color)\"\r\n >\r\n {{ subtitle() }}\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n" }]
|
|
29402
|
+
], template: "<!-- The square card (n8n structure). The Foblex `fNode` directive is on the\r\n HOST element (`<fp-flow-node>`, see flow-canvas.html), NOT here \u2014 that\r\n keeps the node a direct child of Foblex's nodes container (required by\r\n the select/move layer-raise). This inner div is just the visual card;\r\n the label, hover toolbar and add-button are ABSOLUTE children so they\r\n float outside the card without inflating the node's hit box. -->\r\n<div\r\n (dblclick)=\"!isPendingCreate() && openDetails.emit({ stepId: step().id })\"\r\n class=\"fp-canvas-node-card group relative grid cursor-grab select-none place-items-center rounded-md border border-[rgb(var(--fp-border-strong))] bg-(--p-content-background) shadow-[0_1px_2px_rgba(15,23,42,0.07)] transition-[box-shadow,border-color] duration-200 hover:border-[color-mix(in_srgb,var(--p-primary-color)_45%,rgb(var(--fp-border-strong)))] hover:shadow-md\"\r\n [class.is-selected]=\"node().isSelected\"\r\n [ngClass]=\"{\r\n '!border-(--p-primary-color)': node().isSelected,\r\n '!cursor-progress !border-[color-mix(in_srgb,var(--p-primary-color)_55%,rgb(var(--fp-border-strong)))] !shadow-[0_0_0_3px_color-mix(in_srgb,var(--p-primary-color)_12%,transparent)]':\r\n isPendingCreate(),\r\n '!border-[rgb(var(--fp-trigger))]': node().runtimeState === 'current',\r\n '!border-[rgb(var(--fp-commit))]': node().runtimeState === 'completed',\r\n '!border-[rgb(var(--fp-error))]': node().runtimeState === 'failed',\r\n }\"\r\n [attr.data-step-type]=\"step().type\"\r\n [attr.aria-label]=\"title()\"\r\n [attr.aria-busy]=\"\r\n isPendingCreate() || runtimeTone() === 'running' ? 'true' : null\r\n \"\r\n [title]=\"title()\"\r\n [style.width.px]=\"cardWidth\"\r\n [style.height.px]=\"cardHeight()\"\r\n>\r\n <!-- Hover / selected action toolbar \u2014 bare floating icons (n8n concept): no\r\n card/box around them, just clean spaced actions above the node. The\r\n `before:` strip bridges the gap down to the card so moving the cursor up\r\n to the actions doesn't leave the hover area (they were vanishing mid-reach). -->\r\n @if (canShowActions()) {\r\n <div\r\n class=\"pointer-events-none absolute -top-[44px] left-1/2 z-20 flex -translate-x-1/2 items-center gap-1.5 opacity-0 transition-opacity duration-150 before:absolute before:inset-x-0 before:top-full before:h-3 before:content-[''] group-hover:pointer-events-auto group-hover:opacity-100 group-[.is-selected]:pointer-events-auto group-[.is-selected]:opacity-100\"\r\n >\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"'flowplus.node.details' | transloco\"\r\n (onClick)=\"openDetails.emit({ stepId: step().id })\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.copy-04\"\r\n [tooltip]=\"'flowplus.node.duplicate' | transloco\"\r\n (onClick)=\"duplicate.emit({ stepId: step().id })\"\r\n />\r\n @if (childWorkflowId(); as wfId) {\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.link-external-02\"\r\n [tooltip]=\"'flowplus.node.openChild' | transloco\"\r\n (onClick)=\"openChild.emit({ targetWorkflowId: wfId })\"\r\n />\r\n }\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"'flowplus.node.delete' | transloco\"\r\n (onClick)=\"remove.emit({ stepId: step().id })\"\r\n />\r\n </div>\r\n }\r\n\r\n <!-- Centered type icon -->\r\n <fp-automation-node-icon\r\n class=\"fp-canvas-node-icon relative z-[1]\"\r\n [type]=\"step().type\"\r\n [itemKey]=\"step().key\"\r\n [displayName]=\"step().name\"\r\n [metadata]=\"step().metadata\"\r\n [style.color]=\"color()\"\r\n />\r\n\r\n @if (!isPendingCreate() && runtimeTone() === \"running\") {\r\n <span\r\n class=\"pointer-events-none absolute inset-[3px] rounded-[5px] border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_5%,var(--p-content-background))] shadow-[inset_0_0_18px_color-mix(in_srgb,var(--p-primary-color)_10%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"pointer-events-none absolute inset-x-2 top-2 h-1 overflow-hidden rounded-full bg-[color-mix(in_srgb,var(--p-primary-color)_14%,transparent)]\"\r\n aria-hidden=\"true\"\r\n >\r\n <span\r\n class=\"block h-full w-7 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_920ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n } @else if (isPendingCreate()) {\r\n <span\r\n class=\"pointer-events-none absolute inset-x-2 top-2 h-1 overflow-hidden rounded-full bg-[color-mix(in_srgb,var(--p-primary-color)_14%,transparent)]\"\r\n >\r\n <span\r\n class=\"block h-full w-7 rounded-full bg-(--p-primary-color) animate-[fp-busy-bar-slide_900ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n <span\r\n class=\"pointer-events-none absolute top-2 right-2 inline-flex h-[18px] w-[18px] items-center justify-center rounded-full bg-(--p-content-background) text-(--p-primary-color) shadow-[0_1px_2px_rgba(15,23,42,0.08)]\"\r\n [title]=\"pendingLabel()\"\r\n >\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-[var(--p-primary-color)] animate-[fp-loading-spin_820ms_linear_infinite]\"\r\n ></span>\r\n </span>\r\n }\r\n\r\n <!-- Validation indicator \u2014 small premium rounded warning triangle + \"!\",\r\n sitting INSIDE the card at the bottom-right (n8n style). Color comes\r\n from `currentColor` so it stays crisp at any zoom. -->\r\n @if (badgeCount() > 0) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 top-1.5 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.color]=\"\r\n issueSeverity() === 'error'\r\n ? 'rgb(var(--fp-error))'\r\n : issueSeverity() === 'warning'\r\n ? 'rgb(var(--fp-warning))'\r\n : 'var(--p-primary-color)'\r\n \"\r\n tabindex=\"0\"\r\n role=\"img\"\r\n [attr.aria-label]=\"issueTooltip()\"\r\n [mtTooltip]=\"issueTooltip()\"\r\n [tooltipStyleClass]=\"issueTooltipClass()\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"\r\n $event.stopPropagation(); openDetails.emit({ stepId: step().id })\r\n \"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n }\r\n\r\n <!-- Input lives on the node host (fNodeInput above) \u2014 the connection's\r\n arrow marker is the visual cue, matching the Foblex call-center. -->\r\n\r\n <!-- Output ports \u2014 single dot, or a labeled stack for multi-output -->\r\n @if (!isPendingCreate() && runtimeTone(); as tone) {\r\n @if (tone === \"failed\") {\r\n <span\r\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-error))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (tone === \"completed\") {\r\n <span\r\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-commit))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"general.check\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else {\r\n <span\r\n class=\"pointer-events-auto absolute top-1 right-1 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center rounded-full border bg-(--p-content-background) shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color)\"\r\n [ngClass]=\"\r\n tone === 'running'\r\n ? 'shadow-[0_0_0_4px_color-mix(in_srgb,var(--p-primary-color)_12%,transparent)]'\r\n : ''\r\n \"\r\n [style.borderColor]=\"runtimeColor()\"\r\n [style.color]=\"runtimeColor()\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n @if (tone === \"running\") {\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-(--p-primary-color) animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n } @else {\r\n <mt-icon [icon]=\"runtimeIcon()\" class=\"[&_svg]:size-3.5\" />\r\n }\r\n </span>\r\n }\r\n }\r\n\r\n @if (!isPendingCreate() && node().outputs.length > 1) {\r\n <div\r\n class=\"pointer-events-none absolute top-0 bottom-0 left-[calc(100%-8px)] flex flex-col justify-center rtl:right-[calc(100%-8px)] rtl:left-auto\"\r\n [style.row-gap.px]=\"multiOutputGap\"\r\n >\r\n @for (\r\n port of node().outputs;\r\n track port.id;\r\n let outputIndex = $index;\r\n let outputCount = $count\r\n ) {\r\n <div\r\n class=\"pointer-events-auto relative flex min-w-[168px] items-center gap-2 pe-8 rtl:flex-row-reverse rtl:pe-0 rtl:ps-8\"\r\n >\r\n @if (!outputHasConnection(port.key)) {\r\n <span\r\n class=\"fp-node-output-rail pointer-events-none absolute left-3 right-6 top-1/2 h-0.5 -translate-y-1/2 bg-[rgb(var(--fp-connector))] rtl:left-6 rtl:right-3\"\r\n ></span>\r\n }\r\n <div\r\n #outputRef\r\n fNodeOutput\r\n [fOutputId]=\"port.id\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"port.allowMultiple\"\r\n [fOutputDisabled]=\"!!port.isTerminal\"\r\n class=\"fp-connect-output relative z-[2] flex h-4 w-4 shrink-0 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out hover:ring-4 hover:ring-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)]\"\r\n [class.is-terminal]=\"port.isTerminal\"\r\n [ngClass]=\"\r\n port.isTerminal\r\n ? 'cursor-not-allowed opacity-50 !border-[rgb(var(--fp-text-muted))] !bg-[rgb(var(--fp-text-muted))]'\r\n : ''\r\n \"\r\n [attr.aria-label]=\"resolveLabel(port.label)\"\r\n [title]=\"\r\n port.isTerminal\r\n ? 'Terminal action \u2014 cannot route.'\r\n : resolveLabel(port.label)\r\n \"\r\n ></div>\r\n @if (!outputHasConnection(port.key)) {\r\n <span\r\n class=\"relative z-[1] whitespace-nowrap rounded-full bg-(--p-content-background) px-1 text-[10px] font-medium text-(--p-text-muted-color)\"\r\n [class.line-through]=\"port.isTerminal\"\r\n >{{ resolveLabel(port.label) }}</span\r\n >\r\n }\r\n @if (\r\n !port.isTerminal &&\r\n canShowActions() &&\r\n !outputHasConnection(port.key)\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 right-0 z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md rtl:right-auto rtl:left-0\"\r\n [attr.aria-label]=\"'flowplus.node.addNext' | transloco\"\r\n [title]=\"'flowplus.node.addNext' | transloco\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, outputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, outputRef)\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n portPlusClick.emit({\r\n stepId: step().id,\r\n portKey: port.key,\r\n outputIndex,\r\n outputCount,\r\n })\r\n \"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else if (!isPendingCreate() && node().outputs.length === 1) {\r\n @for (port of node().outputs; track port.id) {\r\n <div\r\n #outputRef\r\n fNodeOutput\r\n [fOutputId]=\"port.id\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"port.allowMultiple\"\r\n [fOutputDisabled]=\"!!port.isTerminal\"\r\n class=\"fp-connect-output absolute top-1/2 -right-2 z-[2] flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out hover:ring-4 hover:ring-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)]\"\r\n [class.is-terminal]=\"port.isTerminal\"\r\n [ngClass]=\"\r\n port.isTerminal\r\n ? 'cursor-not-allowed opacity-50 !border-[rgb(var(--fp-text-muted))] !bg-[rgb(var(--fp-text-muted))]'\r\n : 'rtl:-left-2 rtl:right-auto'\r\n \"\r\n [attr.aria-label]=\"resolveLabel(port.label)\"\r\n [title]=\"\r\n port.isTerminal\r\n ? 'Terminal action \u2014 cannot route.'\r\n : resolveLabel(port.label)\r\n \"\r\n ></div>\r\n <!-- \"+\" quick-add only while the output has NO downstream step yet. Once\r\n connected we drop the \"+\"/line entirely and let the output dot be\r\n the connector (it shows a \"+\" cursor to invite another link). -->\r\n @if (\r\n !port.isTerminal && canShowActions() && !outputHasConnection(port.key)\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 -right-[72px] z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out before:absolute before:top-1/2 before:right-full before:h-0.5 before:w-12 before:-translate-y-1/2 before:bg-[rgb(var(--fp-connector))] before:transition-colors before:duration-200 hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md hover:before:bg-(--p-primary-color) rtl:-left-[72px] rtl:right-auto rtl:before:right-auto rtl:before:left-full\"\r\n [attr.aria-label]=\"'flowplus.node.addNext' | transloco\"\r\n [title]=\"'flowplus.node.addNext' | transloco\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, outputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, outputRef)\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n portPlusClick.emit({\r\n stepId: step().id,\r\n portKey: port.key,\r\n outputIndex: 0,\r\n outputCount: 1,\r\n })\r\n \"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n }\r\n }\r\n\r\n <!-- Label below the node (absolute \u2014 does not inflate the node box) -->\r\n <div\r\n class=\"absolute left-1/2 top-[calc(100%+8px)] w-[128px] -translate-x-1/2 text-center leading-tight\"\r\n >\r\n <div\r\n class=\"line-clamp-2 text-[12px] font-semibold text-(--p-text-color)\"\r\n [title]=\"title()\"\r\n >\r\n {{ title() }}\r\n </div>\r\n @if (runtimeTone(); as tone) {\r\n <div\r\n class=\"mt-0.5 flex max-w-full items-center justify-center gap-1 text-[10.5px] font-semibold leading-tight\"\r\n [style.color]=\"runtimeColor()\"\r\n [title]=\"loopProgressLabel() ?? runtimeStatusLabel()\"\r\n >\r\n @if (tone === \"running\") {\r\n <span\r\n class=\"block h-2 w-2 shrink-0 rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] border-t-(--p-primary-color) animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n } @else {\r\n <mt-icon [icon]=\"runtimeIcon()\" class=\"shrink-0 [&_svg]:size-3\" />\r\n }\r\n <span class=\"truncate\">{{\r\n loopProgressLabel() ?? runtimeStatusLabel()\r\n }}</span>\r\n </div>\r\n } @else if (isPendingCreate()) {\r\n <div\r\n class=\"mt-0.5 truncate text-[10.5px] font-semibold text-(--p-primary-color)\"\r\n >\r\n {{ pendingLabel() }}\r\n </div>\r\n } @else if (step().isInitial) {\r\n <!-- Soft, premium \"initial step\" cue \u2014 a gentle primary-tinted pill so\r\n the workflow's entry point reads at a glance without shouting. -->\r\n <span\r\n class=\"mt-1 inline-flex items-center gap-1 rounded-full bg-(--p-primary-color)/10 px-2 py-0.5 text-[10px] font-semibold text-(--p-primary-color)\"\r\n >\r\n <mt-icon icon=\"map.flag-01\" class=\"[&_svg]:size-3\" />\r\n {{ \"flowplus.node.initial\" | transloco }}\r\n </span>\r\n } @else if (subtitle()) {\r\n <div\r\n class=\"mt-0.5 truncate font-mono text-[10px] font-normal text-(--p-text-muted-color)\"\r\n >\r\n {{ subtitle() }}\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n" }]
|
|
29400
29403
|
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], connectedOutputKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "connectedOutputKeys", required: false }] }], positionChange: [{
|
|
29401
29404
|
type: Output
|
|
29402
29405
|
}], nodeClick: [{
|
|
@@ -29744,7 +29747,7 @@ class TriggerNodeComponent {
|
|
|
29744
29747
|
return !t || t === key ? fallback : t;
|
|
29745
29748
|
}
|
|
29746
29749
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TriggerNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
29747
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TriggerNodeComponent, isStandalone: true, selector: "fp-trigger-node", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: true, transformFunction: null }, noFirstStep: { classPropertyName: "noFirstStep", publicName: "noFirstStep", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addFirstStep: "addFirstStep", configure: "configure", execute: "execute", toggleEnabled: "toggleEnabled", remove: "remove", positionChange: "positionChange" }, ngImport: i0, template: "<!-- The rounded-left \"start\" card stays a real Foblex node host. The runtime\r\n toolbar and execute CTA are floating children so the drag/connect surface\r\n remains premium and predictable. -->\r\n<div\r\n (dblclick)=\"configure.emit({ triggerId: trigger().triggerId })\"\r\n class=\"fp-canvas-trigger-card group relative grid h-[88px] w-[96px] cursor-grab select-none place-items-center rounded-l-[44px] rounded-r-md border border-[rgb(var(--fp-border-strong))] bg-(--p-content-background) shadow-[0_1px_2px_rgba(15,23,42,0.07)] transition-[box-shadow,border-color,transform] duration-200 hover:border-[color-mix(in_srgb,var(--p-primary-color)_45%,rgb(var(--fp-border-strong)))] hover:shadow-md\"\r\n [ngClass]=\"{\r\n '!border-[rgb(var(--fp-app-action))] shadow-[0_0_0_3px_rgba(245,158,11,0.16)]':\r\n isRunning(),\r\n '!border-[rgb(var(--fp-commit))] shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-commit))_16%,transparent)]':\r\n isCompleted(),\r\n '!border-[rgb(var(--fp-error))] shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-error))_14%,transparent)]':\r\n isFailed(),\r\n }\"\r\n [attr.aria-label]=\"title()\"\r\n [attr.aria-busy]=\"isRunning() ? 'true' : null\"\r\n>\r\n <div\r\n class=\"absolute top-1/2 right-[calc(100%+10px)] z-[5] -translate-y-1/2\"\r\n [class.cursor-help]=\"!canExecute()\"\r\n [mtTooltip]=\"executeHint()\"\r\n [tooltipDisabled]=\"canExecute() || !executeHint()\"\r\n tooltipPosition=\"top\"\r\n tooltipStyleClass=\"fp-trigger-execute-tooltip\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"relative flex h-9 origin-right items-center justify-center overflow-hidden rounded-full border transition-[width,padding,background-color,border-color,color,box-shadow,transform] duration-250 ease-out\"\r\n [ngClass]=\"\r\n canExecute()\r\n ? 'w-9 cursor-pointer border-[color-mix(in_srgb,rgb(var(--fp-app-action))_22%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_12%,var(--p-content-background))] text-[rgb(var(--fp-app-action))] shadow-[0_6px_16px_rgba(245,158,11,0.0)] group-hover:w-[176px] group-hover:justify-start group-hover:px-4 group-hover:bg-[rgb(var(--fp-app-action))] group-hover:text-white group-hover:shadow-[0_12px_24px_rgba(245,158,11,0.22)]'\r\n : 'pointer-events-none w-9 cursor-help border-[color-mix(in_srgb,rgb(var(--fp-app-action))_20%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_8%,var(--p-content-background))] text-[rgb(var(--fp-app-action))] opacity-75 shadow-none group-hover:w-[176px] group-hover:justify-start group-hover:px-4 group-hover:bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_14%,var(--p-content-background))] group-hover:shadow-[0_8px_18px_rgba(245,158,11,0.12)]'\r\n \"\r\n [disabled]=\"!canExecute()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"onExecute($event)\"\r\n >\r\n <span\r\n class=\"pointer-events-none absolute inset-0 flex items-center justify-center transition-[opacity,transform] duration-200 group-hover:scale-90 group-hover:opacity-0\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon icon=\"general.zap-fast\" class=\"[&_svg]:size-[18px]\" />\r\n </span>\r\n <span\r\n class=\"pointer-events-none flex items-center gap-2 whitespace-nowrap text-[13px] font-semibold opacity-0 translate-x-2 transition-[opacity,transform] duration-200 group-hover:translate-x-0 group-hover:opacity-100\"\r\n >\r\n <mt-icon icon=\"media.play\" class=\"[&_svg]:size-4\" />\r\n {{ executeLabel() }}\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <div\r\n class=\"pointer-events-none absolute -top-[44px] left-1/2 z-[6] flex -translate-x-1/2 items-center gap-1.5 opacity-0 transition-opacity duration-150 before:absolute before:inset-x-0 before:top-full before:h-5 before:content-[''] group-hover:pointer-events-auto group-hover:opacity-100\"\r\n >\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.plus\"\r\n [tooltip]=\"outletLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onAddFirstStep($event)\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"trigger().enabled ? 'general.check-circle' : 'general.x-close'\"\r\n [tooltip]=\"enabledLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onToggleEnabled($event)\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"'flowplus.trigger.node.configure' | transloco\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"\r\n $event.stopPropagation();\r\n configure.emit({ triggerId: trigger().triggerId })\r\n \"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"deleteLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onRemove($event)\"\r\n />\r\n </div>\r\n\r\n @if (isRunning()) {\r\n <span\r\n class=\"pointer-events-none absolute inset-[5px] rounded-l-[38px] rounded-r-[6px] border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_18%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_6%,var(--p-content-background))] shadow-[inset_0_0_22px_color-mix(in_srgb,rgb(var(--fp-app-action))_12%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"pointer-events-none absolute left-1/2 top-1/2 z-[1] h-12 w-12 -translate-x-1/2 -translate-y-1/2 rounded-full border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_20%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_5%,transparent)] shadow-[0_0_0_7px_color-mix(in_srgb,rgb(var(--fp-app-action))_7%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n }\r\n\r\n <fp-automation-node-icon\r\n type=\"Trigger\"\r\n [itemKey]=\"trigger().type\"\r\n [displayName]=\"trigger().name\"\r\n class=\"fp-canvas-trigger-icon relative z-[2] transition-colors duration-200\"\r\n [style.color]=\"triggerAccent().color\"\r\n />\r\n\r\n @if (issueCount() > 0) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 bottom-1.5 z-[4] inline-flex h-5 w-5 cursor-help items-center justify-center outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.color]=\"\r\n issueSeverity() === 'error'\r\n ? 'rgb(var(--fp-error))'\r\n : issueSeverity() === 'warning'\r\n ? 'rgb(var(--fp-warning))'\r\n : 'var(--p-primary-color)'\r\n \"\r\n tabindex=\"0\"\r\n role=\"img\"\r\n [attr.aria-label]=\"issueTooltip()\"\r\n [mtTooltip]=\"issueTooltip()\"\r\n [tooltipStyleClass]=\"issueTooltipClass()\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n configure.emit({ triggerId: trigger().triggerId })\r\n \"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (isRunning()) {\n <span\n class=\"pointer-events-auto absolute bottom-1.5 right-1.5 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center rounded-full border bg-(--p-content-background) shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-app-action))_10%,transparent)] outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\n [style.borderColor]=\"runtimeColor()\"\n [style.color]=\"runtimeColor()\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,rgb(var(--fp-app-action))_26%,transparent)] border-t-[rgb(var(--fp-app-action))] animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n </span>\r\n } @else if (isCompleted()) {\n <span\n class=\"pointer-events-auto absolute right-1 bottom-1 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center text-[rgb(var(--fp-commit))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1 rtl:right-auto\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"general.check\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (isFailed()) {\n <span\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 bottom-1.5 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-error))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n }\r\n\r\n <div\r\n #triggerOutputRef\r\n fNodeOutput\r\n [fOutputId]=\"outputId()\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"true\"\r\n class=\"fp-connect-output absolute top-1/2 -right-2 z-[2] flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out rtl:-left-2 rtl:right-auto\"\r\n [attr.aria-label]=\"outletLabel()\"\r\n [title]=\"outletLabel()\"\r\n ></div>\r\n\r\n @if (noFirstStep()) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 -right-[72px] z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out before:absolute before:top-1/2 before:right-full before:h-0.5 before:w-12 before:-translate-y-1/2 before:bg-[rgb(var(--fp-connector))] before:transition-colors before:duration-200 hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md hover:before:bg-(--p-primary-color) rtl:-left-[72px] rtl:right-auto rtl:before:right-auto rtl:before:left-full\"\r\n [attr.aria-label]=\"outletLabel()\"\r\n [title]=\"outletLabel()\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, triggerOutputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, triggerOutputRef)\"\r\n (click)=\"onAddFirstStep($event)\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n\r\n <div\r\n class=\"absolute left-1/2 top-[calc(100%+8px)] w-[148px] -translate-x-1/2 text-center leading-tight\"\r\n >\r\n <div\r\n class=\"line-clamp-2 text-[12px] font-semibold text-(--p-text-color)\"\r\n [title]=\"title()\"\r\n >\r\n {{ title() }}\r\n </div>\r\n @if (isRunning() || isCompleted() || isFailed()) {\n <div\n class=\"mt-0.5 flex max-w-full items-center justify-center gap-1 text-[10.5px] font-semibold leading-tight\"\n [style.color]=\"runtimeColor()\"\n [title]=\"runtimeStatusLabel()\"\n >\n @if (isRunning()) {\n <span\n class=\"block h-2 w-2 shrink-0 rounded-full border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_28%,transparent)] border-t-[rgb(var(--fp-app-action))] animate-[fp-loading-spin_760ms_linear_infinite]\"\n aria-hidden=\"true\"\n ></span>\n } @else if (isCompleted()) {\n <mt-icon icon=\"general.check\" class=\"shrink-0 [&_svg]:size-3\" />\n } @else {\n <mt-icon\n icon=\"alert.alert-triangle\"\n class=\"shrink-0 [&_svg]:size-3\"\n />\n }\n <span class=\"truncate\">{{ runtimeStatusLabel() }}</span>\n </div>\n } @else if (runtimeStatus()) {\n <div\n class=\"mt-0.5 truncate text-[10.5px] font-semibold\"\n [style.color]=\"runtimeColor()\"\n >\n {{ runtimeStatus() }}\n </div>\n }\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FFlowModule }, { kind: "directive", type: i1$2.FNodeOutputDirective, selector: "[fNodeOutput]", inputs: ["fOutputId", "fOutputMultiple", "fOutputDisabled", "fOutputConnectableSide", "isSelfConnectable", "fCanBeConnectedInputs"], exportAs: ["fNodeOutput"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
|
|
29750
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TriggerNodeComponent, isStandalone: true, selector: "fp-trigger-node", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: true, transformFunction: null }, noFirstStep: { classPropertyName: "noFirstStep", publicName: "noFirstStep", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addFirstStep: "addFirstStep", configure: "configure", execute: "execute", toggleEnabled: "toggleEnabled", remove: "remove", positionChange: "positionChange" }, ngImport: i0, template: "<!-- The rounded-left \"start\" card stays a real Foblex node host. The runtime\r\n toolbar and execute CTA are floating children so the drag/connect surface\r\n remains premium and predictable. -->\r\n<div\r\n (dblclick)=\"configure.emit({ triggerId: trigger().triggerId })\"\r\n class=\"fp-canvas-trigger-card group relative grid h-[88px] w-[96px] cursor-grab select-none place-items-center rounded-l-[44px] rounded-r-md border border-[rgb(var(--fp-border-strong))] bg-(--p-content-background) shadow-[0_1px_2px_rgba(15,23,42,0.07)] transition-[box-shadow,border-color,transform] duration-200 hover:border-[color-mix(in_srgb,var(--p-primary-color)_45%,rgb(var(--fp-border-strong)))] hover:shadow-md\"\r\n [ngClass]=\"{\r\n '!border-[rgb(var(--fp-app-action))] shadow-[0_0_0_3px_rgba(245,158,11,0.16)]':\r\n isRunning(),\r\n '!border-[rgb(var(--fp-commit))] shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-commit))_16%,transparent)]':\r\n isCompleted(),\r\n '!border-[rgb(var(--fp-error))] shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-error))_14%,transparent)]':\r\n isFailed(),\r\n }\"\r\n [attr.aria-label]=\"title()\"\r\n [attr.aria-busy]=\"isRunning() ? 'true' : null\"\r\n>\r\n <div\r\n class=\"absolute top-1/2 right-[calc(100%+10px)] z-[5] -translate-y-1/2\"\r\n [class.cursor-help]=\"!canExecute()\"\r\n [mtTooltip]=\"executeHint()\"\r\n [tooltipDisabled]=\"canExecute() || !executeHint()\"\r\n tooltipPosition=\"top\"\r\n tooltipStyleClass=\"fp-trigger-execute-tooltip\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"relative flex h-9 origin-right items-center justify-center overflow-hidden rounded-full border transition-[width,padding,background-color,border-color,color,box-shadow,transform] duration-250 ease-out\"\r\n [ngClass]=\"\r\n canExecute()\r\n ? 'w-9 cursor-pointer border-[color-mix(in_srgb,rgb(var(--fp-app-action))_22%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_12%,var(--p-content-background))] text-[rgb(var(--fp-app-action))] shadow-[0_6px_16px_rgba(245,158,11,0.0)] group-hover:w-[176px] group-hover:justify-start group-hover:px-4 group-hover:bg-[rgb(var(--fp-app-action))] group-hover:text-white group-hover:shadow-[0_12px_24px_rgba(245,158,11,0.22)]'\r\n : 'pointer-events-none w-9 cursor-help border-[color-mix(in_srgb,rgb(var(--fp-app-action))_20%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_8%,var(--p-content-background))] text-[rgb(var(--fp-app-action))] opacity-75 shadow-none group-hover:w-[176px] group-hover:justify-start group-hover:px-4 group-hover:bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_14%,var(--p-content-background))] group-hover:shadow-[0_8px_18px_rgba(245,158,11,0.12)]'\r\n \"\r\n [disabled]=\"!canExecute()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"onExecute($event)\"\r\n >\r\n <span\r\n class=\"pointer-events-none absolute inset-0 flex items-center justify-center transition-[opacity,transform] duration-200 group-hover:scale-90 group-hover:opacity-0\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon icon=\"general.zap-fast\" class=\"[&_svg]:size-[18px]\" />\r\n </span>\r\n <span\r\n class=\"pointer-events-none flex items-center gap-2 whitespace-nowrap text-[13px] font-semibold opacity-0 translate-x-2 transition-[opacity,transform] duration-200 group-hover:translate-x-0 group-hover:opacity-100\"\r\n >\r\n <mt-icon icon=\"media.play\" class=\"[&_svg]:size-4\" />\r\n {{ executeLabel() }}\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <div\r\n class=\"pointer-events-none absolute -top-[44px] left-1/2 z-[6] flex -translate-x-1/2 items-center gap-1.5 opacity-0 transition-opacity duration-150 before:absolute before:inset-x-0 before:top-full before:h-5 before:content-[''] group-hover:pointer-events-auto group-hover:opacity-100\"\r\n >\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.plus\"\r\n [tooltip]=\"outletLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onAddFirstStep($event)\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"trigger().enabled ? 'general.check-circle' : 'general.x-close'\"\r\n [tooltip]=\"enabledLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onToggleEnabled($event)\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"'flowplus.trigger.node.configure' | transloco\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"\r\n $event.stopPropagation();\r\n configure.emit({ triggerId: trigger().triggerId })\r\n \"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"deleteLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onRemove($event)\"\r\n />\r\n </div>\r\n\r\n @if (isRunning()) {\r\n <span\r\n class=\"pointer-events-none absolute inset-[5px] rounded-l-[38px] rounded-r-[6px] border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_18%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_6%,var(--p-content-background))] shadow-[inset_0_0_22px_color-mix(in_srgb,rgb(var(--fp-app-action))_12%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"pointer-events-none absolute left-1/2 top-1/2 z-[1] h-12 w-12 -translate-x-1/2 -translate-y-1/2 rounded-full border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_20%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_5%,transparent)] shadow-[0_0_0_7px_color-mix(in_srgb,rgb(var(--fp-app-action))_7%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n }\r\n\r\n <fp-automation-node-icon\r\n type=\"Trigger\"\r\n [itemKey]=\"trigger().type\"\r\n [displayName]=\"trigger().name\"\r\n class=\"fp-canvas-trigger-icon relative z-[2] transition-colors duration-200\"\r\n [style.color]=\"triggerAccent().color\"\r\n />\r\n\r\n @if (issueCount() > 0) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 bottom-1.5 z-[4] inline-flex h-5 w-5 cursor-help items-center justify-center outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.color]=\"\r\n issueSeverity() === 'error'\r\n ? 'rgb(var(--fp-error))'\r\n : issueSeverity() === 'warning'\r\n ? 'rgb(var(--fp-warning))'\r\n : 'var(--p-primary-color)'\r\n \"\r\n tabindex=\"0\"\r\n role=\"img\"\r\n [attr.aria-label]=\"issueTooltip()\"\r\n [mtTooltip]=\"issueTooltip()\"\r\n [tooltipStyleClass]=\"issueTooltipClass()\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n configure.emit({ triggerId: trigger().triggerId })\r\n \"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (isRunning()) {\r\n <span\r\n class=\"pointer-events-auto absolute bottom-1.5 right-1.5 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center rounded-full border bg-(--p-content-background) shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-app-action))_10%,transparent)] outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.borderColor]=\"runtimeColor()\"\r\n [style.color]=\"runtimeColor()\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,rgb(var(--fp-app-action))_26%,transparent)] border-t-[rgb(var(--fp-app-action))] animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n </span>\r\n } @else if (isCompleted()) {\r\n <span\r\n class=\"pointer-events-auto absolute right-1 bottom-1 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center text-[rgb(var(--fp-commit))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1 rtl:right-auto\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"general.check\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (isFailed()) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 bottom-1.5 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-error))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n }\r\n\r\n <div\r\n #triggerOutputRef\r\n fNodeOutput\r\n [fOutputId]=\"outputId()\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"true\"\r\n class=\"fp-connect-output absolute top-1/2 -right-2 z-[2] flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out rtl:-left-2 rtl:right-auto\"\r\n [attr.aria-label]=\"outletLabel()\"\r\n [title]=\"outletLabel()\"\r\n ></div>\r\n\r\n @if (noFirstStep()) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 -right-[72px] z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out before:absolute before:top-1/2 before:right-full before:h-0.5 before:w-12 before:-translate-y-1/2 before:bg-[rgb(var(--fp-connector))] before:transition-colors before:duration-200 hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md hover:before:bg-(--p-primary-color) rtl:-left-[72px] rtl:right-auto rtl:before:right-auto rtl:before:left-full\"\r\n [attr.aria-label]=\"outletLabel()\"\r\n [title]=\"outletLabel()\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, triggerOutputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, triggerOutputRef)\"\r\n (click)=\"onAddFirstStep($event)\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n\r\n <div\r\n class=\"absolute left-1/2 top-[calc(100%+8px)] w-[148px] -translate-x-1/2 text-center leading-tight\"\r\n >\r\n <div\r\n class=\"line-clamp-2 text-[12px] font-semibold text-(--p-text-color)\"\r\n [title]=\"title()\"\r\n >\r\n {{ title() }}\r\n </div>\r\n @if (isRunning() || isCompleted() || isFailed()) {\r\n <div\r\n class=\"mt-0.5 flex max-w-full items-center justify-center gap-1 text-[10.5px] font-semibold leading-tight\"\r\n [style.color]=\"runtimeColor()\"\r\n [title]=\"runtimeStatusLabel()\"\r\n >\r\n @if (isRunning()) {\r\n <span\r\n class=\"block h-2 w-2 shrink-0 rounded-full border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_28%,transparent)] border-t-[rgb(var(--fp-app-action))] animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n } @else if (isCompleted()) {\r\n <mt-icon icon=\"general.check\" class=\"shrink-0 [&_svg]:size-3\" />\r\n } @else {\r\n <mt-icon\r\n icon=\"alert.alert-triangle\"\r\n class=\"shrink-0 [&_svg]:size-3\"\r\n />\r\n }\r\n <span class=\"truncate\">{{ runtimeStatusLabel() }}</span>\r\n </div>\r\n } @else if (runtimeStatus()) {\r\n <div\r\n class=\"mt-0.5 truncate text-[10.5px] font-semibold\"\r\n [style.color]=\"runtimeColor()\"\r\n >\r\n {{ runtimeStatus() }}\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FFlowModule }, { kind: "directive", type: i1$2.FNodeOutputDirective, selector: "[fNodeOutput]", inputs: ["fOutputId", "fOutputMultiple", "fOutputDisabled", "fOutputConnectableSide", "isSelfConnectable", "fCanBeConnectedInputs"], exportAs: ["fNodeOutput"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
|
|
29748
29751
|
}
|
|
29749
29752
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TriggerNodeComponent, decorators: [{
|
|
29750
29753
|
type: Component,
|
|
@@ -29756,7 +29759,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
29756
29759
|
Button,
|
|
29757
29760
|
Tooltip,
|
|
29758
29761
|
Icon,
|
|
29759
|
-
], template: "<!-- The rounded-left \"start\" card stays a real Foblex node host. The runtime\r\n toolbar and execute CTA are floating children so the drag/connect surface\r\n remains premium and predictable. -->\r\n<div\r\n (dblclick)=\"configure.emit({ triggerId: trigger().triggerId })\"\r\n class=\"fp-canvas-trigger-card group relative grid h-[88px] w-[96px] cursor-grab select-none place-items-center rounded-l-[44px] rounded-r-md border border-[rgb(var(--fp-border-strong))] bg-(--p-content-background) shadow-[0_1px_2px_rgba(15,23,42,0.07)] transition-[box-shadow,border-color,transform] duration-200 hover:border-[color-mix(in_srgb,var(--p-primary-color)_45%,rgb(var(--fp-border-strong)))] hover:shadow-md\"\r\n [ngClass]=\"{\r\n '!border-[rgb(var(--fp-app-action))] shadow-[0_0_0_3px_rgba(245,158,11,0.16)]':\r\n isRunning(),\r\n '!border-[rgb(var(--fp-commit))] shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-commit))_16%,transparent)]':\r\n isCompleted(),\r\n '!border-[rgb(var(--fp-error))] shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-error))_14%,transparent)]':\r\n isFailed(),\r\n }\"\r\n [attr.aria-label]=\"title()\"\r\n [attr.aria-busy]=\"isRunning() ? 'true' : null\"\r\n>\r\n <div\r\n class=\"absolute top-1/2 right-[calc(100%+10px)] z-[5] -translate-y-1/2\"\r\n [class.cursor-help]=\"!canExecute()\"\r\n [mtTooltip]=\"executeHint()\"\r\n [tooltipDisabled]=\"canExecute() || !executeHint()\"\r\n tooltipPosition=\"top\"\r\n tooltipStyleClass=\"fp-trigger-execute-tooltip\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"relative flex h-9 origin-right items-center justify-center overflow-hidden rounded-full border transition-[width,padding,background-color,border-color,color,box-shadow,transform] duration-250 ease-out\"\r\n [ngClass]=\"\r\n canExecute()\r\n ? 'w-9 cursor-pointer border-[color-mix(in_srgb,rgb(var(--fp-app-action))_22%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_12%,var(--p-content-background))] text-[rgb(var(--fp-app-action))] shadow-[0_6px_16px_rgba(245,158,11,0.0)] group-hover:w-[176px] group-hover:justify-start group-hover:px-4 group-hover:bg-[rgb(var(--fp-app-action))] group-hover:text-white group-hover:shadow-[0_12px_24px_rgba(245,158,11,0.22)]'\r\n : 'pointer-events-none w-9 cursor-help border-[color-mix(in_srgb,rgb(var(--fp-app-action))_20%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_8%,var(--p-content-background))] text-[rgb(var(--fp-app-action))] opacity-75 shadow-none group-hover:w-[176px] group-hover:justify-start group-hover:px-4 group-hover:bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_14%,var(--p-content-background))] group-hover:shadow-[0_8px_18px_rgba(245,158,11,0.12)]'\r\n \"\r\n [disabled]=\"!canExecute()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"onExecute($event)\"\r\n >\r\n <span\r\n class=\"pointer-events-none absolute inset-0 flex items-center justify-center transition-[opacity,transform] duration-200 group-hover:scale-90 group-hover:opacity-0\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon icon=\"general.zap-fast\" class=\"[&_svg]:size-[18px]\" />\r\n </span>\r\n <span\r\n class=\"pointer-events-none flex items-center gap-2 whitespace-nowrap text-[13px] font-semibold opacity-0 translate-x-2 transition-[opacity,transform] duration-200 group-hover:translate-x-0 group-hover:opacity-100\"\r\n >\r\n <mt-icon icon=\"media.play\" class=\"[&_svg]:size-4\" />\r\n {{ executeLabel() }}\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <div\r\n class=\"pointer-events-none absolute -top-[44px] left-1/2 z-[6] flex -translate-x-1/2 items-center gap-1.5 opacity-0 transition-opacity duration-150 before:absolute before:inset-x-0 before:top-full before:h-5 before:content-[''] group-hover:pointer-events-auto group-hover:opacity-100\"\r\n >\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.plus\"\r\n [tooltip]=\"outletLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onAddFirstStep($event)\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"trigger().enabled ? 'general.check-circle' : 'general.x-close'\"\r\n [tooltip]=\"enabledLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onToggleEnabled($event)\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"'flowplus.trigger.node.configure' | transloco\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"\r\n $event.stopPropagation();\r\n configure.emit({ triggerId: trigger().triggerId })\r\n \"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"deleteLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onRemove($event)\"\r\n />\r\n </div>\r\n\r\n @if (isRunning()) {\r\n <span\r\n class=\"pointer-events-none absolute inset-[5px] rounded-l-[38px] rounded-r-[6px] border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_18%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_6%,var(--p-content-background))] shadow-[inset_0_0_22px_color-mix(in_srgb,rgb(var(--fp-app-action))_12%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"pointer-events-none absolute left-1/2 top-1/2 z-[1] h-12 w-12 -translate-x-1/2 -translate-y-1/2 rounded-full border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_20%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_5%,transparent)] shadow-[0_0_0_7px_color-mix(in_srgb,rgb(var(--fp-app-action))_7%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n }\r\n\r\n <fp-automation-node-icon\r\n type=\"Trigger\"\r\n [itemKey]=\"trigger().type\"\r\n [displayName]=\"trigger().name\"\r\n class=\"fp-canvas-trigger-icon relative z-[2] transition-colors duration-200\"\r\n [style.color]=\"triggerAccent().color\"\r\n />\r\n\r\n @if (issueCount() > 0) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 bottom-1.5 z-[4] inline-flex h-5 w-5 cursor-help items-center justify-center outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.color]=\"\r\n issueSeverity() === 'error'\r\n ? 'rgb(var(--fp-error))'\r\n : issueSeverity() === 'warning'\r\n ? 'rgb(var(--fp-warning))'\r\n : 'var(--p-primary-color)'\r\n \"\r\n tabindex=\"0\"\r\n role=\"img\"\r\n [attr.aria-label]=\"issueTooltip()\"\r\n [mtTooltip]=\"issueTooltip()\"\r\n [tooltipStyleClass]=\"issueTooltipClass()\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n configure.emit({ triggerId: trigger().triggerId })\r\n \"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (isRunning()) {\n <span\n class=\"pointer-events-auto absolute bottom-1.5 right-1.5 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center rounded-full border bg-(--p-content-background) shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-app-action))_10%,transparent)] outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\n [style.borderColor]=\"runtimeColor()\"\n [style.color]=\"runtimeColor()\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,rgb(var(--fp-app-action))_26%,transparent)] border-t-[rgb(var(--fp-app-action))] animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n </span>\r\n } @else if (isCompleted()) {\n <span\n class=\"pointer-events-auto absolute right-1 bottom-1 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center text-[rgb(var(--fp-commit))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1 rtl:right-auto\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"general.check\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (isFailed()) {\n <span\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 bottom-1.5 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-error))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\n tabindex=\"0\"\n role=\"status\"\n [attr.aria-label]=\"runtimeTooltip()\"\n [mtTooltip]=\"runtimeTooltip()\"\n tooltipStyleClass=\"fp-runtime-tooltip\"\n tooltipPosition=\"top\"\n appendTo=\"body\"\n [escape]=\"true\"\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n }\r\n\r\n <div\r\n #triggerOutputRef\r\n fNodeOutput\r\n [fOutputId]=\"outputId()\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"true\"\r\n class=\"fp-connect-output absolute top-1/2 -right-2 z-[2] flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out rtl:-left-2 rtl:right-auto\"\r\n [attr.aria-label]=\"outletLabel()\"\r\n [title]=\"outletLabel()\"\r\n ></div>\r\n\r\n @if (noFirstStep()) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 -right-[72px] z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out before:absolute before:top-1/2 before:right-full before:h-0.5 before:w-12 before:-translate-y-1/2 before:bg-[rgb(var(--fp-connector))] before:transition-colors before:duration-200 hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md hover:before:bg-(--p-primary-color) rtl:-left-[72px] rtl:right-auto rtl:before:right-auto rtl:before:left-full\"\r\n [attr.aria-label]=\"outletLabel()\"\r\n [title]=\"outletLabel()\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, triggerOutputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, triggerOutputRef)\"\r\n (click)=\"onAddFirstStep($event)\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n\r\n <div\r\n class=\"absolute left-1/2 top-[calc(100%+8px)] w-[148px] -translate-x-1/2 text-center leading-tight\"\r\n >\r\n <div\r\n class=\"line-clamp-2 text-[12px] font-semibold text-(--p-text-color)\"\r\n [title]=\"title()\"\r\n >\r\n {{ title() }}\r\n </div>\r\n @if (isRunning() || isCompleted() || isFailed()) {\n <div\n class=\"mt-0.5 flex max-w-full items-center justify-center gap-1 text-[10.5px] font-semibold leading-tight\"\n [style.color]=\"runtimeColor()\"\n [title]=\"runtimeStatusLabel()\"\n >\n @if (isRunning()) {\n <span\n class=\"block h-2 w-2 shrink-0 rounded-full border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_28%,transparent)] border-t-[rgb(var(--fp-app-action))] animate-[fp-loading-spin_760ms_linear_infinite]\"\n aria-hidden=\"true\"\n ></span>\n } @else if (isCompleted()) {\n <mt-icon icon=\"general.check\" class=\"shrink-0 [&_svg]:size-3\" />\n } @else {\n <mt-icon\n icon=\"alert.alert-triangle\"\n class=\"shrink-0 [&_svg]:size-3\"\n />\n }\n <span class=\"truncate\">{{ runtimeStatusLabel() }}</span>\n </div>\n } @else if (runtimeStatus()) {\n <div\n class=\"mt-0.5 truncate text-[10.5px] font-semibold\"\n [style.color]=\"runtimeColor()\"\n >\n {{ runtimeStatus() }}\n </div>\n }\r\n </div>\r\n</div>\r\n" }]
|
|
29762
|
+
], template: "<!-- The rounded-left \"start\" card stays a real Foblex node host. The runtime\r\n toolbar and execute CTA are floating children so the drag/connect surface\r\n remains premium and predictable. -->\r\n<div\r\n (dblclick)=\"configure.emit({ triggerId: trigger().triggerId })\"\r\n class=\"fp-canvas-trigger-card group relative grid h-[88px] w-[96px] cursor-grab select-none place-items-center rounded-l-[44px] rounded-r-md border border-[rgb(var(--fp-border-strong))] bg-(--p-content-background) shadow-[0_1px_2px_rgba(15,23,42,0.07)] transition-[box-shadow,border-color,transform] duration-200 hover:border-[color-mix(in_srgb,var(--p-primary-color)_45%,rgb(var(--fp-border-strong)))] hover:shadow-md\"\r\n [ngClass]=\"{\r\n '!border-[rgb(var(--fp-app-action))] shadow-[0_0_0_3px_rgba(245,158,11,0.16)]':\r\n isRunning(),\r\n '!border-[rgb(var(--fp-commit))] shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-commit))_16%,transparent)]':\r\n isCompleted(),\r\n '!border-[rgb(var(--fp-error))] shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-error))_14%,transparent)]':\r\n isFailed(),\r\n }\"\r\n [attr.aria-label]=\"title()\"\r\n [attr.aria-busy]=\"isRunning() ? 'true' : null\"\r\n>\r\n <div\r\n class=\"absolute top-1/2 right-[calc(100%+10px)] z-[5] -translate-y-1/2\"\r\n [class.cursor-help]=\"!canExecute()\"\r\n [mtTooltip]=\"executeHint()\"\r\n [tooltipDisabled]=\"canExecute() || !executeHint()\"\r\n tooltipPosition=\"top\"\r\n tooltipStyleClass=\"fp-trigger-execute-tooltip\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"relative flex h-9 origin-right items-center justify-center overflow-hidden rounded-full border transition-[width,padding,background-color,border-color,color,box-shadow,transform] duration-250 ease-out\"\r\n [ngClass]=\"\r\n canExecute()\r\n ? 'w-9 cursor-pointer border-[color-mix(in_srgb,rgb(var(--fp-app-action))_22%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_12%,var(--p-content-background))] text-[rgb(var(--fp-app-action))] shadow-[0_6px_16px_rgba(245,158,11,0.0)] group-hover:w-[176px] group-hover:justify-start group-hover:px-4 group-hover:bg-[rgb(var(--fp-app-action))] group-hover:text-white group-hover:shadow-[0_12px_24px_rgba(245,158,11,0.22)]'\r\n : 'pointer-events-none w-9 cursor-help border-[color-mix(in_srgb,rgb(var(--fp-app-action))_20%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_8%,var(--p-content-background))] text-[rgb(var(--fp-app-action))] opacity-75 shadow-none group-hover:w-[176px] group-hover:justify-start group-hover:px-4 group-hover:bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_14%,var(--p-content-background))] group-hover:shadow-[0_8px_18px_rgba(245,158,11,0.12)]'\r\n \"\r\n [disabled]=\"!canExecute()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"onExecute($event)\"\r\n >\r\n <span\r\n class=\"pointer-events-none absolute inset-0 flex items-center justify-center transition-[opacity,transform] duration-200 group-hover:scale-90 group-hover:opacity-0\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon icon=\"general.zap-fast\" class=\"[&_svg]:size-[18px]\" />\r\n </span>\r\n <span\r\n class=\"pointer-events-none flex items-center gap-2 whitespace-nowrap text-[13px] font-semibold opacity-0 translate-x-2 transition-[opacity,transform] duration-200 group-hover:translate-x-0 group-hover:opacity-100\"\r\n >\r\n <mt-icon icon=\"media.play\" class=\"[&_svg]:size-4\" />\r\n {{ executeLabel() }}\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <div\r\n class=\"pointer-events-none absolute -top-[44px] left-1/2 z-[6] flex -translate-x-1/2 items-center gap-1.5 opacity-0 transition-opacity duration-150 before:absolute before:inset-x-0 before:top-full before:h-5 before:content-[''] group-hover:pointer-events-auto group-hover:opacity-100\"\r\n >\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.plus\"\r\n [tooltip]=\"outletLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onAddFirstStep($event)\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"trigger().enabled ? 'general.check-circle' : 'general.x-close'\"\r\n [tooltip]=\"enabledLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onToggleEnabled($event)\"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"'flowplus.trigger.node.configure' | transloco\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"\r\n $event.stopPropagation();\r\n configure.emit({ triggerId: trigger().triggerId })\r\n \"\r\n />\r\n <mt-button\r\n variant=\"outlined\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"deleteLabel()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (onClick)=\"onRemove($event)\"\r\n />\r\n </div>\r\n\r\n @if (isRunning()) {\r\n <span\r\n class=\"pointer-events-none absolute inset-[5px] rounded-l-[38px] rounded-r-[6px] border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_18%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_6%,var(--p-content-background))] shadow-[inset_0_0_22px_color-mix(in_srgb,rgb(var(--fp-app-action))_12%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n <span\r\n class=\"pointer-events-none absolute left-1/2 top-1/2 z-[1] h-12 w-12 -translate-x-1/2 -translate-y-1/2 rounded-full border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_20%,transparent)] bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_5%,transparent)] shadow-[0_0_0_7px_color-mix(in_srgb,rgb(var(--fp-app-action))_7%,transparent)]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n }\r\n\r\n <fp-automation-node-icon\r\n type=\"Trigger\"\r\n [itemKey]=\"trigger().type\"\r\n [displayName]=\"trigger().name\"\r\n class=\"fp-canvas-trigger-icon relative z-[2] transition-colors duration-200\"\r\n [style.color]=\"triggerAccent().color\"\r\n />\r\n\r\n @if (issueCount() > 0) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 bottom-1.5 z-[4] inline-flex h-5 w-5 cursor-help items-center justify-center outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.color]=\"\r\n issueSeverity() === 'error'\r\n ? 'rgb(var(--fp-error))'\r\n : issueSeverity() === 'warning'\r\n ? 'rgb(var(--fp-warning))'\r\n : 'var(--p-primary-color)'\r\n \"\r\n tabindex=\"0\"\r\n role=\"img\"\r\n [attr.aria-label]=\"issueTooltip()\"\r\n [mtTooltip]=\"issueTooltip()\"\r\n [tooltipStyleClass]=\"issueTooltipClass()\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"\r\n $event.stopPropagation();\r\n configure.emit({ triggerId: trigger().triggerId })\r\n \"\r\n (dblclick)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (isRunning()) {\r\n <span\r\n class=\"pointer-events-auto absolute bottom-1.5 right-1.5 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center rounded-full border bg-(--p-content-background) shadow-[0_0_0_3px_color-mix(in_srgb,rgb(var(--fp-app-action))_10%,transparent)] outline-none focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n [style.borderColor]=\"runtimeColor()\"\r\n [style.color]=\"runtimeColor()\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <span\r\n class=\"block h-2.5 w-2.5 rounded-full border-[1.5px] border-[color-mix(in_srgb,rgb(var(--fp-app-action))_26%,transparent)] border-t-[rgb(var(--fp-app-action))] animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n </span>\r\n } @else if (isCompleted()) {\r\n <span\r\n class=\"pointer-events-auto absolute right-1 bottom-1 z-[3] inline-flex h-[18px] w-[18px] cursor-help items-center justify-center text-[rgb(var(--fp-commit))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1 rtl:right-auto\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"general.check\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n } @else if (isFailed()) {\r\n <span\r\n class=\"fp-node-badge pointer-events-auto absolute right-1.5 bottom-1.5 z-[3] inline-flex h-5 w-5 cursor-help items-center justify-center text-[rgb(var(--fp-error))] outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-(--p-primary-color) rtl:left-1.5 rtl:right-auto\"\r\n tabindex=\"0\"\r\n role=\"status\"\r\n [attr.aria-label]=\"runtimeTooltip()\"\r\n [mtTooltip]=\"runtimeTooltip()\"\r\n tooltipStyleClass=\"fp-runtime-tooltip\"\r\n tooltipPosition=\"top\"\r\n appendTo=\"body\"\r\n [escape]=\"true\"\r\n [showDelay]=\"180\"\r\n [hideDelay]=\"80\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n >\r\n <mt-icon icon=\"alert.alert-triangle\" class=\"[&_svg]:size-5\" />\r\n </span>\r\n }\r\n\r\n <div\r\n #triggerOutputRef\r\n fNodeOutput\r\n [fOutputId]=\"outputId()\"\r\n [fOutputConnectableSide]=\"'right'\"\r\n [fOutputMultiple]=\"true\"\r\n class=\"fp-connect-output absolute top-1/2 -right-2 z-[2] flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-full border-[1.5px] border-[rgb(var(--fp-border-strong))] bg-[rgb(var(--fp-surface-2))] transition-[box-shadow,background-color,border-color] duration-200 ease-out rtl:-left-2 rtl:right-auto\"\r\n [attr.aria-label]=\"outletLabel()\"\r\n [title]=\"outletLabel()\"\r\n ></div>\r\n\r\n @if (noFirstStep()) {\r\n <button\r\n type=\"button\"\r\n class=\"fp-output-proxy-plus absolute top-1/2 -right-[72px] z-[1] inline-flex h-6 w-6 -translate-y-1/2 cursor-copy items-center justify-center rounded-[7px] border border-(--p-content-border-color) bg-(--p-content-background) text-(--p-text-muted-color) shadow-sm transition-[opacity,border-color,color,background-color,box-shadow] duration-200 ease-out before:absolute before:top-1/2 before:right-full before:h-0.5 before:w-12 before:-translate-y-1/2 before:bg-[rgb(var(--fp-connector))] before:transition-colors before:duration-200 hover:border-(--p-primary-color) hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,var(--p-content-background))] hover:text-(--p-primary-color) hover:shadow-md hover:before:bg-(--p-primary-color) rtl:-left-[72px] rtl:right-auto rtl:before:right-auto rtl:before:left-full\"\r\n [attr.aria-label]=\"outletLabel()\"\r\n [title]=\"outletLabel()\"\r\n (mousedown)=\"onProxyConnectMouseDown($event, triggerOutputRef)\"\r\n (touchstart)=\"onProxyConnectTouchStart($event, triggerOutputRef)\"\r\n (click)=\"onAddFirstStep($event)\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"text-[13px] leading-none\" />\r\n </button>\r\n }\r\n\r\n <div\r\n class=\"absolute left-1/2 top-[calc(100%+8px)] w-[148px] -translate-x-1/2 text-center leading-tight\"\r\n >\r\n <div\r\n class=\"line-clamp-2 text-[12px] font-semibold text-(--p-text-color)\"\r\n [title]=\"title()\"\r\n >\r\n {{ title() }}\r\n </div>\r\n @if (isRunning() || isCompleted() || isFailed()) {\r\n <div\r\n class=\"mt-0.5 flex max-w-full items-center justify-center gap-1 text-[10.5px] font-semibold leading-tight\"\r\n [style.color]=\"runtimeColor()\"\r\n [title]=\"runtimeStatusLabel()\"\r\n >\r\n @if (isRunning()) {\r\n <span\r\n class=\"block h-2 w-2 shrink-0 rounded-full border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_28%,transparent)] border-t-[rgb(var(--fp-app-action))] animate-[fp-loading-spin_760ms_linear_infinite]\"\r\n aria-hidden=\"true\"\r\n ></span>\r\n } @else if (isCompleted()) {\r\n <mt-icon icon=\"general.check\" class=\"shrink-0 [&_svg]:size-3\" />\r\n } @else {\r\n <mt-icon\r\n icon=\"alert.alert-triangle\"\r\n class=\"shrink-0 [&_svg]:size-3\"\r\n />\r\n }\r\n <span class=\"truncate\">{{ runtimeStatusLabel() }}</span>\r\n </div>\r\n } @else if (runtimeStatus()) {\r\n <div\r\n class=\"mt-0.5 truncate text-[10.5px] font-semibold\"\r\n [style.color]=\"runtimeColor()\"\r\n >\r\n {{ runtimeStatus() }}\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n" }]
|
|
29760
29763
|
}], propDecorators: { trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: true }] }], noFirstStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "noFirstStep", required: false }] }], addFirstStep: [{
|
|
29761
29764
|
type: Output
|
|
29762
29765
|
}], configure: [{
|
|
@@ -31696,7 +31699,7 @@ class PaletteComponent {
|
|
|
31696
31699
|
return v && v !== key ? v : fallback;
|
|
31697
31700
|
}
|
|
31698
31701
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: PaletteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
31699
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: PaletteComponent, isStandalone: true, selector: "fp-palette", outputs: { closeRequested: "closeRequested", openRequested: "openRequested", add: "add", addTrigger: "addTrigger", addNote: "addNote", openVariables: "openVariables", openExecutionFiles: "openExecutionFiles" }, host: { listeners: { "document:pointerdown": "onDocumentPointerDown($event)" }, classAttribute: "block absolute top-0 bottom-0 start-0 w-11 z-[5] overflow-visible bg-(--p-surface-200) dark:bg-(--p-surface-800) border-e border-(--p-content-border-color)" }, ngImport: i0, template: "<div\r\n class=\"flex h-full w-11 flex-col items-center gap-4 bg-transparent py-3\"\n role=\"toolbar\"\r\n [attr.aria-label]=\"'flowplus.palette.rail' | transloco\"\r\n>\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"isExpanded() ? 'general.x-close' : 'general.plus'\"\r\n [styleClass]=\"\r\n isExpanded()\r\n ? 'fp-palette-rail-button is-active'\r\n : 'fp-palette-rail-button'\r\n \"\r\n [attr.aria-label]=\"'flowplus.palette.toggle' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.toggle' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"isExpanded() ? closeDrawer() : openDrawer()\"\r\n />\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.search-md\"\r\n [styleClass]=\"\r\n isExpanded()\r\n ? 'fp-palette-rail-button is-active'\r\n : 'fp-palette-rail-button'\r\n \"\r\n [attr.aria-label]=\"'flowplus.palette.search' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.search' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"openDrawer()\"\r\n />\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"dev.variable\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.palette.variables' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.variables' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"openWorkflowVariables()\"\r\n />\r\n\r\n <div class=\"relative\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"file.file-check-02\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.palette.executionFiles' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.executionFiles' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"requestExecutionFiles()\"\r\n />\r\n @if (executionFileCount() > 0) {\r\n <span\r\n class=\"pointer-events-none absolute -end-0.5 -top-0.5 grid min-w-4 place-items-center rounded-full bg-(--p-primary-color) px-1 text-[9.5px] font-bold leading-4 text-(--p-primary-contrast-color)\"\r\n >\r\n {{ executionFileBadge() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"communication.annotation-plus\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.canvas.addNote' | transloco\"\r\n [mtTooltip]=\"'flowplus.canvas.addNote' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"addCanvasNote()\"\r\n />\r\n</div>\r\n\r\n@if (isExpanded()) {\r\n <aside\r\n class=\"absolute start-11 top-0 bottom-10 z-[6] flex w-80 min-h-0 flex-col overflow-hidden border-e border-(--p-content-border-color) bg-(--p-surface-50) shadow-lg animate-[fp-palette-slide-in_280ms_cubic-bezier(0,0,0.2,1)_both] rtl:animate-[fp-palette-slide-in-rtl_280ms_cubic-bezier(0,0,0.2,1)_both]\"\r\n role=\"complementary\"\r\n [attr.aria-label]=\"'flowplus.palette.title' | transloco\"\r\n >\r\n <header class=\"flex flex-col gap-2.5 px-3 pt-3 pb-2.5\">\r\n <div class=\"flex items-center gap-2\">\r\n @if (!searching() && view() !== \"root\") {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"arrow.chevron-left\"\r\n styleClass=\"fp-palette-back-button\"\r\n (onClick)=\"back()\"\r\n [attr.aria-label]=\"'flowplus.palette.back' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.back' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n />\r\n }\r\n <h3\r\n class=\"min-w-0 flex-1 truncate text-[13.5px] font-bold text-(--p-text-color)\"\r\n >\r\n {{ headerTitle() }}\r\n </h3>\r\n </div>\r\n\r\n <mt-text-field\r\n [(ngModel)]=\"searchModel\"\r\n (ngModelChange)=\"onSearchChange($event)\"\r\n [placeholder]=\"'flowplus.palette.search' | transloco\"\r\n icon=\"general.search-md\"\r\n />\r\n </header>\r\n\r\n <div\r\n class=\"fp-scroll fp-pal-list flex flex-1 flex-col gap-0.5 overflow-y-auto px-2.5 pt-1 pb-6\"\r\n role=\"list\"\r\n >\r\n @if (searching() && view() !== \"triggers\") {\r\n @for (item of searchResults(); track paletteItemKey(item, $index)) {\r\n <div\r\n fExternalItem\r\n [fData]=\"item\"\r\n [fExternalItemId]=\"paletteItemKey(item, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"itemDescription(item) || resolve(item.displayName)\"\r\n (click)=\"pickStep(item)\"\r\n (keydown.enter)=\"pickStep(item)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n stepDragPreview;\r\n context: { $implicit: item }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ resolve(item.displayName) }}\r\n </div>\r\n <div\r\n class=\"line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </div>\r\n </div>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n @if (searchResults().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n }\r\n } @else if (view() === \"triggers\") {\r\n @for (option of visibleTriggerOptions(); track option.key) {\r\n <div\r\n fExternalItem\r\n [fData]=\"triggerPaletteItem(option)\"\r\n [fExternalItemId]=\"triggerPaletteItemKey(option, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 text-start transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"\r\n resolve(option.description) || resolve(option.displayName)\r\n \"\r\n (click)=\"pickTrigger(option)\"\r\n (keydown.enter)=\"pickTrigger(option)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n triggerDragPreview;\r\n context: { $implicit: option }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"triggerOptionVars(option).color\"\r\n [style.--fp-avatar-bg]=\"triggerOptionVars(option).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n type=\"Trigger\"\r\n [itemKey]=\"triggerTypeFor(option)\"\r\n [displayName]=\"option.displayName\"\r\n [description]=\"option.description\"\r\n [metadata]=\"option.metadata\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span\r\n class=\"block truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ resolve(option.displayName) }}</span\r\n >\r\n @if (option.description) {\r\n <span\r\n class=\"block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >{{ resolve(option.description) }}</span\r\n >\r\n }\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n @if (visibleTriggerOptions().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n }\r\n } @else if (view() === \"category\") {\r\n @for (section of activeSections(); track section.key) {\r\n <section\r\n class=\"flex flex-col gap-0.5\"\r\n role=\"group\"\r\n [attr.aria-label]=\"section.label\"\r\n >\r\n <header\r\n class=\"px-2.5 pb-1 pt-2 text-[11px] font-bold uppercase tracking-[0.04em] text-(--p-text-muted-color)\"\r\n >\r\n {{ section.label }}\r\n </header>\r\n @for (item of section.items; track paletteItemKey(item, $index)) {\r\n <div\r\n fExternalItem\r\n [fData]=\"item\"\r\n [fExternalItemId]=\"paletteItemKey(item, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"\r\n itemDescription(item) || resolve(item.displayName)\r\n \"\r\n (click)=\"pickStep(item)\"\r\n (keydown.enter)=\"pickStep(item)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n stepDragPreview;\r\n context: { $implicit: item }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ resolve(item.displayName) }}\r\n </div>\r\n <div\r\n class=\"line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </div>\r\n </div>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n }\r\n } @else {\r\n @for (group of groups(); track group.key) {\r\n <div\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2 text-start transition-colors hover:bg-(--p-surface-100) focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n [attr.title]=\"group.previewLabel || group.description\"\r\n (click)=\"openCategory(group.key)\"\r\n (keydown.enter)=\"openCategory(group.key)\"\r\n (keydown.space)=\"$event.preventDefault(); openCategory(group.key)\"\r\n >\r\n <span\r\n class=\"grid h-10 w-10 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"group.color\"\r\n [style.--fp-avatar-bg]=\"group.bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[31px]\"\r\n [visualKey]=\"group.visualKey\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"flex min-w-0 items-center gap-2\">\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ group.label }}</span\r\n >\r\n <span\r\n class=\"flex-none rounded-full bg-(--p-surface-100) px-2 py-0.5 text-[10.5px] font-semibold leading-4 text-(--p-text-muted-color)\"\r\n >{{ group.countLabel }}</span\r\n >\r\n </span>\r\n <span\r\n class=\"mt-0.5 block truncate text-[11.5px] leading-[1.35] text-(--p-text-muted-color)\"\r\n >{{ group.description }}</span\r\n >\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color) [&_svg]:size-4 rtl:rotate-180\"\r\n icon=\"arrow.chevron-right\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (groups().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n } @else {\r\n <div class=\"mx-2.5 my-1.5 h-px bg-(--p-content-border-color)\"></div>\r\n <div\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2.5 text-start transition-colors hover:bg-(--p-surface-100) focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n (click)=\"openTriggers()\"\r\n (keydown.enter)=\"openTriggers()\"\r\n (keydown.space)=\"$event.preventDefault(); openTriggers()\"\r\n >\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"triggerVars().color\"\r\n [style.--fp-avatar-bg]=\"triggerVars().bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n visualKey=\"trigger:manual\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span\r\n class=\"block truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ \"flowplus.palette.addAnotherTrigger\" | transloco }}</span\r\n >\r\n <span\r\n class=\"block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >{{\r\n \"flowplus.palette.addAnotherTriggerDesc\" | transloco\r\n }}</span\r\n >\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color) [&_svg]:size-4 rtl:rotate-180\"\r\n icon=\"arrow.chevron-right\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </aside>\r\n}\r\n\r\n<ng-template #emptyRow>\r\n <div\r\n class=\"flex flex-col items-center justify-center px-6 py-8 text-center text-(--p-text-muted-color)\"\r\n >\r\n <p>{{ \"flowplus.common.empty\" | transloco }}</p>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #stepDragPreview let-item>\r\n <div\r\n class=\"pointer-events-none w-[280px] overflow-hidden rounded-xl border border-[color-mix(in_srgb,var(--fp-avatar-color)_28%,var(--p-content-border-color))] bg-(--p-content-background)/95 text-(--p-text-color) shadow-[0_18px_36px_rgba(15,23,42,0.18),0_2px_8px_rgba(15,23,42,0.08)] ring-1 ring-white/60 backdrop-blur-md dark:ring-white/10\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <div class=\"flex items-start gap-3 p-3\">\r\n <span\r\n class=\"grid h-12 w-12 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_24%,transparent)] [&_.p-avatar]:bg-transparent [&_.p-avatar]:text-inherit\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"block truncate text-[13.5px] font-bold leading-5\">\r\n {{ resolve(item.displayName) }}\r\n </span>\r\n <span\r\n class=\"mt-0.5 block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </span>\r\n </span>\r\n </div>\r\n <div\r\n class=\"flex items-center justify-between border-t border-(--p-content-border-color)/70 bg-[color-mix(in_srgb,var(--fp-avatar-color)_7%,transparent)] px-3 py-2\"\r\n >\r\n <span\r\n class=\"max-w-[150px] truncate text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n {{ itemGroupLabel(item) }}\r\n </span>\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-(--p-content-background) px-2 py-0.5 text-[10.5px] font-semibold text-[var(--fp-avatar-color)] shadow-sm\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"[&_svg]:size-3\" />\r\n Drop to add\r\n </span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #triggerDragPreview let-option>\r\n <div\r\n class=\"pointer-events-none w-[280px] overflow-hidden rounded-xl border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_30%,var(--p-content-border-color))] bg-(--p-content-background)/95 text-(--p-text-color) shadow-[0_18px_36px_rgba(245,158,11,0.20),0_2px_8px_rgba(15,23,42,0.08)] ring-1 ring-white/60 backdrop-blur-md dark:ring-white/10\"\r\n >\r\n <div class=\"flex items-start gap-3 p-3\">\r\n <span\r\n class=\"grid h-12 w-12 flex-none place-items-center rounded-xl bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_16%,transparent)] text-[rgb(var(--fp-app-action))] shadow-inner ring-1 ring-[color-mix(in_srgb,rgb(var(--fp-app-action))_24%,transparent)] [&_.p-avatar]:bg-transparent [&_.p-avatar]:text-inherit\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n type=\"Trigger\"\r\n [itemKey]=\"triggerTypeFor(option)\"\r\n [displayName]=\"option.displayName\"\r\n [description]=\"option.description\"\r\n [metadata]=\"option.metadata\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"block truncate text-[13.5px] font-bold leading-5\">\r\n {{ resolve(option.displayName) }}\r\n </span>\r\n @if (option.description) {\r\n <span\r\n class=\"mt-0.5 block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ resolve(option.description) }}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n <div\r\n class=\"flex items-center justify-between border-t border-(--p-content-border-color)/70 bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_8%,transparent)] px-3 py-2\"\r\n >\r\n <span\r\n class=\"max-w-[150px] truncate text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Trigger\r\n </span>\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-(--p-content-background) px-2 py-0.5 text-[10.5px] font-semibold text-[rgb(var(--fp-app-action))] shadow-sm\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"[&_svg]:size-3\" />\r\n Drop to add\r\n </span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "directive", type:
|
|
31702
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: PaletteComponent, isStandalone: true, selector: "fp-palette", outputs: { closeRequested: "closeRequested", openRequested: "openRequested", add: "add", addTrigger: "addTrigger", addNote: "addNote", openVariables: "openVariables", openExecutionFiles: "openExecutionFiles" }, host: { listeners: { "document:pointerdown": "onDocumentPointerDown($event)" }, classAttribute: "block absolute top-0 bottom-0 start-0 w-11 z-[5] overflow-visible bg-(--p-surface-200) dark:bg-(--p-surface-800) border-e border-(--p-content-border-color)" }, ngImport: i0, template: "<div\r\n class=\"flex h-full w-11 flex-col items-center gap-4 bg-transparent py-3\"\r\n role=\"toolbar\"\r\n [attr.aria-label]=\"'flowplus.palette.rail' | transloco\"\r\n>\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"isExpanded() ? 'general.x-close' : 'general.plus'\"\r\n [styleClass]=\"\r\n isExpanded()\r\n ? 'fp-palette-rail-button is-active'\r\n : 'fp-palette-rail-button'\r\n \"\r\n [attr.aria-label]=\"'flowplus.palette.toggle' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.toggle' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"isExpanded() ? closeDrawer() : openDrawer()\"\r\n />\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.search-md\"\r\n [styleClass]=\"\r\n isExpanded()\r\n ? 'fp-palette-rail-button is-active'\r\n : 'fp-palette-rail-button'\r\n \"\r\n [attr.aria-label]=\"'flowplus.palette.search' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.search' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"openDrawer()\"\r\n />\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"dev.variable\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.palette.variables' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.variables' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"openWorkflowVariables()\"\r\n />\r\n\r\n <div class=\"relative\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"file.file-check-02\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.palette.executionFiles' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.executionFiles' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"requestExecutionFiles()\"\r\n />\r\n @if (executionFileCount() > 0) {\r\n <span\r\n class=\"pointer-events-none absolute -end-0.5 -top-0.5 grid min-w-4 place-items-center rounded-full bg-(--p-primary-color) px-1 text-[9.5px] font-bold leading-4 text-(--p-primary-contrast-color)\"\r\n >\r\n {{ executionFileBadge() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"communication.annotation-plus\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.canvas.addNote' | transloco\"\r\n [mtTooltip]=\"'flowplus.canvas.addNote' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"addCanvasNote()\"\r\n />\r\n</div>\r\n\r\n@if (isExpanded()) {\r\n <aside\r\n class=\"absolute start-11 top-0 bottom-10 z-[6] flex w-80 min-h-0 flex-col overflow-hidden border-e border-(--p-content-border-color) bg-(--p-surface-50) shadow-lg animate-[fp-palette-slide-in_280ms_cubic-bezier(0,0,0.2,1)_both] rtl:animate-[fp-palette-slide-in-rtl_280ms_cubic-bezier(0,0,0.2,1)_both]\"\r\n role=\"complementary\"\r\n [attr.aria-label]=\"'flowplus.palette.title' | transloco\"\r\n >\r\n <header class=\"flex flex-col gap-2.5 px-3 pt-3 pb-2.5\">\r\n <div class=\"flex items-center gap-2\">\r\n @if (!searching() && view() !== \"root\") {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"arrow.chevron-left\"\r\n styleClass=\"fp-palette-back-button\"\r\n (onClick)=\"back()\"\r\n [attr.aria-label]=\"'flowplus.palette.back' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.back' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n />\r\n }\r\n <h3\r\n class=\"min-w-0 flex-1 truncate text-[13.5px] font-bold text-(--p-text-color)\"\r\n >\r\n {{ headerTitle() }}\r\n </h3>\r\n </div>\r\n\r\n <mt-text-field\r\n [(ngModel)]=\"searchModel\"\r\n (ngModelChange)=\"onSearchChange($event)\"\r\n [placeholder]=\"'flowplus.palette.search' | transloco\"\r\n icon=\"general.search-md\"\r\n />\r\n </header>\r\n\r\n <div\r\n class=\"fp-scroll fp-pal-list flex flex-1 flex-col gap-0.5 overflow-y-auto px-2.5 pt-1 pb-6\"\r\n role=\"list\"\r\n >\r\n @if (searching() && view() !== \"triggers\") {\r\n @for (item of searchResults(); track paletteItemKey(item, $index)) {\r\n <div\r\n fExternalItem\r\n [fData]=\"item\"\r\n [fExternalItemId]=\"paletteItemKey(item, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"itemDescription(item) || resolve(item.displayName)\"\r\n (click)=\"pickStep(item)\"\r\n (keydown.enter)=\"pickStep(item)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n stepDragPreview;\r\n context: { $implicit: item }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ resolve(item.displayName) }}\r\n </div>\r\n <div\r\n class=\"line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </div>\r\n </div>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n @if (searchResults().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n }\r\n } @else if (view() === \"triggers\") {\r\n @for (option of visibleTriggerOptions(); track option.key) {\r\n <div\r\n fExternalItem\r\n [fData]=\"triggerPaletteItem(option)\"\r\n [fExternalItemId]=\"triggerPaletteItemKey(option, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 text-start transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"\r\n resolve(option.description) || resolve(option.displayName)\r\n \"\r\n (click)=\"pickTrigger(option)\"\r\n (keydown.enter)=\"pickTrigger(option)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n triggerDragPreview;\r\n context: { $implicit: option }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"triggerOptionVars(option).color\"\r\n [style.--fp-avatar-bg]=\"triggerOptionVars(option).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n type=\"Trigger\"\r\n [itemKey]=\"triggerTypeFor(option)\"\r\n [displayName]=\"option.displayName\"\r\n [description]=\"option.description\"\r\n [metadata]=\"option.metadata\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span\r\n class=\"block truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ resolve(option.displayName) }}</span\r\n >\r\n @if (option.description) {\r\n <span\r\n class=\"block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >{{ resolve(option.description) }}</span\r\n >\r\n }\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n @if (visibleTriggerOptions().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n }\r\n } @else if (view() === \"category\") {\r\n @for (section of activeSections(); track section.key) {\r\n <section\r\n class=\"flex flex-col gap-0.5\"\r\n role=\"group\"\r\n [attr.aria-label]=\"section.label\"\r\n >\r\n <header\r\n class=\"px-2.5 pb-1 pt-2 text-[11px] font-bold uppercase tracking-[0.04em] text-(--p-text-muted-color)\"\r\n >\r\n {{ section.label }}\r\n </header>\r\n @for (item of section.items; track paletteItemKey(item, $index)) {\r\n <div\r\n fExternalItem\r\n [fData]=\"item\"\r\n [fExternalItemId]=\"paletteItemKey(item, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"\r\n itemDescription(item) || resolve(item.displayName)\r\n \"\r\n (click)=\"pickStep(item)\"\r\n (keydown.enter)=\"pickStep(item)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n stepDragPreview;\r\n context: { $implicit: item }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ resolve(item.displayName) }}\r\n </div>\r\n <div\r\n class=\"line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </div>\r\n </div>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n }\r\n } @else {\r\n @for (group of groups(); track group.key) {\r\n <div\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2 text-start transition-colors hover:bg-(--p-surface-100) focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n [attr.title]=\"group.previewLabel || group.description\"\r\n (click)=\"openCategory(group.key)\"\r\n (keydown.enter)=\"openCategory(group.key)\"\r\n (keydown.space)=\"$event.preventDefault(); openCategory(group.key)\"\r\n >\r\n <span\r\n class=\"grid h-10 w-10 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"group.color\"\r\n [style.--fp-avatar-bg]=\"group.bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[31px]\"\r\n [visualKey]=\"group.visualKey\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"flex min-w-0 items-center gap-2\">\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ group.label }}</span\r\n >\r\n <span\r\n class=\"flex-none rounded-full bg-(--p-surface-100) px-2 py-0.5 text-[10.5px] font-semibold leading-4 text-(--p-text-muted-color)\"\r\n >{{ group.countLabel }}</span\r\n >\r\n </span>\r\n <span\r\n class=\"mt-0.5 block truncate text-[11.5px] leading-[1.35] text-(--p-text-muted-color)\"\r\n >{{ group.description }}</span\r\n >\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color) [&_svg]:size-4 rtl:rotate-180\"\r\n icon=\"arrow.chevron-right\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (groups().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n } @else {\r\n <div class=\"mx-2.5 my-1.5 h-px bg-(--p-content-border-color)\"></div>\r\n <div\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2.5 text-start transition-colors hover:bg-(--p-surface-100) focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n (click)=\"openTriggers()\"\r\n (keydown.enter)=\"openTriggers()\"\r\n (keydown.space)=\"$event.preventDefault(); openTriggers()\"\r\n >\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"triggerVars().color\"\r\n [style.--fp-avatar-bg]=\"triggerVars().bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n visualKey=\"trigger:manual\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span\r\n class=\"block truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ \"flowplus.palette.addAnotherTrigger\" | transloco }}</span\r\n >\r\n <span\r\n class=\"block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >{{\r\n \"flowplus.palette.addAnotherTriggerDesc\" | transloco\r\n }}</span\r\n >\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color) [&_svg]:size-4 rtl:rotate-180\"\r\n icon=\"arrow.chevron-right\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </aside>\r\n}\r\n\r\n<ng-template #emptyRow>\r\n <div\r\n class=\"flex flex-col items-center justify-center px-6 py-8 text-center text-(--p-text-muted-color)\"\r\n >\r\n <p>{{ \"flowplus.common.empty\" | transloco }}</p>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #stepDragPreview let-item>\r\n <div\r\n class=\"pointer-events-none w-[280px] overflow-hidden rounded-xl border border-[color-mix(in_srgb,var(--fp-avatar-color)_28%,var(--p-content-border-color))] bg-(--p-content-background)/95 text-(--p-text-color) shadow-[0_18px_36px_rgba(15,23,42,0.18),0_2px_8px_rgba(15,23,42,0.08)] ring-1 ring-white/60 backdrop-blur-md dark:ring-white/10\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <div class=\"flex items-start gap-3 p-3\">\r\n <span\r\n class=\"grid h-12 w-12 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_24%,transparent)] [&_.p-avatar]:bg-transparent [&_.p-avatar]:text-inherit\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"block truncate text-[13.5px] font-bold leading-5\">\r\n {{ resolve(item.displayName) }}\r\n </span>\r\n <span\r\n class=\"mt-0.5 block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </span>\r\n </span>\r\n </div>\r\n <div\r\n class=\"flex items-center justify-between border-t border-(--p-content-border-color)/70 bg-[color-mix(in_srgb,var(--fp-avatar-color)_7%,transparent)] px-3 py-2\"\r\n >\r\n <span\r\n class=\"max-w-[150px] truncate text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n {{ itemGroupLabel(item) }}\r\n </span>\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-(--p-content-background) px-2 py-0.5 text-[10.5px] font-semibold text-[var(--fp-avatar-color)] shadow-sm\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"[&_svg]:size-3\" />\r\n Drop to add\r\n </span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #triggerDragPreview let-option>\r\n <div\r\n class=\"pointer-events-none w-[280px] overflow-hidden rounded-xl border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_30%,var(--p-content-border-color))] bg-(--p-content-background)/95 text-(--p-text-color) shadow-[0_18px_36px_rgba(245,158,11,0.20),0_2px_8px_rgba(15,23,42,0.08)] ring-1 ring-white/60 backdrop-blur-md dark:ring-white/10\"\r\n >\r\n <div class=\"flex items-start gap-3 p-3\">\r\n <span\r\n class=\"grid h-12 w-12 flex-none place-items-center rounded-xl bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_16%,transparent)] text-[rgb(var(--fp-app-action))] shadow-inner ring-1 ring-[color-mix(in_srgb,rgb(var(--fp-app-action))_24%,transparent)] [&_.p-avatar]:bg-transparent [&_.p-avatar]:text-inherit\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n type=\"Trigger\"\r\n [itemKey]=\"triggerTypeFor(option)\"\r\n [displayName]=\"option.displayName\"\r\n [description]=\"option.description\"\r\n [metadata]=\"option.metadata\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"block truncate text-[13.5px] font-bold leading-5\">\r\n {{ resolve(option.displayName) }}\r\n </span>\r\n @if (option.description) {\r\n <span\r\n class=\"mt-0.5 block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ resolve(option.description) }}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n <div\r\n class=\"flex items-center justify-between border-t border-(--p-content-border-color)/70 bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_8%,transparent)] px-3 py-2\"\r\n >\r\n <span\r\n class=\"max-w-[150px] truncate text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Trigger\r\n </span>\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-(--p-content-background) px-2 py-0.5 text-[10.5px] font-semibold text-[rgb(var(--fp-app-action))] shadow-sm\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"[&_svg]:size-3\" />\r\n Drop to add\r\n </span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: AutomationNodeIconComponent, selector: "fp-automation-node-icon", inputs: ["type", "itemKey", "icon", "iconKey", "category", "displayName", "description", "metadata", "defaultConfig", "visualKey"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "directive", type:
|
|
31700
31703
|
// Native Foblex external-item drag (clone preview, like the official
|
|
31701
31704
|
// add-node-from-palette example).
|
|
31702
31705
|
FExternalItem, selector: "[fExternalItem]", inputs: ["fExternalItemId", "fData", "fDisabled", "fPreview", "fPreviewMatchSize", "fPlaceholder"], outputs: ["fPreviewChange", "fPlaceholderChange"] }, { kind: "directive", type: FExternalItemPreview, selector: "ng-template[fExternalItemPreview]" }, { kind: "pipe", type: i2.TranslocoPipe, name: "transloco" }] });
|
|
@@ -31718,7 +31721,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
31718
31721
|
FExternalItemPreview,
|
|
31719
31722
|
], host: {
|
|
31720
31723
|
class: 'block absolute top-0 bottom-0 start-0 w-11 z-[5] overflow-visible bg-(--p-surface-200) dark:bg-(--p-surface-800) border-e border-(--p-content-border-color)',
|
|
31721
|
-
}, template: "<div\r\n class=\"flex h-full w-11 flex-col items-center gap-4 bg-transparent py-3\"\n role=\"toolbar\"\r\n [attr.aria-label]=\"'flowplus.palette.rail' | transloco\"\r\n>\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"isExpanded() ? 'general.x-close' : 'general.plus'\"\r\n [styleClass]=\"\r\n isExpanded()\r\n ? 'fp-palette-rail-button is-active'\r\n : 'fp-palette-rail-button'\r\n \"\r\n [attr.aria-label]=\"'flowplus.palette.toggle' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.toggle' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"isExpanded() ? closeDrawer() : openDrawer()\"\r\n />\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.search-md\"\r\n [styleClass]=\"\r\n isExpanded()\r\n ? 'fp-palette-rail-button is-active'\r\n : 'fp-palette-rail-button'\r\n \"\r\n [attr.aria-label]=\"'flowplus.palette.search' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.search' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"openDrawer()\"\r\n />\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"dev.variable\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.palette.variables' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.variables' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"openWorkflowVariables()\"\r\n />\r\n\r\n <div class=\"relative\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"file.file-check-02\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.palette.executionFiles' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.executionFiles' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"requestExecutionFiles()\"\r\n />\r\n @if (executionFileCount() > 0) {\r\n <span\r\n class=\"pointer-events-none absolute -end-0.5 -top-0.5 grid min-w-4 place-items-center rounded-full bg-(--p-primary-color) px-1 text-[9.5px] font-bold leading-4 text-(--p-primary-contrast-color)\"\r\n >\r\n {{ executionFileBadge() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"communication.annotation-plus\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.canvas.addNote' | transloco\"\r\n [mtTooltip]=\"'flowplus.canvas.addNote' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"addCanvasNote()\"\r\n />\r\n</div>\r\n\r\n@if (isExpanded()) {\r\n <aside\r\n class=\"absolute start-11 top-0 bottom-10 z-[6] flex w-80 min-h-0 flex-col overflow-hidden border-e border-(--p-content-border-color) bg-(--p-surface-50) shadow-lg animate-[fp-palette-slide-in_280ms_cubic-bezier(0,0,0.2,1)_both] rtl:animate-[fp-palette-slide-in-rtl_280ms_cubic-bezier(0,0,0.2,1)_both]\"\r\n role=\"complementary\"\r\n [attr.aria-label]=\"'flowplus.palette.title' | transloco\"\r\n >\r\n <header class=\"flex flex-col gap-2.5 px-3 pt-3 pb-2.5\">\r\n <div class=\"flex items-center gap-2\">\r\n @if (!searching() && view() !== \"root\") {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"arrow.chevron-left\"\r\n styleClass=\"fp-palette-back-button\"\r\n (onClick)=\"back()\"\r\n [attr.aria-label]=\"'flowplus.palette.back' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.back' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n />\r\n }\r\n <h3\r\n class=\"min-w-0 flex-1 truncate text-[13.5px] font-bold text-(--p-text-color)\"\r\n >\r\n {{ headerTitle() }}\r\n </h3>\r\n </div>\r\n\r\n <mt-text-field\r\n [(ngModel)]=\"searchModel\"\r\n (ngModelChange)=\"onSearchChange($event)\"\r\n [placeholder]=\"'flowplus.palette.search' | transloco\"\r\n icon=\"general.search-md\"\r\n />\r\n </header>\r\n\r\n <div\r\n class=\"fp-scroll fp-pal-list flex flex-1 flex-col gap-0.5 overflow-y-auto px-2.5 pt-1 pb-6\"\r\n role=\"list\"\r\n >\r\n @if (searching() && view() !== \"triggers\") {\r\n @for (item of searchResults(); track paletteItemKey(item, $index)) {\r\n <div\r\n fExternalItem\r\n [fData]=\"item\"\r\n [fExternalItemId]=\"paletteItemKey(item, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"itemDescription(item) || resolve(item.displayName)\"\r\n (click)=\"pickStep(item)\"\r\n (keydown.enter)=\"pickStep(item)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n stepDragPreview;\r\n context: { $implicit: item }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ resolve(item.displayName) }}\r\n </div>\r\n <div\r\n class=\"line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </div>\r\n </div>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n @if (searchResults().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n }\r\n } @else if (view() === \"triggers\") {\r\n @for (option of visibleTriggerOptions(); track option.key) {\r\n <div\r\n fExternalItem\r\n [fData]=\"triggerPaletteItem(option)\"\r\n [fExternalItemId]=\"triggerPaletteItemKey(option, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 text-start transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"\r\n resolve(option.description) || resolve(option.displayName)\r\n \"\r\n (click)=\"pickTrigger(option)\"\r\n (keydown.enter)=\"pickTrigger(option)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n triggerDragPreview;\r\n context: { $implicit: option }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"triggerOptionVars(option).color\"\r\n [style.--fp-avatar-bg]=\"triggerOptionVars(option).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n type=\"Trigger\"\r\n [itemKey]=\"triggerTypeFor(option)\"\r\n [displayName]=\"option.displayName\"\r\n [description]=\"option.description\"\r\n [metadata]=\"option.metadata\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span\r\n class=\"block truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ resolve(option.displayName) }}</span\r\n >\r\n @if (option.description) {\r\n <span\r\n class=\"block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >{{ resolve(option.description) }}</span\r\n >\r\n }\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n @if (visibleTriggerOptions().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n }\r\n } @else if (view() === \"category\") {\r\n @for (section of activeSections(); track section.key) {\r\n <section\r\n class=\"flex flex-col gap-0.5\"\r\n role=\"group\"\r\n [attr.aria-label]=\"section.label\"\r\n >\r\n <header\r\n class=\"px-2.5 pb-1 pt-2 text-[11px] font-bold uppercase tracking-[0.04em] text-(--p-text-muted-color)\"\r\n >\r\n {{ section.label }}\r\n </header>\r\n @for (item of section.items; track paletteItemKey(item, $index)) {\r\n <div\r\n fExternalItem\r\n [fData]=\"item\"\r\n [fExternalItemId]=\"paletteItemKey(item, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"\r\n itemDescription(item) || resolve(item.displayName)\r\n \"\r\n (click)=\"pickStep(item)\"\r\n (keydown.enter)=\"pickStep(item)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n stepDragPreview;\r\n context: { $implicit: item }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ resolve(item.displayName) }}\r\n </div>\r\n <div\r\n class=\"line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </div>\r\n </div>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n }\r\n } @else {\r\n @for (group of groups(); track group.key) {\r\n <div\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2 text-start transition-colors hover:bg-(--p-surface-100) focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n [attr.title]=\"group.previewLabel || group.description\"\r\n (click)=\"openCategory(group.key)\"\r\n (keydown.enter)=\"openCategory(group.key)\"\r\n (keydown.space)=\"$event.preventDefault(); openCategory(group.key)\"\r\n >\r\n <span\r\n class=\"grid h-10 w-10 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"group.color\"\r\n [style.--fp-avatar-bg]=\"group.bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[31px]\"\r\n [visualKey]=\"group.visualKey\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"flex min-w-0 items-center gap-2\">\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ group.label }}</span\r\n >\r\n <span\r\n class=\"flex-none rounded-full bg-(--p-surface-100) px-2 py-0.5 text-[10.5px] font-semibold leading-4 text-(--p-text-muted-color)\"\r\n >{{ group.countLabel }}</span\r\n >\r\n </span>\r\n <span\r\n class=\"mt-0.5 block truncate text-[11.5px] leading-[1.35] text-(--p-text-muted-color)\"\r\n >{{ group.description }}</span\r\n >\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color) [&_svg]:size-4 rtl:rotate-180\"\r\n icon=\"arrow.chevron-right\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (groups().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n } @else {\r\n <div class=\"mx-2.5 my-1.5 h-px bg-(--p-content-border-color)\"></div>\r\n <div\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2.5 text-start transition-colors hover:bg-(--p-surface-100) focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n (click)=\"openTriggers()\"\r\n (keydown.enter)=\"openTriggers()\"\r\n (keydown.space)=\"$event.preventDefault(); openTriggers()\"\r\n >\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"triggerVars().color\"\r\n [style.--fp-avatar-bg]=\"triggerVars().bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n visualKey=\"trigger:manual\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span\r\n class=\"block truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ \"flowplus.palette.addAnotherTrigger\" | transloco }}</span\r\n >\r\n <span\r\n class=\"block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >{{\r\n \"flowplus.palette.addAnotherTriggerDesc\" | transloco\r\n }}</span\r\n >\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color) [&_svg]:size-4 rtl:rotate-180\"\r\n icon=\"arrow.chevron-right\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </aside>\r\n}\r\n\r\n<ng-template #emptyRow>\r\n <div\r\n class=\"flex flex-col items-center justify-center px-6 py-8 text-center text-(--p-text-muted-color)\"\r\n >\r\n <p>{{ \"flowplus.common.empty\" | transloco }}</p>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #stepDragPreview let-item>\r\n <div\r\n class=\"pointer-events-none w-[280px] overflow-hidden rounded-xl border border-[color-mix(in_srgb,var(--fp-avatar-color)_28%,var(--p-content-border-color))] bg-(--p-content-background)/95 text-(--p-text-color) shadow-[0_18px_36px_rgba(15,23,42,0.18),0_2px_8px_rgba(15,23,42,0.08)] ring-1 ring-white/60 backdrop-blur-md dark:ring-white/10\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <div class=\"flex items-start gap-3 p-3\">\r\n <span\r\n class=\"grid h-12 w-12 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_24%,transparent)] [&_.p-avatar]:bg-transparent [&_.p-avatar]:text-inherit\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"block truncate text-[13.5px] font-bold leading-5\">\r\n {{ resolve(item.displayName) }}\r\n </span>\r\n <span\r\n class=\"mt-0.5 block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </span>\r\n </span>\r\n </div>\r\n <div\r\n class=\"flex items-center justify-between border-t border-(--p-content-border-color)/70 bg-[color-mix(in_srgb,var(--fp-avatar-color)_7%,transparent)] px-3 py-2\"\r\n >\r\n <span\r\n class=\"max-w-[150px] truncate text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n {{ itemGroupLabel(item) }}\r\n </span>\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-(--p-content-background) px-2 py-0.5 text-[10.5px] font-semibold text-[var(--fp-avatar-color)] shadow-sm\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"[&_svg]:size-3\" />\r\n Drop to add\r\n </span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #triggerDragPreview let-option>\r\n <div\r\n class=\"pointer-events-none w-[280px] overflow-hidden rounded-xl border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_30%,var(--p-content-border-color))] bg-(--p-content-background)/95 text-(--p-text-color) shadow-[0_18px_36px_rgba(245,158,11,0.20),0_2px_8px_rgba(15,23,42,0.08)] ring-1 ring-white/60 backdrop-blur-md dark:ring-white/10\"\r\n >\r\n <div class=\"flex items-start gap-3 p-3\">\r\n <span\r\n class=\"grid h-12 w-12 flex-none place-items-center rounded-xl bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_16%,transparent)] text-[rgb(var(--fp-app-action))] shadow-inner ring-1 ring-[color-mix(in_srgb,rgb(var(--fp-app-action))_24%,transparent)] [&_.p-avatar]:bg-transparent [&_.p-avatar]:text-inherit\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n type=\"Trigger\"\r\n [itemKey]=\"triggerTypeFor(option)\"\r\n [displayName]=\"option.displayName\"\r\n [description]=\"option.description\"\r\n [metadata]=\"option.metadata\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"block truncate text-[13.5px] font-bold leading-5\">\r\n {{ resolve(option.displayName) }}\r\n </span>\r\n @if (option.description) {\r\n <span\r\n class=\"mt-0.5 block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ resolve(option.description) }}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n <div\r\n class=\"flex items-center justify-between border-t border-(--p-content-border-color)/70 bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_8%,transparent)] px-3 py-2\"\r\n >\r\n <span\r\n class=\"max-w-[150px] truncate text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Trigger\r\n </span>\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-(--p-content-background) px-2 py-0.5 text-[10.5px] font-semibold text-[rgb(var(--fp-app-action))] shadow-sm\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"[&_svg]:size-3\" />\r\n Drop to add\r\n </span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n" }]
|
|
31724
|
+
}, template: "<div\r\n class=\"flex h-full w-11 flex-col items-center gap-4 bg-transparent py-3\"\r\n role=\"toolbar\"\r\n [attr.aria-label]=\"'flowplus.palette.rail' | transloco\"\r\n>\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n [icon]=\"isExpanded() ? 'general.x-close' : 'general.plus'\"\r\n [styleClass]=\"\r\n isExpanded()\r\n ? 'fp-palette-rail-button is-active'\r\n : 'fp-palette-rail-button'\r\n \"\r\n [attr.aria-label]=\"'flowplus.palette.toggle' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.toggle' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"isExpanded() ? closeDrawer() : openDrawer()\"\r\n />\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"general.search-md\"\r\n [styleClass]=\"\r\n isExpanded()\r\n ? 'fp-palette-rail-button is-active'\r\n : 'fp-palette-rail-button'\r\n \"\r\n [attr.aria-label]=\"'flowplus.palette.search' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.search' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"openDrawer()\"\r\n />\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"dev.variable\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.palette.variables' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.variables' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"openWorkflowVariables()\"\r\n />\r\n\r\n <div class=\"relative\">\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"file.file-check-02\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.palette.executionFiles' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.executionFiles' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"requestExecutionFiles()\"\r\n />\r\n @if (executionFileCount() > 0) {\r\n <span\r\n class=\"pointer-events-none absolute -end-0.5 -top-0.5 grid min-w-4 place-items-center rounded-full bg-(--p-primary-color) px-1 text-[9.5px] font-bold leading-4 text-(--p-primary-contrast-color)\"\r\n >\r\n {{ executionFileBadge() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"communication.annotation-plus\"\r\n styleClass=\"fp-palette-rail-button\"\r\n [attr.aria-label]=\"'flowplus.canvas.addNote' | transloco\"\r\n [mtTooltip]=\"'flowplus.canvas.addNote' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n (onClick)=\"addCanvasNote()\"\r\n />\r\n</div>\r\n\r\n@if (isExpanded()) {\r\n <aside\r\n class=\"absolute start-11 top-0 bottom-10 z-[6] flex w-80 min-h-0 flex-col overflow-hidden border-e border-(--p-content-border-color) bg-(--p-surface-50) shadow-lg animate-[fp-palette-slide-in_280ms_cubic-bezier(0,0,0.2,1)_both] rtl:animate-[fp-palette-slide-in-rtl_280ms_cubic-bezier(0,0,0.2,1)_both]\"\r\n role=\"complementary\"\r\n [attr.aria-label]=\"'flowplus.palette.title' | transloco\"\r\n >\r\n <header class=\"flex flex-col gap-2.5 px-3 pt-3 pb-2.5\">\r\n <div class=\"flex items-center gap-2\">\r\n @if (!searching() && view() !== \"root\") {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n icon=\"arrow.chevron-left\"\r\n styleClass=\"fp-palette-back-button\"\r\n (onClick)=\"back()\"\r\n [attr.aria-label]=\"'flowplus.palette.back' | transloco\"\r\n [mtTooltip]=\"'flowplus.palette.back' | transloco\"\r\n tooltipPosition=\"right\"\r\n appendTo=\"body\"\r\n [showDelay]=\"220\"\r\n [hideDelay]=\"80\"\r\n />\r\n }\r\n <h3\r\n class=\"min-w-0 flex-1 truncate text-[13.5px] font-bold text-(--p-text-color)\"\r\n >\r\n {{ headerTitle() }}\r\n </h3>\r\n </div>\r\n\r\n <mt-text-field\r\n [(ngModel)]=\"searchModel\"\r\n (ngModelChange)=\"onSearchChange($event)\"\r\n [placeholder]=\"'flowplus.palette.search' | transloco\"\r\n icon=\"general.search-md\"\r\n />\r\n </header>\r\n\r\n <div\r\n class=\"fp-scroll fp-pal-list flex flex-1 flex-col gap-0.5 overflow-y-auto px-2.5 pt-1 pb-6\"\r\n role=\"list\"\r\n >\r\n @if (searching() && view() !== \"triggers\") {\r\n @for (item of searchResults(); track paletteItemKey(item, $index)) {\r\n <div\r\n fExternalItem\r\n [fData]=\"item\"\r\n [fExternalItemId]=\"paletteItemKey(item, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"itemDescription(item) || resolve(item.displayName)\"\r\n (click)=\"pickStep(item)\"\r\n (keydown.enter)=\"pickStep(item)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n stepDragPreview;\r\n context: { $implicit: item }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ resolve(item.displayName) }}\r\n </div>\r\n <div\r\n class=\"line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </div>\r\n </div>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n @if (searchResults().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n }\r\n } @else if (view() === \"triggers\") {\r\n @for (option of visibleTriggerOptions(); track option.key) {\r\n <div\r\n fExternalItem\r\n [fData]=\"triggerPaletteItem(option)\"\r\n [fExternalItemId]=\"triggerPaletteItemKey(option, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 text-start transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"\r\n resolve(option.description) || resolve(option.displayName)\r\n \"\r\n (click)=\"pickTrigger(option)\"\r\n (keydown.enter)=\"pickTrigger(option)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n triggerDragPreview;\r\n context: { $implicit: option }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"triggerOptionVars(option).color\"\r\n [style.--fp-avatar-bg]=\"triggerOptionVars(option).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n type=\"Trigger\"\r\n [itemKey]=\"triggerTypeFor(option)\"\r\n [displayName]=\"option.displayName\"\r\n [description]=\"option.description\"\r\n [metadata]=\"option.metadata\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span\r\n class=\"block truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ resolve(option.displayName) }}</span\r\n >\r\n @if (option.description) {\r\n <span\r\n class=\"block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >{{ resolve(option.description) }}</span\r\n >\r\n }\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n @if (visibleTriggerOptions().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n }\r\n } @else if (view() === \"category\") {\r\n @for (section of activeSections(); track section.key) {\r\n <section\r\n class=\"flex flex-col gap-0.5\"\r\n role=\"group\"\r\n [attr.aria-label]=\"section.label\"\r\n >\r\n <header\r\n class=\"px-2.5 pb-1 pt-2 text-[11px] font-bold uppercase tracking-[0.04em] text-(--p-text-muted-color)\"\r\n >\r\n {{ section.label }}\r\n </header>\r\n @for (item of section.items; track paletteItemKey(item, $index)) {\r\n <div\r\n fExternalItem\r\n [fData]=\"item\"\r\n [fExternalItemId]=\"paletteItemKey(item, $index)\"\r\n [fPreviewMatchSize]=\"false\"\r\n class=\"group/item !flex w-full cursor-grab touch-none select-none items-center gap-3 rounded-lg px-2.5 py-2.5 transition-colors hover:bg-(--p-surface-100) active:cursor-grabbing focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.title]=\"\r\n itemDescription(item) || resolve(item.displayName)\r\n \"\r\n (click)=\"pickStep(item)\"\r\n (keydown.enter)=\"pickStep(item)\"\r\n >\r\n <ng-template fExternalItemPreview>\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n stepDragPreview;\r\n context: { $implicit: item }\r\n \"\r\n />\r\n </ng-template>\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <div class=\"min-w-0 flex-1\">\r\n <div\r\n class=\"truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >\r\n {{ resolve(item.displayName) }}\r\n </div>\r\n <div\r\n class=\"line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </div>\r\n </div>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color)/40 transition-colors group-hover/item:text-(--p-text-muted-color) [&_svg]:size-4\"\r\n icon=\"general.dots-grid\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n }\r\n } @else {\r\n @for (group of groups(); track group.key) {\r\n <div\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2 text-start transition-colors hover:bg-(--p-surface-100) focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n [attr.title]=\"group.previewLabel || group.description\"\r\n (click)=\"openCategory(group.key)\"\r\n (keydown.enter)=\"openCategory(group.key)\"\r\n (keydown.space)=\"$event.preventDefault(); openCategory(group.key)\"\r\n >\r\n <span\r\n class=\"grid h-10 w-10 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"group.color\"\r\n [style.--fp-avatar-bg]=\"group.bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[31px]\"\r\n [visualKey]=\"group.visualKey\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"flex min-w-0 items-center gap-2\">\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ group.label }}</span\r\n >\r\n <span\r\n class=\"flex-none rounded-full bg-(--p-surface-100) px-2 py-0.5 text-[10.5px] font-semibold leading-4 text-(--p-text-muted-color)\"\r\n >{{ group.countLabel }}</span\r\n >\r\n </span>\r\n <span\r\n class=\"mt-0.5 block truncate text-[11.5px] leading-[1.35] text-(--p-text-muted-color)\"\r\n >{{ group.description }}</span\r\n >\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color) [&_svg]:size-4 rtl:rotate-180\"\r\n icon=\"arrow.chevron-right\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (groups().length === 0) {\r\n <ng-container *ngTemplateOutlet=\"emptyRow\" />\r\n } @else {\r\n <div class=\"mx-2.5 my-1.5 h-px bg-(--p-content-border-color)\"></div>\r\n <div\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2.5 text-start transition-colors hover:bg-(--p-surface-100) focus-visible:bg-(--p-surface-100) focus-visible:outline-none\"\r\n (click)=\"openTriggers()\"\r\n (keydown.enter)=\"openTriggers()\"\r\n (keydown.space)=\"$event.preventDefault(); openTriggers()\"\r\n >\r\n <span\r\n class=\"grid h-11 w-11 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_18%,transparent)]\"\r\n [style.--fp-avatar-color]=\"triggerVars().color\"\r\n [style.--fp-avatar-bg]=\"triggerVars().bg\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[34px]\"\r\n visualKey=\"trigger:manual\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span\r\n class=\"block truncate text-[13px] font-semibold text-(--p-text-color)\"\r\n >{{ \"flowplus.palette.addAnotherTrigger\" | transloco }}</span\r\n >\r\n <span\r\n class=\"block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >{{\r\n \"flowplus.palette.addAnotherTriggerDesc\" | transloco\r\n }}</span\r\n >\r\n </span>\r\n <mt-icon\r\n class=\"flex-none text-(--p-text-muted-color) [&_svg]:size-4 rtl:rotate-180\"\r\n icon=\"arrow.chevron-right\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </aside>\r\n}\r\n\r\n<ng-template #emptyRow>\r\n <div\r\n class=\"flex flex-col items-center justify-center px-6 py-8 text-center text-(--p-text-muted-color)\"\r\n >\r\n <p>{{ \"flowplus.common.empty\" | transloco }}</p>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #stepDragPreview let-item>\r\n <div\r\n class=\"pointer-events-none w-[280px] overflow-hidden rounded-xl border border-[color-mix(in_srgb,var(--fp-avatar-color)_28%,var(--p-content-border-color))] bg-(--p-content-background)/95 text-(--p-text-color) shadow-[0_18px_36px_rgba(15,23,42,0.18),0_2px_8px_rgba(15,23,42,0.08)] ring-1 ring-white/60 backdrop-blur-md dark:ring-white/10\"\r\n [style.--fp-avatar-color]=\"visualFor(item).color\"\r\n [style.--fp-avatar-bg]=\"visualFor(item).bg\"\r\n >\r\n <div class=\"flex items-start gap-3 p-3\">\r\n <span\r\n class=\"grid h-12 w-12 flex-none place-items-center rounded-xl bg-[var(--fp-avatar-bg)] text-[var(--fp-avatar-color)] shadow-inner ring-1 ring-[color-mix(in_srgb,var(--fp-avatar-color)_24%,transparent)] [&_.p-avatar]:bg-transparent [&_.p-avatar]:text-inherit\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n [type]=\"item.type\"\r\n [itemKey]=\"item.key\"\r\n [icon]=\"item.icon\"\r\n [category]=\"item.category\"\r\n [displayName]=\"item.displayName\"\r\n [description]=\"item.description\"\r\n [metadata]=\"item.metadata\"\r\n [defaultConfig]=\"item.defaultConfig\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"block truncate text-[13.5px] font-bold leading-5\">\r\n {{ resolve(item.displayName) }}\r\n </span>\r\n <span\r\n class=\"mt-0.5 block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ itemDescription(item) }}\r\n </span>\r\n </span>\r\n </div>\r\n <div\r\n class=\"flex items-center justify-between border-t border-(--p-content-border-color)/70 bg-[color-mix(in_srgb,var(--fp-avatar-color)_7%,transparent)] px-3 py-2\"\r\n >\r\n <span\r\n class=\"max-w-[150px] truncate text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n {{ itemGroupLabel(item) }}\r\n </span>\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-(--p-content-background) px-2 py-0.5 text-[10.5px] font-semibold text-[var(--fp-avatar-color)] shadow-sm\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"[&_svg]:size-3\" />\r\n Drop to add\r\n </span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #triggerDragPreview let-option>\r\n <div\r\n class=\"pointer-events-none w-[280px] overflow-hidden rounded-xl border border-[color-mix(in_srgb,rgb(var(--fp-app-action))_30%,var(--p-content-border-color))] bg-(--p-content-background)/95 text-(--p-text-color) shadow-[0_18px_36px_rgba(245,158,11,0.20),0_2px_8px_rgba(15,23,42,0.08)] ring-1 ring-white/60 backdrop-blur-md dark:ring-white/10\"\r\n >\r\n <div class=\"flex items-start gap-3 p-3\">\r\n <span\r\n class=\"grid h-12 w-12 flex-none place-items-center rounded-xl bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_16%,transparent)] text-[rgb(var(--fp-app-action))] shadow-inner ring-1 ring-[color-mix(in_srgb,rgb(var(--fp-app-action))_24%,transparent)] [&_.p-avatar]:bg-transparent [&_.p-avatar]:text-inherit\"\r\n >\r\n <fp-automation-node-icon\r\n class=\"text-[36px]\"\r\n type=\"Trigger\"\r\n [itemKey]=\"triggerTypeFor(option)\"\r\n [displayName]=\"option.displayName\"\r\n [description]=\"option.description\"\r\n [metadata]=\"option.metadata\"\r\n />\r\n </span>\r\n <span class=\"min-w-0 flex-1\">\r\n <span class=\"block truncate text-[13.5px] font-bold leading-5\">\r\n {{ resolve(option.displayName) }}\r\n </span>\r\n @if (option.description) {\r\n <span\r\n class=\"mt-0.5 block line-clamp-2 text-[11.5px] leading-[1.45] text-(--p-text-muted-color)\"\r\n >\r\n {{ resolve(option.description) }}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n <div\r\n class=\"flex items-center justify-between border-t border-(--p-content-border-color)/70 bg-[color-mix(in_srgb,rgb(var(--fp-app-action))_8%,transparent)] px-3 py-2\"\r\n >\r\n <span\r\n class=\"max-w-[150px] truncate text-[10.5px] font-semibold text-(--p-text-muted-color)\"\r\n >\r\n Trigger\r\n </span>\r\n <span\r\n class=\"inline-flex items-center gap-1 rounded-full bg-(--p-content-background) px-2 py-0.5 text-[10.5px] font-semibold text-[rgb(var(--fp-app-action))] shadow-sm\"\r\n >\r\n <mt-icon icon=\"general.plus\" class=\"[&_svg]:size-3\" />\r\n Drop to add\r\n </span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n" }]
|
|
31722
31725
|
}], ctorParameters: () => [], propDecorators: { onDocumentPointerDown: [{
|
|
31723
31726
|
type: HostListener,
|
|
31724
31727
|
args: ['document:pointerdown', ['$event']]
|