@mohamedatia/fly-design-system 2.3.2 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/mohamedatia-fly-design-system.mjs +1548 -127
- package/fesm2022/mohamedatia-fly-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scss/_business-app-buttons.scss +209 -208
- package/scss/_theme-auto.scss +10 -9
- package/scss/_theme-dark-vars.scss +50 -0
- package/scss/_theme-dark.scss +142 -79
- package/scss/_theme-light.scss +60 -47
- package/scss/_theme-spatial-vars.scss +56 -66
- package/scss/_theme-spatial.scss +23 -22
- package/scss/_tokens.scss +115 -126
- package/scss/apps.scss +886 -0
- package/types/mohamedatia-fly-design-system.d.ts +685 -5
- package/types/mohamedatia-fly-design-system.d.ts.map +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, Output, Input, forwardRef, Injector, viewChild, effect, afterNextRender, ViewEncapsulation, model } from '@angular/core';
|
|
2
|
+
import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, Output, Input, forwardRef, Injector, viewChild, effect, afterNextRender, ViewEncapsulation, model, Directive } from '@angular/core';
|
|
3
3
|
import * as i1$1 from '@angular/common';
|
|
4
|
-
import { isPlatformBrowser, CommonModule } from '@angular/common';
|
|
5
|
-
import { Router } from '@angular/router';
|
|
4
|
+
import { isPlatformBrowser, CommonModule, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
5
|
+
import { Router, NavigationEnd } from '@angular/router';
|
|
6
6
|
import { of, ReplaySubject, Subject } from 'rxjs';
|
|
7
7
|
import * as i1 from '@angular/forms';
|
|
8
8
|
import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
|
|
@@ -29,6 +29,22 @@ const WINDOW_DATA = new InjectionToken('WINDOW_DATA');
|
|
|
29
29
|
*/
|
|
30
30
|
const LAUNCH_CONTEXT = new InjectionToken('LAUNCH_CONTEXT');
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Generic share / ACL panel models — hosts map domain DTOs to these shapes.
|
|
34
|
+
*
|
|
35
|
+
* v2.0.0: introduces the {@link SharePrincipal} discriminated union to replace the v1
|
|
36
|
+
* "set-which-is-set" discriminator on {@link SharePermissionEntry} (`grantedToUserId` /
|
|
37
|
+
* `grantedToOuId` / `grantedToAppId`). The new shape compiles invalid combinations away
|
|
38
|
+
* (a permission row carries exactly one principal, never overlapping ids) and reserves
|
|
39
|
+
* a `role` kind for the planned apps-chart role-as-principal flow.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Stable identifiers for platform-managed system charts. Hosts that don't recognise the
|
|
43
|
+
* value just fall through to the default selection — these constants are advisory.
|
|
44
|
+
*/
|
|
45
|
+
const SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT = 'default-company';
|
|
46
|
+
const SHARE_ORG_CHART_SYSTEM_KEY_APPS = 'apps';
|
|
47
|
+
|
|
32
48
|
/** Discriminator string values — re-exported so hosts can build kind menus dynamically. */
|
|
33
49
|
const AUDIENCE_TERM_KINDS = [
|
|
34
50
|
'users',
|
|
@@ -359,6 +375,152 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
359
375
|
type: Injectable
|
|
360
376
|
}] });
|
|
361
377
|
|
|
378
|
+
/**
|
|
379
|
+
* FlyOS standard navigation surface for Business / Supporting App remotes.
|
|
380
|
+
*
|
|
381
|
+
* Problem this solves
|
|
382
|
+
* -------------------
|
|
383
|
+
* A federated remote ships with its own Angular `Routes` table that works
|
|
384
|
+
* perfectly in **standalone** mode (e.g. `http://localhost:7202/signals/:id`).
|
|
385
|
+
* Once the same remote is mounted inside the FlyOS desktop shell via
|
|
386
|
+
* `*ngComponentOutlet`, `inject(Router)` resolves to the **host shell's**
|
|
387
|
+
* Router — which has no knowledge of `/signals/:id`. Calls like
|
|
388
|
+
* `router.navigate(['/signals', id])` then silently no-op and the click
|
|
389
|
+
* "does nothing."
|
|
390
|
+
*
|
|
391
|
+
* The fix
|
|
392
|
+
* -------
|
|
393
|
+
* Inject `FlyRemoteRouter` instead of `Router`. It mirrors `Router.navigate` /
|
|
394
|
+
* `Router.navigateByUrl` and:
|
|
395
|
+
*
|
|
396
|
+
* - **Standalone** (no `WINDOW_DATA`): delegates to the real Angular Router,
|
|
397
|
+
* so the URL bar, browser back button, and `RouterLink` continue to work
|
|
398
|
+
* exactly as before.
|
|
399
|
+
* - **Embedded** (mounted in the shell via `WINDOW_DATA`): writes the target
|
|
400
|
+
* URL to an internal signal (`url`, `segments`) and does **not** touch the
|
|
401
|
+
* host's Router. The remote's root component watches the signal and renders
|
|
402
|
+
* the right view (typically a `@switch` on the first segment, plus an inline
|
|
403
|
+
* overlay for detail pages).
|
|
404
|
+
*
|
|
405
|
+
* Usage in a list page
|
|
406
|
+
* --------------------
|
|
407
|
+
* ```ts
|
|
408
|
+
* private readonly router = inject(FlyRemoteRouter);
|
|
409
|
+
*
|
|
410
|
+
* openDetail(item: Signal): void {
|
|
411
|
+
* this.router.navigate(['/signals', item.id]); // works in both modes
|
|
412
|
+
* }
|
|
413
|
+
* ```
|
|
414
|
+
*
|
|
415
|
+
* Usage in the remote's root component (embedded dispatcher)
|
|
416
|
+
* ----------------------------------------------------------
|
|
417
|
+
* ```ts
|
|
418
|
+
* private readonly router = inject(FlyRemoteRouter);
|
|
419
|
+
*
|
|
420
|
+
* readonly view = computed(() => {
|
|
421
|
+
* if (!this.router.isEmbedded) return null; // standalone: <router-outlet> handles it
|
|
422
|
+
* const [head, id] = this.router.segments();
|
|
423
|
+
* if (head === 'signals' && id) return { kind: 'signal-detail', id };
|
|
424
|
+
* if (head === 'signals') return { kind: 'signals-list' };
|
|
425
|
+
* return { kind: head ?? 'home' };
|
|
426
|
+
* });
|
|
427
|
+
* ```
|
|
428
|
+
*
|
|
429
|
+
* Notes
|
|
430
|
+
* -----
|
|
431
|
+
* - `WINDOW_DATA` is the canonical "I am running inside the shell" marker; the
|
|
432
|
+
* shell provides it per window, and it is `null` everywhere else.
|
|
433
|
+
* - `Router` is injected as `optional`. A remote that does not register a Router
|
|
434
|
+
* (e.g. a tiny chromeless utility app) still gets a working `navigate` that
|
|
435
|
+
* updates the `url` signal in embedded mode.
|
|
436
|
+
*/
|
|
437
|
+
class FlyRemoteRouter {
|
|
438
|
+
windowData = inject(WINDOW_DATA, { optional: true });
|
|
439
|
+
router = inject(Router, { optional: true });
|
|
440
|
+
/** True when this remote is rendered inside the FlyOS desktop shell. */
|
|
441
|
+
isEmbedded = this.windowData != null;
|
|
442
|
+
_url = signal('/', ...(ngDevMode ? [{ debugName: "_url" }] : /* istanbul ignore next */ []));
|
|
443
|
+
/**
|
|
444
|
+
* Current logical URL of the remote — `/signals/abc` etc.
|
|
445
|
+
*
|
|
446
|
+
* Standalone: mirrors `Router.url` and is updated on every `NavigationEnd`.
|
|
447
|
+
* Embedded: updated by `navigate` / `navigateByUrl` only.
|
|
448
|
+
*/
|
|
449
|
+
url = this._url.asReadonly();
|
|
450
|
+
/**
|
|
451
|
+
* `url` parsed into path segments, e.g. `'/signals/abc'` → `['signals', 'abc']`.
|
|
452
|
+
* Query string and hash are stripped. Empty segments removed.
|
|
453
|
+
*/
|
|
454
|
+
segments = computed(() => {
|
|
455
|
+
const u = this._url();
|
|
456
|
+
return u.split('?')[0].split('#')[0].split('/').filter(Boolean);
|
|
457
|
+
}, ...(ngDevMode ? [{ debugName: "segments" }] : /* istanbul ignore next */ []));
|
|
458
|
+
constructor() {
|
|
459
|
+
if (!this.isEmbedded && this.router) {
|
|
460
|
+
// Seed with the current standalone URL so consumers can read `segments()`
|
|
461
|
+
// synchronously during initial render, before any NavigationEnd fires.
|
|
462
|
+
this._url.set(this.router.url);
|
|
463
|
+
this.router.events.subscribe(event => {
|
|
464
|
+
if (event instanceof NavigationEnd) {
|
|
465
|
+
this._url.set(event.urlAfterRedirects);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Navigate to a route, identified by an array of commands as you would pass
|
|
472
|
+
* to Angular's `Router.navigate`. In embedded mode `extras` is ignored — the
|
|
473
|
+
* shell owns history and query-param handling for the window's URL.
|
|
474
|
+
*/
|
|
475
|
+
navigate(commands, extras) {
|
|
476
|
+
if (!this.isEmbedded && this.router) {
|
|
477
|
+
return this.router.navigate(commands, extras);
|
|
478
|
+
}
|
|
479
|
+
this._url.set(this.buildUrl(commands));
|
|
480
|
+
return Promise.resolve(true);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Navigate to a route given as a full URL string. Mirrors `Router.navigateByUrl`.
|
|
484
|
+
*/
|
|
485
|
+
navigateByUrl(url, extras) {
|
|
486
|
+
if (!this.isEmbedded && this.router) {
|
|
487
|
+
return this.router.navigateByUrl(url, extras);
|
|
488
|
+
}
|
|
489
|
+
this._url.set(url.startsWith('/') ? url : '/' + url);
|
|
490
|
+
return Promise.resolve(true);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Convenience: pop the last two segments (the typical "go back from detail
|
|
494
|
+
* to list" gesture). In standalone mode this falls through to `history.back()`
|
|
495
|
+
* so the browser's back stack is respected.
|
|
496
|
+
*/
|
|
497
|
+
back() {
|
|
498
|
+
if (!this.isEmbedded) {
|
|
499
|
+
if (typeof history !== 'undefined')
|
|
500
|
+
history.back();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const segs = [...this.segments()];
|
|
504
|
+
if (segs.length === 0)
|
|
505
|
+
return;
|
|
506
|
+
segs.pop();
|
|
507
|
+
this._url.set('/' + segs.join('/'));
|
|
508
|
+
}
|
|
509
|
+
buildUrl(commands) {
|
|
510
|
+
const parts = commands
|
|
511
|
+
.map(c => (c == null ? '' : String(c)))
|
|
512
|
+
.map(s => s.replace(/^\/+|\/+$/g, ''))
|
|
513
|
+
.filter(Boolean);
|
|
514
|
+
return '/' + parts.join('/');
|
|
515
|
+
}
|
|
516
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
517
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouter, providedIn: 'root' });
|
|
518
|
+
}
|
|
519
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouter, decorators: [{
|
|
520
|
+
type: Injectable,
|
|
521
|
+
args: [{ providedIn: 'root' }]
|
|
522
|
+
}], ctorParameters: () => [] });
|
|
523
|
+
|
|
362
524
|
/**
|
|
363
525
|
* Translates a key using the shared I18nService.
|
|
364
526
|
*
|
|
@@ -603,11 +765,11 @@ class ContextMenuComponent {
|
|
|
603
765
|
this.previouslyFocused = null;
|
|
604
766
|
}
|
|
605
767
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ContextMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
606
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ContextMenuComponent, isStandalone: true, selector: "fly-context-menu", inputs: { x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: true, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: true, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { action: "action", closed: "closed" }, host: { listeners: { "document:mousedown": "onClickOutside($event)", "document:keydown.escape": "onEscape()", "document:contextmenu": "onContextMenu($event)", "keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["contextMenu"], descendants: true }], ngImport: i0, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72))
|
|
768
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ContextMenuComponent, isStandalone: true, selector: "fly-context-menu", inputs: { x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: true, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: true, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { action: "action", closed: "closed" }, host: { listeners: { "document:mousedown": "onClickOutside($event)", "document:keydown.escape": "onEscape()", "document:contextmenu": "onContextMenu($event)", "keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["contextMenu"], descendants: true }], ngImport: i0, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72));-webkit-backdrop-filter:blur(40px) saturate(180%);backdrop-filter:blur(40px) saturate(180%);border:1px solid var(--glass-border, rgba(255, 255, 255, .15));box-shadow:0 12px 40px #00000059,0 2px 8px #0003,inset 0 .5px #ffffff1a;animation:menuEnter .18s cubic-bezier(.22,1,.36,1) both;transform-origin:top left}@keyframes menuEnter{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.menu-section-label{padding:6px 12px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-color-secondary, rgba(255, 255, 255, .45));pointer-events:none}.menu-item{gap:10px;width:100%;padding:8px 12px;color:var(--text-color, #fff);font-size:var(--link-font-size, 13px);font-weight:var(--btn-symbol-font-weight, 510);justify-content:flex-start}.menu-item i{font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item:hover{background:var(--primary-color, #E8732A);color:#fff}.menu-item:hover i{opacity:1}.menu-item:focus-visible{background:var(--primary-color, #E8732A);outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass-border, rgba(255, 255, 255, .12))}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
607
769
|
}
|
|
608
770
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ContextMenuComponent, decorators: [{
|
|
609
771
|
type: Component,
|
|
610
|
-
args: [{ selector: 'fly-context-menu', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72))
|
|
772
|
+
args: [{ selector: 'fly-context-menu', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72));-webkit-backdrop-filter:blur(40px) saturate(180%);backdrop-filter:blur(40px) saturate(180%);border:1px solid var(--glass-border, rgba(255, 255, 255, .15));box-shadow:0 12px 40px #00000059,0 2px 8px #0003,inset 0 .5px #ffffff1a;animation:menuEnter .18s cubic-bezier(.22,1,.36,1) both;transform-origin:top left}@keyframes menuEnter{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.menu-section-label{padding:6px 12px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-color-secondary, rgba(255, 255, 255, .45));pointer-events:none}.menu-item{gap:10px;width:100%;padding:8px 12px;color:var(--text-color, #fff);font-size:var(--link-font-size, 13px);font-weight:var(--btn-symbol-font-weight, 510);justify-content:flex-start}.menu-item i{font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item:hover{background:var(--primary-color, #E8732A);color:#fff}.menu-item:hover i{opacity:1}.menu-item:focus-visible{background:var(--primary-color, #E8732A);outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass-border, rgba(255, 255, 255, .12))}\n"] }]
|
|
611
773
|
}], propDecorators: { menuEl: [{
|
|
612
774
|
type: ViewChild,
|
|
613
775
|
args: ['contextMenu']
|
|
@@ -660,29 +822,65 @@ class MessageBoxService {
|
|
|
660
822
|
message = signal('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
661
823
|
icon = signal(MessageBoxIcon.None, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
662
824
|
buttons = signal([], ...(ngDevMode ? [{ debugName: "buttons" }] : /* istanbul ignore next */ []));
|
|
825
|
+
/**
|
|
826
|
+
* Active "Don't ask again" config — populated only by `showAcknowledged()`.
|
|
827
|
+
* `show()` callers cannot reach this signal because the type system rejects
|
|
828
|
+
* `dontAskAgain` on the base `MessageBoxOptions`.
|
|
829
|
+
*/
|
|
830
|
+
dontAskAgain = signal(undefined, ...(ngDevMode ? [{ debugName: "dontAskAgain" }] : /* istanbul ignore next */ []));
|
|
831
|
+
/**
|
|
832
|
+
* Component-side checkbox state. The component (`MessageBoxComponent`) wires
|
|
833
|
+
* its own signal to this one; the service explicitly re-seeds it on every
|
|
834
|
+
* `_open()` so reusing a constant `options` literal across two opens does
|
|
835
|
+
* NOT leak the previous user's checkbox state through signal `===` dedupe.
|
|
836
|
+
* Exposed for the component to read/write; not for general callers.
|
|
837
|
+
*/
|
|
838
|
+
dontAskAgainChecked = signal(false, ...(ngDevMode ? [{ debugName: "dontAskAgainChecked" }] : /* istanbul ignore next */ []));
|
|
663
839
|
resolver = null;
|
|
664
840
|
show(options) {
|
|
665
|
-
|
|
841
|
+
return this._open(options).then((r) => r.result);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Opens a message box with a "Don't ask again" checkbox and resolves to both
|
|
845
|
+
* the dialog result and the checkbox state. Throws when `options.dontAskAgain`
|
|
846
|
+
* is missing — the checkbox config is mandatory for this entry point. The
|
|
847
|
+
* compile-time signature already guarantees presence; the runtime check is
|
|
848
|
+
* defense-in-depth for callers that bypass the type system via `as never`.
|
|
849
|
+
*/
|
|
850
|
+
showAcknowledged(options) {
|
|
851
|
+
if (!options.dontAskAgain) {
|
|
852
|
+
throw new Error('MessageBoxService.showAcknowledged() requires options.dontAskAgain — use show() for plain dialogs.');
|
|
853
|
+
}
|
|
854
|
+
return this._open(options);
|
|
855
|
+
}
|
|
856
|
+
resolve(result, dontAskAgain = false) {
|
|
857
|
+
this.visible.set(false);
|
|
666
858
|
if (this.resolver) {
|
|
667
|
-
this.resolver(
|
|
859
|
+
this.resolver({ result, dontAskAgain });
|
|
668
860
|
this.resolver = null;
|
|
669
861
|
}
|
|
862
|
+
}
|
|
863
|
+
_open(options) {
|
|
864
|
+
// Dismiss any pending dialog so its Promise resolves rather than leaking.
|
|
865
|
+
if (this.resolver) {
|
|
866
|
+
this.resolver({ result: DialogResult.None, dontAskAgain: false });
|
|
867
|
+
this.resolver = null;
|
|
868
|
+
}
|
|
869
|
+
const daa = 'dontAskAgain' in options ? options.dontAskAgain : undefined;
|
|
670
870
|
this.title.set(options.title);
|
|
671
871
|
this.message.set(options.message);
|
|
672
872
|
this.icon.set(options.icon ?? MessageBoxIcon.None);
|
|
673
873
|
this.buttons.set(this.resolveButtons(options.buttons ?? MessageBoxButtons.OK));
|
|
874
|
+
this.dontAskAgain.set(daa);
|
|
875
|
+
// Explicit re-seed: signal `set` dedupes by `===`, so an `effect()` on
|
|
876
|
+
// `dontAskAgain` would not re-fire when a caller passes the SAME options
|
|
877
|
+
// literal twice. Set the checkbox state directly each open.
|
|
878
|
+
this.dontAskAgainChecked.set(daa?.defaultChecked ?? false);
|
|
674
879
|
this.visible.set(true);
|
|
675
880
|
return new Promise((resolve) => {
|
|
676
881
|
this.resolver = resolve;
|
|
677
882
|
});
|
|
678
883
|
}
|
|
679
|
-
resolve(result) {
|
|
680
|
-
this.visible.set(false);
|
|
681
|
-
if (this.resolver) {
|
|
682
|
-
this.resolver(result);
|
|
683
|
-
this.resolver = null;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
884
|
resolveButtons(buttons) {
|
|
687
885
|
const t = (key) => this.i18n.t(key);
|
|
688
886
|
switch (buttons) {
|
|
@@ -730,6 +928,19 @@ class MessageBoxComponent {
|
|
|
730
928
|
elRef = inject(ElementRef);
|
|
731
929
|
MessageBoxIcon = MessageBoxIcon;
|
|
732
930
|
DialogResult = DialogResult;
|
|
931
|
+
/** Mirror of the service's `dontAskAgain` config so the template can read it directly. */
|
|
932
|
+
dontAskAgainConfig = computed(() => this.service.dontAskAgain(), ...(ngDevMode ? [{ debugName: "dontAskAgainConfig" }] : /* istanbul ignore next */ []));
|
|
933
|
+
/**
|
|
934
|
+
* User-driven checkbox state, owned by the service and re-seeded on every
|
|
935
|
+
* `_open()` so reusing a constant options literal cannot leak prior state.
|
|
936
|
+
*/
|
|
937
|
+
dontAskAgainChecked = this.service.dontAskAgainChecked;
|
|
938
|
+
/**
|
|
939
|
+
* Concatenated id list for the dialog's `aria-describedby`. Always includes
|
|
940
|
+
* the message; appends the checkbox label id when the checkbox is rendered
|
|
941
|
+
* so screen readers announce the checkbox as part of the description.
|
|
942
|
+
*/
|
|
943
|
+
ariaDescribedBy = computed(() => this.dontAskAgainConfig() ? 'mb-message mb-dont-ask' : 'mb-message', ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
|
|
733
944
|
previouslyFocused = null;
|
|
734
945
|
ngAfterViewInit() {
|
|
735
946
|
// Store focus origin so we can restore it on close
|
|
@@ -738,18 +949,22 @@ class MessageBoxComponent {
|
|
|
738
949
|
}
|
|
739
950
|
onEscape() {
|
|
740
951
|
if (this.service.visible()) {
|
|
741
|
-
this.service.resolve(DialogResult.Cancel);
|
|
952
|
+
this.service.resolve(DialogResult.Cancel, this.dontAskAgainChecked());
|
|
742
953
|
this.restoreFocus();
|
|
743
954
|
}
|
|
744
955
|
}
|
|
745
956
|
onBackdropClick() {
|
|
746
|
-
this.service.resolve(DialogResult.Cancel);
|
|
957
|
+
this.service.resolve(DialogResult.Cancel, this.dontAskAgainChecked());
|
|
747
958
|
this.restoreFocus();
|
|
748
959
|
}
|
|
749
960
|
onButtonClick(result) {
|
|
750
|
-
this.service.resolve(result);
|
|
961
|
+
this.service.resolve(result, this.dontAskAgainChecked());
|
|
751
962
|
this.restoreFocus();
|
|
752
963
|
}
|
|
964
|
+
onDontAskAgainToggle(ev) {
|
|
965
|
+
const target = ev.target;
|
|
966
|
+
this.dontAskAgainChecked.set(!!target?.checked);
|
|
967
|
+
}
|
|
753
968
|
iconClass() {
|
|
754
969
|
switch (this.service.icon()) {
|
|
755
970
|
case MessageBoxIcon.Information: return 'pi pi-info-circle';
|
|
@@ -772,11 +987,11 @@ class MessageBoxComponent {
|
|
|
772
987
|
this.previouslyFocused = null;
|
|
773
988
|
}
|
|
774
989
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
775
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageBoxComponent, isStandalone: true, selector: "fly-message-box", host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (service.visible()) {\n <div\n class=\"mb-backdrop\"\n (click)=\"onBackdropClick()\"\n role=\"presentation\">\n <div\n class=\"mb-dialog\"\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\n (click)=\"$event.stopPropagation()\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"mb-title\"\n aria-describedby=\"
|
|
990
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageBoxComponent, isStandalone: true, selector: "fly-message-box", host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (service.visible()) {\r\n <div\r\n class=\"mb-backdrop\"\r\n (click)=\"onBackdropClick()\"\r\n role=\"presentation\">\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\r\n (click)=\"$event.stopPropagation()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ service.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ service.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of service.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"vos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;background:#00000073;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color, #E8732A);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
776
991
|
}
|
|
777
992
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageBoxComponent, decorators: [{
|
|
778
993
|
type: Component,
|
|
779
|
-
args: [{ selector: 'fly-message-box', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (service.visible()) {\n <div\n class=\"mb-backdrop\"\n (click)=\"onBackdropClick()\"\n role=\"presentation\">\n <div\n class=\"mb-dialog\"\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\n (click)=\"$event.stopPropagation()\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"mb-title\"\n aria-describedby=\"
|
|
994
|
+
args: [{ selector: 'fly-message-box', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (service.visible()) {\r\n <div\r\n class=\"mb-backdrop\"\r\n (click)=\"onBackdropClick()\"\r\n role=\"presentation\">\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\r\n (click)=\"$event.stopPropagation()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ service.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ service.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of service.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"vos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;background:#00000073;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color, #E8732A);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"] }]
|
|
780
995
|
}], propDecorators: { onEscape: [{
|
|
781
996
|
type: HostListener,
|
|
782
997
|
args: ['document:keydown.escape']
|
|
@@ -804,6 +1019,17 @@ class SharePanelComponent {
|
|
|
804
1019
|
loadOuTree;
|
|
805
1020
|
/** Chart selector options; first entry should be the default chart (`id: null`). */
|
|
806
1021
|
loadChartOptions;
|
|
1022
|
+
/**
|
|
1023
|
+
* Initial chart selection. Resolution order:
|
|
1024
|
+
* <list type="number">
|
|
1025
|
+
* <item>{@link defaultChartId} when explicitly provided (string or null).</item>
|
|
1026
|
+
* <item>First option matching {@link defaultChartSystemKey} (e.g. <c>'apps'</c>).</item>
|
|
1027
|
+
* <item>Host order — <c>options[0]?.id ?? null</c>.</item>
|
|
1028
|
+
* </list>
|
|
1029
|
+
*/
|
|
1030
|
+
defaultChartSystemKey;
|
|
1031
|
+
/** Hard-coded chart id to pre-select. Takes priority over {@link defaultChartSystemKey}. */
|
|
1032
|
+
defaultChartId;
|
|
807
1033
|
/**
|
|
808
1034
|
* Labels from the default (official) OU tree for stable display names on OU grants
|
|
809
1035
|
* when the picker shows an alternative chart.
|
|
@@ -909,8 +1135,7 @@ class SharePanelComponent {
|
|
|
909
1135
|
.subscribe({
|
|
910
1136
|
next: (opts) => {
|
|
911
1137
|
this.chartOptions.set(opts);
|
|
912
|
-
const
|
|
913
|
-
const initial = first ? first.id : null;
|
|
1138
|
+
const initial = this.resolveInitialChartId(opts);
|
|
914
1139
|
this.selectedOrgChartId.set(initial);
|
|
915
1140
|
this.selectedChartId$.next(initial);
|
|
916
1141
|
},
|
|
@@ -921,6 +1146,24 @@ class SharePanelComponent {
|
|
|
921
1146
|
},
|
|
922
1147
|
});
|
|
923
1148
|
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Picks the chart id to seed the OU picker with. Honours explicit
|
|
1151
|
+
* {@link defaultChartId} first (null is a valid id), then {@link defaultChartSystemKey},
|
|
1152
|
+
* then falls back to host order.
|
|
1153
|
+
*/
|
|
1154
|
+
resolveInitialChartId(opts) {
|
|
1155
|
+
if (this.defaultChartId !== undefined) {
|
|
1156
|
+
const exists = opts.some((o) => o.id === this.defaultChartId);
|
|
1157
|
+
return exists ? this.defaultChartId : (opts[0]?.id ?? null);
|
|
1158
|
+
}
|
|
1159
|
+
const key = this.defaultChartSystemKey;
|
|
1160
|
+
if (key) {
|
|
1161
|
+
const match = opts.find((o) => o.systemKey === key);
|
|
1162
|
+
if (match)
|
|
1163
|
+
return match.id;
|
|
1164
|
+
}
|
|
1165
|
+
return opts[0]?.id ?? null;
|
|
1166
|
+
}
|
|
924
1167
|
onOrgChartSelectChange(chartId) {
|
|
925
1168
|
this.selectedOrgChartId.set(chartId);
|
|
926
1169
|
this.selectedChartId$.next(chartId);
|
|
@@ -1080,7 +1323,7 @@ class SharePanelComponent {
|
|
|
1080
1323
|
}
|
|
1081
1324
|
}
|
|
1082
1325
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SharePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1083
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SharePanelComponent, isStandalone: true, selector: "fly-share-panel", inputs: { targetName: "targetName", titleKey: "titleKey", loadPermissions: "loadPermissions", searchUsers: "searchUsers", loadOuTree: "loadOuTree", loadChartOptions: "loadChartOptions", loadOuLabelMap: "loadOuLabelMap", grantToUser: "grantToUser", grantToOu: "grantToOu", updatePermission: "updatePermission", revokePermission: "revokePermission", showApplyToChildren: "showApplyToChildren", supportsDeny: "supportsDeny", permissionLevels: "permissionLevels", everyoneLabelKey: "everyoneLabelKey", loadRoleOus: "loadRoleOus" }, outputs: { close: "close" }, ngImport: i0, template: "<div class=\"fac-overlay\" (click)=\"close.emit()\">\r\n <div class=\"fac-panel\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"fac-header\">\r\n <h3>{{ titleKey | translate }}</h3>\r\n <span class=\"fac-target-name\">{{ targetName }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fac-close\"\r\n (click)=\"close.emit()\"\r\n [attr.aria-label]=\"'files.share.close' | translate\">\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n\r\n <div class=\"fac-add-section\">\r\n <div class=\"fac-search-row\">\r\n <input\r\n type=\"text\"\r\n class=\"fac-input\"\r\n [(ngModel)]=\"searchQuery\"\r\n (input)=\"onSearchInput()\"\r\n [placeholder]=\"'files.share.search_placeholder' | translate\"\r\n [attr.aria-label]=\"'files.share.search_users' | translate\"\r\n />\r\n <select class=\"fac-select\" [(ngModel)]=\"newLevel\">\r\n @for (opt of levelOptions; track opt.value) {\r\n <option [value]=\"opt.value\">{{ opt.labelKey | translate }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (searchResults().length > 0) {\r\n <div class=\"fac-search-results\">\r\n @for (result of searchResults(); track result.id) {\r\n <div class=\"fac-search-item\" (click)=\"onGrantToUser(result)\">\r\n <i class=\"pi pi-user\" aria-hidden=\"true\"></i>\r\n <span>{{ result.firstName }} {{ result.lastName }}</span>\r\n <span class=\"fac-email\">{{ result.email }}</span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (chartOptions().length > 0) {\r\n <div class=\"fac-chart-row\">\r\n <label for=\"fac-org-chart-select\" class=\"fac-section-label\">{{\r\n 'files.share.org_chart' | translate\r\n }}</label>\r\n <select\r\n id=\"fac-org-chart-select\"\r\n class=\"fac-select\"\r\n [ngModel]=\"selectedOrgChartId()\"\r\n (ngModelChange)=\"onOrgChartSelectChange($event)\">\r\n @for (opt of chartOptions(); track $index) {\r\n <option [ngValue]=\"opt.id\">{{ opt.name }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n\r\n @if (showOuPicker()) {\r\n <div class=\"fac-ou-section\">\r\n <div class=\"fac-section-label\">{{ 'files.share.share_with_ou' | translate }}</div>\r\n @for (ou of ouTree(); track ou.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fac-ou-item\"\r\n [style.padding-inline-start.px]=\"ouIndentStartPx(ou)\"\r\n (click)=\"onGrantToOu(ou)\"\r\n [attr.aria-label]=\"('files.share.share_with_ou_named' | translate) + ' ' + ou.displayName\"\r\n >\r\n <i class=\"pi pi-sitemap\" aria-hidden=\"true\"></i>\r\n <span dir=\"auto\">{{ ou.displayName }}</span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (loadRoleOus && roleOus().length > 0) {\r\n <div class=\"fac-role-section\">\r\n <div class=\"fac-section-label\">{{ 'files.share.share_with_role' | translate }}</div>\r\n <input\r\n type=\"text\"\r\n class=\"fac-input\"\r\n [(ngModel)]=\"roleSearchQuery\"\r\n [placeholder]=\"'files.share.role_search_placeholder' | translate\"\r\n [attr.aria-label]=\"'files.share.search_roles' | translate\"\r\n [hidden]=\"!showRolePicker()\" />\r\n @if (showRolePicker()) {\r\n <div class=\"fac-role-list\">\r\n @for (role of filteredRoles(); track role.ouId) {\r\n <button\r\n type=\"button\"\r\n class=\"fac-role-item\"\r\n (click)=\"onGrantToRole(role)\"\r\n [attr.aria-label]=\"('files.share.share_with_role_named' | translate) + ' ' + role.displayName\">\r\n <i class=\"pi pi-id-card\" aria-hidden=\"true\"></i>\r\n <span dir=\"auto\">{{ role.displayName }}</span>\r\n <span class=\"fac-role-app\">{{ role.appId }}</span>\r\n </button>\r\n }\r\n @if (filteredRoles().length === 0) {\r\n <div class=\"fac-empty\">{{ 'files.share.no_roles_match' | translate }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"fac-toggle-row\">\r\n <button type=\"button\" class=\"fac-link\" (click)=\"showOuPicker.set(!showOuPicker())\">\r\n {{ showOuPicker() ? ('files.share.hide_ous' | translate) : ('files.share.show_ous' | translate) }}\r\n </button>\r\n @if (loadRoleOus && roleOus().length > 0) {\r\n <button type=\"button\" class=\"fac-link\" (click)=\"showRolePicker.set(!showRolePicker())\">\r\n {{ showRolePicker() ? ('files.share.hide_roles' | translate) : ('files.share.show_roles' | translate) }}\r\n </button>\r\n }\r\n @if (showApplyToChildren) {\r\n <label class=\"fac-checkbox-label\">\r\n <input type=\"checkbox\" [(ngModel)]=\"applyToChildren\" />\r\n {{ 'files.share.apply_children' | translate }}\r\n </label>\r\n }\r\n @if (supportsDeny) {\r\n <label class=\"fac-checkbox-label fac-deny-toggle\"\r\n [title]=\"'files.share.deny_help' | translate\">\r\n <input type=\"checkbox\" [(ngModel)]=\"addAsDeny\" />\r\n <i class=\"pi pi-ban\" aria-hidden=\"true\"></i>\r\n {{ 'files.share.add_as_deny' | translate }}\r\n </label>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fac-perms-section\">\r\n <div class=\"fac-section-label\">{{ 'files.share.current_permissions' | translate }}</div>\r\n @if (loading()) {\r\n <div class=\"fac-loading\"><i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i></div>\r\n } @else if (permissions().length === 0) {\r\n <div class=\"fac-empty\">{{ 'files.share.no_permissions' | translate }}</div>\r\n } @else {\r\n @for (perm of permissions(); track perm.id) {\r\n <div class=\"fac-perm-row\" [class.fac-perm-row-deny]=\"perm.isDeny\">\r\n <div class=\"fac-perm-info\">\r\n <i [class]=\"getPermIcon(perm)\" aria-hidden=\"true\"></i>\r\n <span class=\"fac-perm-name\" dir=\"auto\">{{ getPermLabel(perm) }}</span>\r\n @if (perm.isDeny) {\r\n <span class=\"fac-badge deny\">{{ 'files.share.denied' | translate }}</span>\r\n }\r\n @if (perm.isInherited) {\r\n <span class=\"fac-badge inherited\">{{ 'files.share.inherited' | translate }}</span>\r\n }\r\n </div>\r\n <div class=\"fac-perm-actions\">\r\n @if (!perm.isDeny) {\r\n <select\r\n class=\"fac-select sm\"\r\n [ngModel]=\"perm.level\"\r\n (ngModelChange)=\"onUpdateLevel(perm, $event)\"\r\n [disabled]=\"perm.isInherited === true\"\r\n >\r\n @for (opt of levelOptions; track opt.value) {\r\n <option [value]=\"opt.value\">{{ opt.labelKey | translate }}</option>\r\n }\r\n </select>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fac-icon-btn danger\"\r\n (click)=\"onRevokePermission(perm)\"\r\n [disabled]=\"perm.isInherited === true\"\r\n [title]=\"'files.share.revoke' | translate\"\r\n >\r\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".fac-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:2000;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.fac-panel{background:var(--surface-card, #1e1e1e);border:1px solid var(--surface-border);border-radius:12px;width:600px;max-width:min(600px,96vw);max-height:85vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 16px 48px #0006}.fac-header{display:flex;align-items:center;gap:10px;padding:16px 20px;border-bottom:1px solid var(--surface-border)}.fac-header h3{margin:0;font-size:16px;font-weight:600}.fac-target-name{flex:1;font-size:13px;color:var(--text-color-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fac-close{background:none;border:none;color:var(--text-color-secondary);cursor:pointer;font-size:16px;padding:4px}.fac-close:hover{color:var(--text-color)}.fac-add-section{padding:16px 20px;border-bottom:1px solid var(--surface-border)}.fac-search-row{display:flex;gap:8px}.fac-chart-row{display:flex;flex-direction:column;gap:6px;margin-top:12px}.fac-chart-row .fac-select{width:100%}.fac-input{flex:1;background:#ffffff14;border:1px solid rgba(255,255,255,.15);border-radius:8px;padding:8px 12px;color:inherit;font-size:13px;outline:none}.fac-input:focus{border-color:var(--primary-color, #e8732a)}.fac-select{background:#ffffff14;border:1px solid rgba(255,255,255,.15);border-radius:8px;padding:8px 10px;color:inherit;font-size:13px;outline:none}.fac-select.sm{padding:4px 8px;font-size:12px}.fac-search-results{margin-top:8px;max-height:150px;overflow-y:auto;border:1px solid var(--surface-border);border-radius:8px}.fac-search-item{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;font-size:13px}.fac-search-item:hover{background:var(--surface-hover)}.fac-search-item i{color:var(--text-color-secondary)}.fac-email{color:var(--text-color-secondary);font-size:12px;margin-inline-start:auto}.fac-ou-section{margin-top:10px;max-height:220px;overflow-y:auto}.fac-ou-item{display:flex;align-items:center;gap:8px;padding:6px 12px;padding-inline-start:12px;cursor:pointer;font-size:13px;border-radius:6px}.fac-ou-item:hover{background:var(--surface-hover)}.fac-ou-item i{color:var(--text-color-secondary)}.fac-section-label{font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-color-secondary);letter-spacing:.5px;margin-bottom:8px}.fac-toggle-row{display:flex;align-items:center;justify-content:space-between;margin-top:10px}.fac-link{background:none;border:none;color:var(--primary-color, #e8732a);cursor:pointer;font-size:12px;padding:0}.fac-link:hover{text-decoration:underline}.fac-checkbox-label{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-color-secondary);cursor:pointer}.fac-checkbox-label input{accent-color:var(--primary-color, #e8732a)}.fac-perms-section{flex:1;overflow-y:auto;padding:16px 20px}.fac-loading,.fac-empty{text-align:center;padding:20px;color:var(--text-color-secondary);font-size:13px}.fac-perm-row{display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,.05);gap:8px}.fac-perm-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.fac-perm-info i{color:var(--text-color-secondary);flex-shrink:0}.fac-perm-name{font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fac-badge{font-size:10px;padding:2px 6px;border-radius:8px;flex-shrink:0}.fac-badge.inherited{background:#ffffff1a;color:var(--text-color-secondary)}.fac-badge.deny{background:#ff3b302e;color:#ff3b30;text-transform:uppercase;font-weight:600;letter-spacing:.5px}.fac-perm-row-deny{background:#ff3b300f;border-inline-start:3px solid #ff3b30;padding-inline-start:8px;border-radius:6px}.fac-perm-row-deny .fac-perm-info i{color:#ff3b30}.fac-deny-toggle{color:#ff6b6b}.fac-deny-toggle i{color:#ff6b6b;font-size:11px;margin-inline-end:2px}.fac-deny-toggle input{accent-color:#ff3b30}.fac-role-section{margin-top:10px;display:flex;flex-direction:column;gap:6px}.fac-role-list{margin-top:6px;max-height:220px;overflow-y:auto;border:1px solid var(--surface-border);border-radius:8px}.fac-role-item{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;font-size:13px}.fac-role-item:hover{background:var(--surface-hover)}.fac-role-item i{color:var(--text-color-secondary)}.fac-role-app{margin-inline-start:auto;font-size:11px;color:var(--text-color-secondary);text-transform:uppercase;letter-spacing:.5px}.fac-perm-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.fac-icon-btn{background:#ffffff0f;border:none;border-radius:6px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;color:inherit;cursor:pointer;font-size:12px}.fac-icon-btn:hover{background:#ffffff24}.fac-icon-btn.danger:hover{background:#ff3b3033;color:#ff3b30}.fac-icon-btn:disabled{opacity:.4;cursor:default}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1326
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SharePanelComponent, isStandalone: true, selector: "fly-share-panel", inputs: { targetName: "targetName", titleKey: "titleKey", loadPermissions: "loadPermissions", searchUsers: "searchUsers", loadOuTree: "loadOuTree", loadChartOptions: "loadChartOptions", defaultChartSystemKey: "defaultChartSystemKey", defaultChartId: "defaultChartId", loadOuLabelMap: "loadOuLabelMap", grantToUser: "grantToUser", grantToOu: "grantToOu", updatePermission: "updatePermission", revokePermission: "revokePermission", showApplyToChildren: "showApplyToChildren", supportsDeny: "supportsDeny", permissionLevels: "permissionLevels", everyoneLabelKey: "everyoneLabelKey", loadRoleOus: "loadRoleOus" }, outputs: { close: "close" }, ngImport: i0, template: "<div class=\"fac-overlay\" (click)=\"close.emit()\">\r\n <div class=\"fac-panel\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"fac-header\">\r\n <h3>{{ titleKey | translate }}</h3>\r\n <span class=\"fac-target-name\">{{ targetName }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fac-close\"\r\n (click)=\"close.emit()\"\r\n [attr.aria-label]=\"'files.share.close' | translate\">\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n\r\n <div class=\"fac-add-section\">\r\n <div class=\"fac-search-row\">\r\n <input\r\n type=\"text\"\r\n class=\"fac-input\"\r\n [(ngModel)]=\"searchQuery\"\r\n (input)=\"onSearchInput()\"\r\n [placeholder]=\"'files.share.search_placeholder' | translate\"\r\n [attr.aria-label]=\"'files.share.search_users' | translate\"\r\n />\r\n <select class=\"fac-select\" [(ngModel)]=\"newLevel\">\r\n @for (opt of levelOptions; track opt.value) {\r\n <option [value]=\"opt.value\">{{ opt.labelKey | translate }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (searchResults().length > 0) {\r\n <div class=\"fac-search-results\">\r\n @for (result of searchResults(); track result.id) {\r\n <div class=\"fac-search-item\" (click)=\"onGrantToUser(result)\">\r\n <i class=\"pi pi-user\" aria-hidden=\"true\"></i>\r\n <span>{{ result.firstName }} {{ result.lastName }}</span>\r\n <span class=\"fac-email\">{{ result.email }}</span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (chartOptions().length > 0) {\r\n <div class=\"fac-chart-row\">\r\n <label for=\"fac-org-chart-select\" class=\"fac-section-label\">{{\r\n 'files.share.org_chart' | translate\r\n }}</label>\r\n <select\r\n id=\"fac-org-chart-select\"\r\n class=\"fac-select\"\r\n [ngModel]=\"selectedOrgChartId()\"\r\n (ngModelChange)=\"onOrgChartSelectChange($event)\">\r\n @for (opt of chartOptions(); track $index) {\r\n <option [ngValue]=\"opt.id\">{{ opt.name }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n\r\n @if (showOuPicker()) {\r\n <div class=\"fac-ou-section\">\r\n <div class=\"fac-section-label\">{{ 'files.share.share_with_ou' | translate }}</div>\r\n @for (ou of ouTree(); track ou.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fac-ou-item\"\r\n [style.padding-inline-start.px]=\"ouIndentStartPx(ou)\"\r\n (click)=\"onGrantToOu(ou)\"\r\n [attr.aria-label]=\"('files.share.share_with_ou_named' | translate) + ' ' + ou.displayName\"\r\n >\r\n <i class=\"pi pi-sitemap\" aria-hidden=\"true\"></i>\r\n <span dir=\"auto\">{{ ou.displayName }}</span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (loadRoleOus && roleOus().length > 0) {\r\n <div class=\"fac-role-section\">\r\n <div class=\"fac-section-label\">{{ 'files.share.share_with_role' | translate }}</div>\r\n <input\r\n type=\"text\"\r\n class=\"fac-input\"\r\n [(ngModel)]=\"roleSearchQuery\"\r\n [placeholder]=\"'files.share.role_search_placeholder' | translate\"\r\n [attr.aria-label]=\"'files.share.search_roles' | translate\"\r\n [hidden]=\"!showRolePicker()\" />\r\n @if (showRolePicker()) {\r\n <div class=\"fac-role-list\">\r\n @for (role of filteredRoles(); track role.ouId) {\r\n <button\r\n type=\"button\"\r\n class=\"fac-role-item\"\r\n (click)=\"onGrantToRole(role)\"\r\n [attr.aria-label]=\"('files.share.share_with_role_named' | translate) + ' ' + role.displayName\">\r\n <i class=\"pi pi-id-card\" aria-hidden=\"true\"></i>\r\n <span dir=\"auto\">{{ role.displayName }}</span>\r\n <span class=\"fac-role-app\">{{ role.appId }}</span>\r\n </button>\r\n }\r\n @if (filteredRoles().length === 0) {\r\n <div class=\"fac-empty\">{{ 'files.share.no_roles_match' | translate }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"fac-toggle-row\">\r\n <button type=\"button\" class=\"fac-link\" (click)=\"showOuPicker.set(!showOuPicker())\">\r\n {{ showOuPicker() ? ('files.share.hide_ous' | translate) : ('files.share.show_ous' | translate) }}\r\n </button>\r\n @if (loadRoleOus && roleOus().length > 0) {\r\n <button type=\"button\" class=\"fac-link\" (click)=\"showRolePicker.set(!showRolePicker())\">\r\n {{ showRolePicker() ? ('files.share.hide_roles' | translate) : ('files.share.show_roles' | translate) }}\r\n </button>\r\n }\r\n @if (showApplyToChildren) {\r\n <label class=\"fac-checkbox-label\">\r\n <input type=\"checkbox\" [(ngModel)]=\"applyToChildren\" />\r\n {{ 'files.share.apply_children' | translate }}\r\n </label>\r\n }\r\n @if (supportsDeny) {\r\n <label class=\"fac-checkbox-label fac-deny-toggle\"\r\n [title]=\"'files.share.deny_help' | translate\">\r\n <input type=\"checkbox\" [(ngModel)]=\"addAsDeny\" />\r\n <i class=\"pi pi-ban\" aria-hidden=\"true\"></i>\r\n {{ 'files.share.add_as_deny' | translate }}\r\n </label>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fac-perms-section\">\r\n <div class=\"fac-section-label\">{{ 'files.share.current_permissions' | translate }}</div>\r\n @if (loading()) {\r\n <div class=\"fac-loading\"><i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i></div>\r\n } @else if (permissions().length === 0) {\r\n <div class=\"fac-empty\">{{ 'files.share.no_permissions' | translate }}</div>\r\n } @else {\r\n @for (perm of permissions(); track perm.id) {\r\n <div class=\"fac-perm-row\" [class.fac-perm-row-deny]=\"perm.isDeny\">\r\n <div class=\"fac-perm-info\">\r\n <i [class]=\"getPermIcon(perm)\" aria-hidden=\"true\"></i>\r\n <span class=\"fac-perm-name\" dir=\"auto\">{{ getPermLabel(perm) }}</span>\r\n @if (perm.isDeny) {\r\n <span class=\"fac-badge deny\">{{ 'files.share.denied' | translate }}</span>\r\n }\r\n @if (perm.isInherited) {\r\n <span class=\"fac-badge inherited\">{{ 'files.share.inherited' | translate }}</span>\r\n }\r\n </div>\r\n <div class=\"fac-perm-actions\">\r\n @if (!perm.isDeny) {\r\n <select\r\n class=\"fac-select sm\"\r\n [ngModel]=\"perm.level\"\r\n (ngModelChange)=\"onUpdateLevel(perm, $event)\"\r\n [disabled]=\"perm.isInherited === true\"\r\n >\r\n @for (opt of levelOptions; track opt.value) {\r\n <option [value]=\"opt.value\">{{ opt.labelKey | translate }}</option>\r\n }\r\n </select>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fac-icon-btn danger\"\r\n (click)=\"onRevokePermission(perm)\"\r\n [disabled]=\"perm.isInherited === true\"\r\n [title]=\"'files.share.revoke' | translate\"\r\n >\r\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".fac-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:2000;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.fac-panel{background:var(--surface-card, #1e1e1e);border:1px solid var(--surface-border);border-radius:12px;width:600px;max-width:min(600px,96vw);max-height:85vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 16px 48px #0006}.fac-header{display:flex;align-items:center;gap:10px;padding:16px 20px;border-bottom:1px solid var(--surface-border)}.fac-header h3{margin:0;font-size:16px;font-weight:600}.fac-target-name{flex:1;font-size:13px;color:var(--text-color-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fac-close{background:none;border:none;color:var(--text-color-secondary);cursor:pointer;font-size:16px;padding:4px}.fac-close:hover{color:var(--text-color)}.fac-add-section{padding:16px 20px;border-bottom:1px solid var(--surface-border)}.fac-search-row{display:flex;gap:8px}.fac-chart-row{display:flex;flex-direction:column;gap:6px;margin-top:12px}.fac-chart-row .fac-select{width:100%}.fac-input{flex:1;background:#ffffff14;border:1px solid rgba(255,255,255,.15);border-radius:8px;padding:8px 12px;color:inherit;font-size:13px;outline:none}.fac-input:focus{border-color:var(--primary-color, #e8732a)}.fac-select{background:#ffffff14;border:1px solid rgba(255,255,255,.15);border-radius:8px;padding:8px 10px;color:inherit;font-size:13px;outline:none}.fac-select.sm{padding:4px 8px;font-size:12px}.fac-search-results{margin-top:8px;max-height:150px;overflow-y:auto;border:1px solid var(--surface-border);border-radius:8px}.fac-search-item{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;font-size:13px}.fac-search-item:hover{background:var(--surface-hover)}.fac-search-item i{color:var(--text-color-secondary)}.fac-email{color:var(--text-color-secondary);font-size:12px;margin-inline-start:auto}.fac-ou-section{margin-top:10px;max-height:220px;overflow-y:auto}.fac-ou-item{display:flex;align-items:center;gap:8px;padding:6px 12px;padding-inline-start:12px;cursor:pointer;font-size:13px;border-radius:6px}.fac-ou-item:hover{background:var(--surface-hover)}.fac-ou-item i{color:var(--text-color-secondary)}.fac-section-label{font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-color-secondary);letter-spacing:.5px;margin-bottom:8px}.fac-toggle-row{display:flex;align-items:center;justify-content:space-between;margin-top:10px}.fac-link{background:none;border:none;color:var(--primary-color, #e8732a);cursor:pointer;font-size:12px;padding:0}.fac-link:hover{text-decoration:underline}.fac-checkbox-label{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-color-secondary);cursor:pointer}.fac-checkbox-label input{accent-color:var(--primary-color, #e8732a)}.fac-perms-section{flex:1;overflow-y:auto;padding:16px 20px}.fac-loading,.fac-empty{text-align:center;padding:20px;color:var(--text-color-secondary);font-size:13px}.fac-perm-row{display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,.05);gap:8px}.fac-perm-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.fac-perm-info i{color:var(--text-color-secondary);flex-shrink:0}.fac-perm-name{font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fac-badge{font-size:10px;padding:2px 6px;border-radius:8px;flex-shrink:0}.fac-badge.inherited{background:#ffffff1a;color:var(--text-color-secondary)}.fac-badge.deny{background:#ff3b302e;color:#ff3b30;text-transform:uppercase;font-weight:600;letter-spacing:.5px}.fac-perm-row-deny{background:#ff3b300f;border-inline-start:3px solid #ff3b30;padding-inline-start:8px;border-radius:6px}.fac-perm-row-deny .fac-perm-info i{color:#ff3b30}.fac-deny-toggle{color:#ff6b6b}.fac-deny-toggle i{color:#ff6b6b;font-size:11px;margin-inline-end:2px}.fac-deny-toggle input{accent-color:#ff3b30}.fac-role-section{margin-top:10px;display:flex;flex-direction:column;gap:6px}.fac-role-list{margin-top:6px;max-height:220px;overflow-y:auto;border:1px solid var(--surface-border);border-radius:8px}.fac-role-item{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;font-size:13px}.fac-role-item:hover{background:var(--surface-hover)}.fac-role-item i{color:var(--text-color-secondary)}.fac-role-app{margin-inline-start:auto;font-size:11px;color:var(--text-color-secondary);text-transform:uppercase;letter-spacing:.5px}.fac-perm-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.fac-icon-btn{background:#ffffff0f;border:none;border-radius:6px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;color:inherit;cursor:pointer;font-size:12px}.fac-icon-btn:hover{background:#ffffff24}.fac-icon-btn.danger:hover{background:#ff3b3033;color:#ff3b30}.fac-icon-btn:disabled{opacity:.4;cursor:default}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1084
1327
|
}
|
|
1085
1328
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SharePanelComponent, decorators: [{
|
|
1086
1329
|
type: Component,
|
|
@@ -1101,6 +1344,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1101
1344
|
}], loadChartOptions: [{
|
|
1102
1345
|
type: Input,
|
|
1103
1346
|
args: [{ required: true }]
|
|
1347
|
+
}], defaultChartSystemKey: [{
|
|
1348
|
+
type: Input
|
|
1349
|
+
}], defaultChartId: [{
|
|
1350
|
+
type: Input
|
|
1104
1351
|
}], loadOuLabelMap: [{
|
|
1105
1352
|
type: Input,
|
|
1106
1353
|
args: [{ required: true }]
|
|
@@ -1175,6 +1422,25 @@ class AudienceBuilderComponent {
|
|
|
1175
1422
|
searchUsers;
|
|
1176
1423
|
loadOuTree;
|
|
1177
1424
|
loadChartOptions;
|
|
1425
|
+
/**
|
|
1426
|
+
* Initial chart selection for the OU picker. Resolution order:
|
|
1427
|
+
* <list type="number">
|
|
1428
|
+
* <item>If <see cref="defaultChartId"/> is explicitly provided (string or null), use it.</item>
|
|
1429
|
+
* <item>Else if <see cref="defaultChartSystemKey"/> is set, pick the first option whose
|
|
1430
|
+
* <c>systemKey</c> matches (e.g. <c>'apps'</c> → the platform Apps chart).</item>
|
|
1431
|
+
* <item>Else fall back to <c>options[0]?.id ?? null</c> (host-defined default order).</item>
|
|
1432
|
+
* </list>
|
|
1433
|
+
* Hosts that already know the tenant-scoped chart Guid use {@link defaultChartId};
|
|
1434
|
+
* hosts that only know the semantic kind (typically <c>'apps'</c>) use
|
|
1435
|
+
* {@link defaultChartSystemKey}.
|
|
1436
|
+
*/
|
|
1437
|
+
defaultChartSystemKey;
|
|
1438
|
+
/**
|
|
1439
|
+
* Hard-coded chart id to pre-select. Takes priority over {@link defaultChartSystemKey}.
|
|
1440
|
+
* Use <c>null</c> to force the default/official chart (id null) even when other charts
|
|
1441
|
+
* are listed first.
|
|
1442
|
+
*/
|
|
1443
|
+
defaultChartId;
|
|
1178
1444
|
/**
|
|
1179
1445
|
* Optional apps-chart role-OU index. When supplied, the picker exposes a "Roles" tab
|
|
1180
1446
|
* surfacing role chips; without it, that tab is hidden even if `'roles'` is in
|
|
@@ -1280,7 +1546,7 @@ class AudienceBuilderComponent {
|
|
|
1280
1546
|
.subscribe({
|
|
1281
1547
|
next: (opts) => {
|
|
1282
1548
|
this.chartOptions.set(opts);
|
|
1283
|
-
const initial = opts
|
|
1549
|
+
const initial = this.resolveInitialChartId(opts);
|
|
1284
1550
|
this.selectedChartId.set(initial);
|
|
1285
1551
|
this.selectedChartId$.next(initial);
|
|
1286
1552
|
},
|
|
@@ -1379,6 +1645,26 @@ class AudienceBuilderComponent {
|
|
|
1379
1645
|
const d = ou.depth ?? 0;
|
|
1380
1646
|
return 12 + d * 18;
|
|
1381
1647
|
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Picks the chart id to seed the OU picker with. Honours explicit {@link defaultChartId}
|
|
1650
|
+
* first (even when null, since null is a valid id meaning "default chart"), then
|
|
1651
|
+
* {@link defaultChartSystemKey}, then falls back to host order. Lookup uses
|
|
1652
|
+
* <see cref="hasOwnProperty"/> on the input keys to distinguish "host did not set this
|
|
1653
|
+
* input" from "host set it to null" — Angular reflects an unset input as <c>undefined</c>.
|
|
1654
|
+
*/
|
|
1655
|
+
resolveInitialChartId(opts) {
|
|
1656
|
+
if (this.defaultChartId !== undefined) {
|
|
1657
|
+
const exists = opts.some((o) => o.id === this.defaultChartId);
|
|
1658
|
+
return exists ? this.defaultChartId : (opts[0]?.id ?? null);
|
|
1659
|
+
}
|
|
1660
|
+
const key = this.defaultChartSystemKey;
|
|
1661
|
+
if (key) {
|
|
1662
|
+
const match = opts.find((o) => o.systemKey === key);
|
|
1663
|
+
if (match)
|
|
1664
|
+
return match.id;
|
|
1665
|
+
}
|
|
1666
|
+
return opts[0]?.id ?? null;
|
|
1667
|
+
}
|
|
1382
1668
|
// ── App-everyone picker ──────────────────────────────────────────────────────
|
|
1383
1669
|
pickAppEveryone(appId) {
|
|
1384
1670
|
this.addToActiveBucket((existing) => this.upsertAppEveryoneTerm(existing, appId));
|
|
@@ -1676,7 +1962,7 @@ class AudienceBuilderComponent {
|
|
|
1676
1962
|
return this.validateFilter(candidate) === null;
|
|
1677
1963
|
}
|
|
1678
1964
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AudienceBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1679
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: AudienceBuilderComponent, isStandalone: true, selector: "fly-audience-builder", inputs: { value: "value", showExcludes: "showExcludes", supportedKinds: "supportedKinds", selfUserId: "selfUserId", appEveryoneOptions: "appEveryoneOptions", searchUsers: "searchUsers", loadOuTree: "loadOuTree", loadChartOptions: "loadChartOptions", loadRoleOus: "loadRoleOus" }, outputs: { audienceChange: "audienceChange", validityChange: "validityChange" }, providers: [
|
|
1965
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: AudienceBuilderComponent, isStandalone: true, selector: "fly-audience-builder", inputs: { value: "value", showExcludes: "showExcludes", supportedKinds: "supportedKinds", selfUserId: "selfUserId", appEveryoneOptions: "appEveryoneOptions", searchUsers: "searchUsers", loadOuTree: "loadOuTree", loadChartOptions: "loadChartOptions", defaultChartSystemKey: "defaultChartSystemKey", defaultChartId: "defaultChartId", loadRoleOus: "loadRoleOus" }, outputs: { audienceChange: "audienceChange", validityChange: "validityChange" }, providers: [
|
|
1680
1966
|
{
|
|
1681
1967
|
provide: NG_VALUE_ACCESSOR,
|
|
1682
1968
|
useExisting: forwardRef(() => AudienceBuilderComponent),
|
|
@@ -1722,6 +2008,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1722
2008
|
}], loadChartOptions: [{
|
|
1723
2009
|
type: Input,
|
|
1724
2010
|
args: [{ required: true }]
|
|
2011
|
+
}], defaultChartSystemKey: [{
|
|
2012
|
+
type: Input
|
|
2013
|
+
}], defaultChartId: [{
|
|
2014
|
+
type: Input
|
|
1725
2015
|
}], loadRoleOus: [{
|
|
1726
2016
|
type: Input
|
|
1727
2017
|
}], audienceChange: [{
|
|
@@ -1741,11 +2031,11 @@ class FlyBlockUiComponent {
|
|
|
1741
2031
|
return k && k.length > 0 ? k : 'common.loading';
|
|
1742
2032
|
}, ...(ngDevMode ? [{ debugName: "resolvedMessageKey" }] : /* istanbul ignore next */ []));
|
|
1743
2033
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyBlockUiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1744
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyBlockUiComponent, isStandalone: true, selector: "fly-block-ui", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent)
|
|
2034
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyBlockUiComponent, isStandalone: true, selector: "fly-block-ui", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1745
2035
|
}
|
|
1746
2036
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyBlockUiComponent, decorators: [{
|
|
1747
2037
|
type: Component,
|
|
1748
|
-
args: [{ selector: 'fly-block-ui', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent)
|
|
2038
|
+
args: [{ selector: 'fly-block-ui', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"] }]
|
|
1749
2039
|
}], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: true }] }], messageKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageKey", required: false }] }] } });
|
|
1750
2040
|
|
|
1751
2041
|
/**
|
|
@@ -1926,110 +2216,110 @@ class FlyImageUploadComponent {
|
|
|
1926
2216
|
});
|
|
1927
2217
|
}
|
|
1928
2218
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyImageUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1929
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyImageUploadComponent, isStandalone: true, selector: "fly-image-upload", inputs: { aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, maxSizeBytes: { classPropertyName: "maxSizeBytes", publicName: "maxSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, currentImageId: { classPropertyName: "currentImageId", publicName: "currentImageId", isSignal: true, isRequired: false, transformFunction: null }, sourceApp: { classPropertyName: "sourceApp", publicName: "sourceApp", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityType: { classPropertyName: "sourceEntityType", publicName: "sourceEntityType", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityId: { classPropertyName: "sourceEntityId", publicName: "sourceEntityId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploaded: "uploaded", removed: "removed" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }, { propertyName: "cropImage", first: true, predicate: ["cropImage"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1930
|
-
<div class="fly-image-upload">
|
|
1931
|
-
@if (previewUrl()) {
|
|
1932
|
-
<div class="fly-image-upload__preview">
|
|
1933
|
-
<img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
|
|
1934
|
-
<div class="fly-image-upload__overlay">
|
|
1935
|
-
<button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
|
|
1936
|
-
<span class="pi pi-pencil" aria-hidden="true"></span>
|
|
1937
|
-
</button>
|
|
1938
|
-
<button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
|
|
1939
|
-
<span class="pi pi-trash" aria-hidden="true"></span>
|
|
1940
|
-
</button>
|
|
1941
|
-
</div>
|
|
1942
|
-
</div>
|
|
1943
|
-
} @else {
|
|
1944
|
-
<button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
|
|
1945
|
-
@if (uploading()) {
|
|
1946
|
-
<span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
|
|
1947
|
-
<span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
|
|
1948
|
-
} @else {
|
|
1949
|
-
<span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
|
|
1950
|
-
<span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
|
|
1951
|
-
<span class="fly-image-upload__hint">{{ sizeHint() }}</span>
|
|
1952
|
-
}
|
|
1953
|
-
</button>
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
@if (error()) {
|
|
1957
|
-
<div class="fly-image-upload__error">{{ error() }}</div>
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
<input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
|
|
1961
|
-
|
|
1962
|
-
@if (showCropper()) {
|
|
1963
|
-
<div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
|
|
1964
|
-
<div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
|
|
1965
|
-
<div class="fly-image-upload__crop-header">
|
|
1966
|
-
<h3>{{ 'files.imageUpload.crop' | translate }}</h3>
|
|
1967
|
-
</div>
|
|
1968
|
-
<div class="fly-image-upload__crop-body">
|
|
1969
|
-
<img #cropImage [src]="rawImageUrl()" alt="" />
|
|
1970
|
-
</div>
|
|
1971
|
-
<div class="fly-image-upload__crop-footer">
|
|
1972
|
-
<button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
|
|
1973
|
-
<button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
|
|
1974
|
-
</div>
|
|
1975
|
-
</div>
|
|
1976
|
-
</div>
|
|
1977
|
-
}
|
|
1978
|
-
</div>
|
|
2219
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyImageUploadComponent, isStandalone: true, selector: "fly-image-upload", inputs: { aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, maxSizeBytes: { classPropertyName: "maxSizeBytes", publicName: "maxSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, currentImageId: { classPropertyName: "currentImageId", publicName: "currentImageId", isSignal: true, isRequired: false, transformFunction: null }, sourceApp: { classPropertyName: "sourceApp", publicName: "sourceApp", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityType: { classPropertyName: "sourceEntityType", publicName: "sourceEntityType", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityId: { classPropertyName: "sourceEntityId", publicName: "sourceEntityId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploaded: "uploaded", removed: "removed" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }, { propertyName: "cropImage", first: true, predicate: ["cropImage"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
2220
|
+
<div class="fly-image-upload">
|
|
2221
|
+
@if (previewUrl()) {
|
|
2222
|
+
<div class="fly-image-upload__preview">
|
|
2223
|
+
<img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
|
|
2224
|
+
<div class="fly-image-upload__overlay">
|
|
2225
|
+
<button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
|
|
2226
|
+
<span class="pi pi-pencil" aria-hidden="true"></span>
|
|
2227
|
+
</button>
|
|
2228
|
+
<button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
|
|
2229
|
+
<span class="pi pi-trash" aria-hidden="true"></span>
|
|
2230
|
+
</button>
|
|
2231
|
+
</div>
|
|
2232
|
+
</div>
|
|
2233
|
+
} @else {
|
|
2234
|
+
<button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
|
|
2235
|
+
@if (uploading()) {
|
|
2236
|
+
<span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
|
|
2237
|
+
<span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
|
|
2238
|
+
} @else {
|
|
2239
|
+
<span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
|
|
2240
|
+
<span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
|
|
2241
|
+
<span class="fly-image-upload__hint">{{ sizeHint() }}</span>
|
|
2242
|
+
}
|
|
2243
|
+
</button>
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
@if (error()) {
|
|
2247
|
+
<div class="fly-image-upload__error">{{ error() }}</div>
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
<input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
|
|
2251
|
+
|
|
2252
|
+
@if (showCropper()) {
|
|
2253
|
+
<div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
|
|
2254
|
+
<div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
|
|
2255
|
+
<div class="fly-image-upload__crop-header">
|
|
2256
|
+
<h3>{{ 'files.imageUpload.crop' | translate }}</h3>
|
|
2257
|
+
</div>
|
|
2258
|
+
<div class="fly-image-upload__crop-body">
|
|
2259
|
+
<img #cropImage [src]="rawImageUrl()" alt="" />
|
|
2260
|
+
</div>
|
|
2261
|
+
<div class="fly-image-upload__crop-footer">
|
|
2262
|
+
<button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
|
|
2263
|
+
<button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
|
|
2264
|
+
</div>
|
|
2265
|
+
</div>
|
|
2266
|
+
</div>
|
|
2267
|
+
}
|
|
2268
|
+
</div>
|
|
1979
2269
|
`, isInline: true, styles: [".cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{inset:0;position:absolute}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:\" \";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}\n", ".fly-image-upload{display:flex;flex-direction:column;gap:6px}.fly-image-upload__hidden{display:none}.fly-image-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:24px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:12px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s;min-height:120px}.fly-image-upload__dropzone:hover{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__icon{font-size:28px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-image-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__preview{position:relative;border-radius:12px;overflow:hidden}.fly-image-upload__img{display:block;width:100%;max-height:280px;object-fit:cover;border-radius:12px}.fly-image-upload__overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:12px;background:#00000059;opacity:0;transition:opacity .15s}.fly-image-upload__preview:hover .fly-image-upload__overlay{opacity:1}.fly-image-upload__action-btn{width:36px;height:36px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:#ffffffe6;color:var(--label-primary, #1f2937);font-size:14px;transition:background .15s}.fly-image-upload__action-btn:hover{background:#fff}.fly-image-upload__action-btn--danger:hover{background:#fee2e2;color:#dc2626}.fly-image-upload__error{font-size:12px;color:var(--system-red, #ef4444)}.fly-image-upload__crop-backdrop{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:#0009;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.fly-image-upload__crop-modal{background:var(--bg-primary, #fff);color:var(--label-primary, #1f2937);border-radius:16px;overflow:hidden;max-width:700px;width:90vw;box-shadow:0 20px 60px #0006;border:1px solid var(--separator-primary, rgba(0,0,0,.1))}.fly-image-upload__crop-header{padding:16px 20px;border-bottom:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}.fly-image-upload__crop-header h3{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1f2937)}.fly-image-upload__crop-body{max-height:60vh;overflow:hidden;background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__crop-body img{display:block;max-width:100%}.fly-image-upload__crop-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}html.spatial-theme .fly-image-upload__crop-modal{background:#ffffffa6;backdrop-filter:blur(40px) saturate(1.8);-webkit-backdrop-filter:blur(40px) saturate(1.8);border:1px solid rgba(255,255,255,.4)}html.spatial-theme .fly-image-upload__crop-header,html.spatial-theme .fly-image-upload__crop-footer{background:transparent}html.spatial-theme .fly-image-upload__crop-body{background:#0000000d}html.dark-theme .fly-image-upload__crop-modal{background:var(--bg-primary, #1c1c1e);border:1px solid var(--separator-primary, rgba(255,255,255,.1))}html.dark-theme .fly-image-upload__crop-body{background:var(--fill-tertiary, #2c2c2e)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1980
2270
|
}
|
|
1981
2271
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyImageUploadComponent, decorators: [{
|
|
1982
2272
|
type: Component,
|
|
1983
|
-
args: [{ selector: 'fly-image-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
1984
|
-
<div class="fly-image-upload">
|
|
1985
|
-
@if (previewUrl()) {
|
|
1986
|
-
<div class="fly-image-upload__preview">
|
|
1987
|
-
<img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
|
|
1988
|
-
<div class="fly-image-upload__overlay">
|
|
1989
|
-
<button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
|
|
1990
|
-
<span class="pi pi-pencil" aria-hidden="true"></span>
|
|
1991
|
-
</button>
|
|
1992
|
-
<button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
|
|
1993
|
-
<span class="pi pi-trash" aria-hidden="true"></span>
|
|
1994
|
-
</button>
|
|
1995
|
-
</div>
|
|
1996
|
-
</div>
|
|
1997
|
-
} @else {
|
|
1998
|
-
<button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
|
|
1999
|
-
@if (uploading()) {
|
|
2000
|
-
<span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
|
|
2001
|
-
<span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
|
|
2002
|
-
} @else {
|
|
2003
|
-
<span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
|
|
2004
|
-
<span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
|
|
2005
|
-
<span class="fly-image-upload__hint">{{ sizeHint() }}</span>
|
|
2006
|
-
}
|
|
2007
|
-
</button>
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
@if (error()) {
|
|
2011
|
-
<div class="fly-image-upload__error">{{ error() }}</div>
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
<input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
|
|
2015
|
-
|
|
2016
|
-
@if (showCropper()) {
|
|
2017
|
-
<div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
|
|
2018
|
-
<div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
|
|
2019
|
-
<div class="fly-image-upload__crop-header">
|
|
2020
|
-
<h3>{{ 'files.imageUpload.crop' | translate }}</h3>
|
|
2021
|
-
</div>
|
|
2022
|
-
<div class="fly-image-upload__crop-body">
|
|
2023
|
-
<img #cropImage [src]="rawImageUrl()" alt="" />
|
|
2024
|
-
</div>
|
|
2025
|
-
<div class="fly-image-upload__crop-footer">
|
|
2026
|
-
<button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
|
|
2027
|
-
<button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
|
|
2028
|
-
</div>
|
|
2029
|
-
</div>
|
|
2030
|
-
</div>
|
|
2031
|
-
}
|
|
2032
|
-
</div>
|
|
2273
|
+
args: [{ selector: 'fly-image-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
2274
|
+
<div class="fly-image-upload">
|
|
2275
|
+
@if (previewUrl()) {
|
|
2276
|
+
<div class="fly-image-upload__preview">
|
|
2277
|
+
<img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
|
|
2278
|
+
<div class="fly-image-upload__overlay">
|
|
2279
|
+
<button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
|
|
2280
|
+
<span class="pi pi-pencil" aria-hidden="true"></span>
|
|
2281
|
+
</button>
|
|
2282
|
+
<button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
|
|
2283
|
+
<span class="pi pi-trash" aria-hidden="true"></span>
|
|
2284
|
+
</button>
|
|
2285
|
+
</div>
|
|
2286
|
+
</div>
|
|
2287
|
+
} @else {
|
|
2288
|
+
<button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
|
|
2289
|
+
@if (uploading()) {
|
|
2290
|
+
<span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
|
|
2291
|
+
<span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
|
|
2292
|
+
} @else {
|
|
2293
|
+
<span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
|
|
2294
|
+
<span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
|
|
2295
|
+
<span class="fly-image-upload__hint">{{ sizeHint() }}</span>
|
|
2296
|
+
}
|
|
2297
|
+
</button>
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
@if (error()) {
|
|
2301
|
+
<div class="fly-image-upload__error">{{ error() }}</div>
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
<input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
|
|
2305
|
+
|
|
2306
|
+
@if (showCropper()) {
|
|
2307
|
+
<div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
|
|
2308
|
+
<div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
|
|
2309
|
+
<div class="fly-image-upload__crop-header">
|
|
2310
|
+
<h3>{{ 'files.imageUpload.crop' | translate }}</h3>
|
|
2311
|
+
</div>
|
|
2312
|
+
<div class="fly-image-upload__crop-body">
|
|
2313
|
+
<img #cropImage [src]="rawImageUrl()" alt="" />
|
|
2314
|
+
</div>
|
|
2315
|
+
<div class="fly-image-upload__crop-footer">
|
|
2316
|
+
<button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
|
|
2317
|
+
<button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
|
|
2318
|
+
</div>
|
|
2319
|
+
</div>
|
|
2320
|
+
</div>
|
|
2321
|
+
}
|
|
2322
|
+
</div>
|
|
2033
2323
|
`, styles: [".cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{inset:0;position:absolute}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:\" \";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}\n", ".fly-image-upload{display:flex;flex-direction:column;gap:6px}.fly-image-upload__hidden{display:none}.fly-image-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:24px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:12px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s;min-height:120px}.fly-image-upload__dropzone:hover{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__icon{font-size:28px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-image-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__preview{position:relative;border-radius:12px;overflow:hidden}.fly-image-upload__img{display:block;width:100%;max-height:280px;object-fit:cover;border-radius:12px}.fly-image-upload__overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:12px;background:#00000059;opacity:0;transition:opacity .15s}.fly-image-upload__preview:hover .fly-image-upload__overlay{opacity:1}.fly-image-upload__action-btn{width:36px;height:36px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:#ffffffe6;color:var(--label-primary, #1f2937);font-size:14px;transition:background .15s}.fly-image-upload__action-btn:hover{background:#fff}.fly-image-upload__action-btn--danger:hover{background:#fee2e2;color:#dc2626}.fly-image-upload__error{font-size:12px;color:var(--system-red, #ef4444)}.fly-image-upload__crop-backdrop{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:#0009;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.fly-image-upload__crop-modal{background:var(--bg-primary, #fff);color:var(--label-primary, #1f2937);border-radius:16px;overflow:hidden;max-width:700px;width:90vw;box-shadow:0 20px 60px #0006;border:1px solid var(--separator-primary, rgba(0,0,0,.1))}.fly-image-upload__crop-header{padding:16px 20px;border-bottom:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}.fly-image-upload__crop-header h3{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1f2937)}.fly-image-upload__crop-body{max-height:60vh;overflow:hidden;background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__crop-body img{display:block;max-width:100%}.fly-image-upload__crop-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}html.spatial-theme .fly-image-upload__crop-modal{background:#ffffffa6;backdrop-filter:blur(40px) saturate(1.8);-webkit-backdrop-filter:blur(40px) saturate(1.8);border:1px solid rgba(255,255,255,.4)}html.spatial-theme .fly-image-upload__crop-header,html.spatial-theme .fly-image-upload__crop-footer{background:transparent}html.spatial-theme .fly-image-upload__crop-body{background:#0000000d}html.dark-theme .fly-image-upload__crop-modal{background:var(--bg-primary, #1c1c1e);border:1px solid var(--separator-primary, rgba(255,255,255,.1))}html.dark-theme .fly-image-upload__crop-body{background:var(--fill-tertiary, #2c2c2e)}\n"] }]
|
|
2034
2324
|
}], ctorParameters: () => [], propDecorators: { aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], maxSizeBytes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSizeBytes", required: false }] }], currentImageId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentImageId", required: false }] }], sourceApp: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceApp", required: false }] }], sourceEntityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityType", required: false }] }], sourceEntityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityId", required: false }] }], uploaded: [{ type: i0.Output, args: ["uploaded"] }], removed: [{ type: i0.Output, args: ["removed"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], cropImage: [{ type: i0.ViewChild, args: ['cropImage', { isSignal: true }] }] } });
|
|
2035
2325
|
|
|
@@ -2365,6 +2655,1113 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
2365
2655
|
`, styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:10px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s}.fly-file-upload__dropzone:hover,.fly-file-upload__dropzone--drag{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-file-upload__icon{font-size:24px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-file-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__error{font-size:12px;color:var(--system-red, #ef4444);padding:4px 0}.fly-file-upload__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.fly-file-upload__item{display:flex;align-items:center;gap:10px;padding:8px 12px;border:1px solid var(--separator-primary, #e5e7eb);border-radius:8px;background:var(--fill-quaternary, #f9fafb);font-size:13px}.fly-file-upload__file-icon{font-size:18px;color:var(--label-tertiary, #9ca3af);flex-shrink:0}.fly-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fly-file-upload__file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--label-primary, #1f2937)}.fly-file-upload__file-size{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__progress-track{width:60px;height:4px;border-radius:2px;background:var(--fill-tertiary, #e5e7eb);overflow:hidden}.fly-file-upload__progress-fill{height:100%;background:var(--accent-primary, #0071e3);border-radius:2px;transition:width .2s}.fly-file-upload__file-ok{color:var(--system-green, #22c55e);font-size:14px}.fly-file-upload__file-error{color:var(--system-red, #ef4444);font-size:14px}.fly-file-upload__remove-btn{background:none;border:none;cursor:pointer;color:var(--label-tertiary, #9ca3af);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:color .15s,background .15s;flex-shrink:0}.fly-file-upload__remove-btn:hover{color:var(--system-red, #ef4444);background:var(--fill-tertiary, #f3f4f6)}\n"] }]
|
|
2366
2656
|
}], ctorParameters: () => [], propDecorators: { maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], maxFileSizeBytes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSizeBytes", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], sourceApp: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceApp", required: false }] }], sourceEntityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityType", required: false }] }], sourceEntityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityId", required: false }] }], existingFileIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFileIds", required: false }] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }, { type: i0.Output, args: ["filesChange"] }], filesChanged: [{ type: i0.Output, args: ["filesChanged"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
|
|
2367
2657
|
|
|
2658
|
+
/**
|
|
2659
|
+
* Singleton registry of slash commands offered by the agent input palette.
|
|
2660
|
+
*
|
|
2661
|
+
* Lives in the DS so it crosses the federation boundary as a single instance via
|
|
2662
|
+
* `sharedMappings: ['@mohamedatia/fly-design-system']`. Federated remotes register
|
|
2663
|
+
* their commands at boot (and dispose on window close) without forking the shell.
|
|
2664
|
+
*
|
|
2665
|
+
* Storage is a signal store. `register` is O(1) for the unique-id case and O(n) when
|
|
2666
|
+
* replacing an existing id (filter then push). The append-then-replace strategy is
|
|
2667
|
+
* deliberate: registrations are rare (each one corresponds to a remote's app-init
|
|
2668
|
+
* effect), and the tradeoff buys us a stable id-collision contract — the *latest*
|
|
2669
|
+
* registration wins, and the previous handle's `dispose()` becomes a no-op rather
|
|
2670
|
+
* than removing the new entry.
|
|
2671
|
+
*/
|
|
2672
|
+
class AgentCommandRegistry {
|
|
2673
|
+
_commands = signal([], ...(ngDevMode ? [{ debugName: "_commands" }] : /* istanbul ignore next */ []));
|
|
2674
|
+
/** All currently-registered commands, in insertion order. */
|
|
2675
|
+
all = this._commands.asReadonly();
|
|
2676
|
+
/**
|
|
2677
|
+
* Returns a signal of commands whose scope is `'global'` OR whose `scope.appId` is in
|
|
2678
|
+
* the live app set. The signal recomputes when either the registry or `liveAppIds`
|
|
2679
|
+
* changes — pass a `Signal<ReadonlySet<string>>` from the host's app-registry service
|
|
2680
|
+
* for reactive filtering.
|
|
2681
|
+
*/
|
|
2682
|
+
visible(liveAppIds) {
|
|
2683
|
+
const liveSignal = isSignal(liveAppIds) ? liveAppIds : signal(liveAppIds).asReadonly();
|
|
2684
|
+
return computed(() => {
|
|
2685
|
+
const live = liveSignal();
|
|
2686
|
+
return this._commands().filter((cmd) => {
|
|
2687
|
+
if (cmd.scope === 'global')
|
|
2688
|
+
return true;
|
|
2689
|
+
return live.has(cmd.scope.appId);
|
|
2690
|
+
});
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
/**
|
|
2694
|
+
* Register a single command. Returns a handle whose `dispose()` removes the row by
|
|
2695
|
+
* id. If the same id is later re-registered, the original handle's `dispose()`
|
|
2696
|
+
* becomes a no-op (the newer registration owns the row). Idempotent disposal.
|
|
2697
|
+
*/
|
|
2698
|
+
register(cmd) {
|
|
2699
|
+
const generation = ++this._generation;
|
|
2700
|
+
this._commands.update((rows) => [...rows.filter((r) => r.id !== cmd.id), cmd]);
|
|
2701
|
+
this._owners.set(cmd.id, generation);
|
|
2702
|
+
return {
|
|
2703
|
+
dispose: () => {
|
|
2704
|
+
if (this._owners.get(cmd.id) === generation) {
|
|
2705
|
+
this._owners.delete(cmd.id);
|
|
2706
|
+
this._commands.update((rows) => rows.filter((r) => r.id !== cmd.id));
|
|
2707
|
+
}
|
|
2708
|
+
},
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Bulk register. Rolls back on duplicate id within the input batch (throws before any
|
|
2713
|
+
* row lands). Cross-batch duplicates against existing rows follow the standard
|
|
2714
|
+
* "latest wins" rule and do NOT trigger rollback.
|
|
2715
|
+
*
|
|
2716
|
+
* Returns a handle whose `dispose()` tears down every row registered by this call.
|
|
2717
|
+
*/
|
|
2718
|
+
registerAll(cmds) {
|
|
2719
|
+
const seen = new Set();
|
|
2720
|
+
for (const c of cmds) {
|
|
2721
|
+
if (seen.has(c.id)) {
|
|
2722
|
+
throw new Error(`AgentCommandRegistry.registerAll: duplicate id "${c.id}" in batch`);
|
|
2723
|
+
}
|
|
2724
|
+
seen.add(c.id);
|
|
2725
|
+
}
|
|
2726
|
+
const handles = cmds.map((c) => this.register(c));
|
|
2727
|
+
let disposed = false;
|
|
2728
|
+
return {
|
|
2729
|
+
dispose: () => {
|
|
2730
|
+
if (disposed)
|
|
2731
|
+
return;
|
|
2732
|
+
disposed = true;
|
|
2733
|
+
for (const h of handles)
|
|
2734
|
+
h.dispose();
|
|
2735
|
+
},
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
/** Tear down by id. Idempotent. */
|
|
2739
|
+
unregister(id) {
|
|
2740
|
+
if (this._owners.delete(id)) {
|
|
2741
|
+
this._commands.update((rows) => rows.filter((r) => r.id !== id));
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
/** Monotonic counter; identifies which registration call currently owns each id. */
|
|
2745
|
+
_generation = 0;
|
|
2746
|
+
/** id → generation. Used so a stale handle's `dispose()` is a no-op after replacement. */
|
|
2747
|
+
_owners = new Map();
|
|
2748
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentCommandRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2749
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentCommandRegistry, providedIn: 'root' });
|
|
2750
|
+
}
|
|
2751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentCommandRegistry, decorators: [{
|
|
2752
|
+
type: Injectable,
|
|
2753
|
+
args: [{ providedIn: 'root' }]
|
|
2754
|
+
}] });
|
|
2755
|
+
/** `isSignal` shim — narrows to either `Signal<T>` or a plain value. */
|
|
2756
|
+
function isSignal(v) {
|
|
2757
|
+
return typeof v === 'function';
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
/**
|
|
2761
|
+
* Singleton registry of chip-renderer components and keyboard-alternative draggable
|
|
2762
|
+
* items, keyed by `kind` and `appId`.
|
|
2763
|
+
*
|
|
2764
|
+
* Like {@link AgentCommandRegistry}, this lives in the DS so it crosses the federation
|
|
2765
|
+
* boundary as a single instance. Hosts (the shell `<fly-agent-input>`) lookup
|
|
2766
|
+
* renderers; remotes register them.
|
|
2767
|
+
*
|
|
2768
|
+
* Renderer lookup is `O(n)` over the registered list — the registry is small (one or
|
|
2769
|
+
* two entries per app) and lookups happen on drop, not per frame.
|
|
2770
|
+
*
|
|
2771
|
+
* Draggable storage is per-`appId` writable signal cached in a Map, so each `appId`
|
|
2772
|
+
* gets a stable {@link Signal} reference across reads (callers can `===`-compare).
|
|
2773
|
+
*/
|
|
2774
|
+
class AgentDropRegistry {
|
|
2775
|
+
_renderers = signal([], ...(ngDevMode ? [{ debugName: "_renderers" }] : /* istanbul ignore next */ []));
|
|
2776
|
+
/** Per-appId writable store for draggables. Read-only mirror returned to callers. */
|
|
2777
|
+
_draggablesByApp = new Map();
|
|
2778
|
+
/**
|
|
2779
|
+
* Look up the chip-renderer component class for a `kind`. The newest registration for
|
|
2780
|
+
* a given `kind` wins, regardless of `appId` — we scan the list in reverse so a later
|
|
2781
|
+
* `register` call shadows an earlier one for the same `kind`.
|
|
2782
|
+
*
|
|
2783
|
+
* Returns `null` when no renderer is registered; the host falls back to a generic
|
|
2784
|
+
* `plainTextFallback` chip.
|
|
2785
|
+
*/
|
|
2786
|
+
rendererFor(kind) {
|
|
2787
|
+
const list = this._renderers();
|
|
2788
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
2789
|
+
if (list[i].kind === kind)
|
|
2790
|
+
return list[i].component;
|
|
2791
|
+
}
|
|
2792
|
+
return null;
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Register a renderer for a `(kind, appId)` pair. Re-registering the same pair
|
|
2796
|
+
* replaces the prior entry; the disposal handle for the prior registration becomes
|
|
2797
|
+
* a no-op.
|
|
2798
|
+
*
|
|
2799
|
+
* Returns a {@link AgentCommandHandle} (re-used to keep the disposable shape uniform
|
|
2800
|
+
* across registries) whose `dispose()` removes this exact registration.
|
|
2801
|
+
*/
|
|
2802
|
+
register(reg) {
|
|
2803
|
+
const generation = ++this._generation;
|
|
2804
|
+
const key = compositeKey(reg.kind, reg.appId);
|
|
2805
|
+
this._renderers.update((rows) => [
|
|
2806
|
+
...rows.filter((r) => compositeKey(r.kind, r.appId) !== key),
|
|
2807
|
+
reg,
|
|
2808
|
+
]);
|
|
2809
|
+
this._owners.set(key, generation);
|
|
2810
|
+
return {
|
|
2811
|
+
dispose: () => {
|
|
2812
|
+
if (this._owners.get(key) === generation) {
|
|
2813
|
+
this._owners.delete(key);
|
|
2814
|
+
this._renderers.update((rows) => rows.filter((r) => compositeKey(r.kind, r.appId) !== key));
|
|
2815
|
+
}
|
|
2816
|
+
},
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
/**
|
|
2820
|
+
* Apps publish their live "draggable from focused window" set so the keyboard
|
|
2821
|
+
* "Attach from app…" menu can offer them. Hosts call this each time the user-visible
|
|
2822
|
+
* draggable list changes; passing an empty array clears the entry for this `appId`.
|
|
2823
|
+
*/
|
|
2824
|
+
publishDraggables(appId, items) {
|
|
2825
|
+
if (items.length === 0) {
|
|
2826
|
+
const existing = this._draggablesByApp.get(appId);
|
|
2827
|
+
if (existing)
|
|
2828
|
+
existing.set([]);
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
this.bucketFor(appId).set(items);
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Reactive read of the draggable set published for `appId`. Empty when none. The
|
|
2835
|
+
* returned signal is stable across calls (cached by `appId`), so consumers can use
|
|
2836
|
+
* it as a stable input to `computed()`.
|
|
2837
|
+
*/
|
|
2838
|
+
draggablesFor(appId) {
|
|
2839
|
+
return this.bucketFor(appId).asReadonly();
|
|
2840
|
+
}
|
|
2841
|
+
/** Lazy-init the per-appId writable bucket. Returns the writable handle for internal use. */
|
|
2842
|
+
bucketFor(appId) {
|
|
2843
|
+
let bucket = this._draggablesByApp.get(appId);
|
|
2844
|
+
if (!bucket) {
|
|
2845
|
+
bucket = signal([]);
|
|
2846
|
+
this._draggablesByApp.set(appId, bucket);
|
|
2847
|
+
}
|
|
2848
|
+
return bucket;
|
|
2849
|
+
}
|
|
2850
|
+
_generation = 0;
|
|
2851
|
+
_owners = new Map();
|
|
2852
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentDropRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2853
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentDropRegistry, providedIn: 'root' });
|
|
2854
|
+
}
|
|
2855
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentDropRegistry, decorators: [{
|
|
2856
|
+
type: Injectable,
|
|
2857
|
+
args: [{ providedIn: 'root' }]
|
|
2858
|
+
}] });
|
|
2859
|
+
function compositeKey(kind, appId) {
|
|
2860
|
+
return `${kind}::${appId}`;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
/**
|
|
2864
|
+
* Agent input contracts.
|
|
2865
|
+
*
|
|
2866
|
+
* These types define the wire and DI surface for the new `<fly-agent-input>` orchestrator
|
|
2867
|
+
* (Phase 2+). Phase 1 ships only the contracts: payload envelope, command/drop registry
|
|
2868
|
+
* shapes, and chip-host inputs. The concrete components consume them later.
|
|
2869
|
+
*
|
|
2870
|
+
* Design rules:
|
|
2871
|
+
* - All fields are `readonly` — the registries store these in signal stores and we never
|
|
2872
|
+
* want a host mutating after registration. Hosts construct fresh objects to update.
|
|
2873
|
+
* - The discriminated kinds (`scope`, `mode`, `kind`) carry the entire variance — no
|
|
2874
|
+
* boolean flags. New variants extend the union without breaking existing consumers.
|
|
2875
|
+
* - The `Type<AgentChipHostInputs<T>>` shape is intentional: chip components declare
|
|
2876
|
+
* `payload`, `mode`, optional `onRemove` as Angular `input()` signals; the registry
|
|
2877
|
+
* wires them via `NgComponentOutlet` inputs in the consumer. The component reference
|
|
2878
|
+
* itself never leaks across the federation boundary by value — only by class identity.
|
|
2879
|
+
*/
|
|
2880
|
+
/** Frozen MIME used by `flyAgentDraggable` and the drop-zone reader. Never change without a DS major. */
|
|
2881
|
+
const AGENT_DRAG_MIME = 'application/x-fly-agent-payload+json';
|
|
2882
|
+
/** Frozen payload envelope version. Bump only with a published DS major. */
|
|
2883
|
+
const AGENT_PAYLOAD_VERSION = 1;
|
|
2884
|
+
const DEFAULT_AGENT_PAYLOAD_LIMITS = Object.freeze({
|
|
2885
|
+
maxJsonBytes: 32 * 1024,
|
|
2886
|
+
maxPlainTextFallbackBytes: 8 * 1024,
|
|
2887
|
+
maxStringFieldBytes: 4 * 1024,
|
|
2888
|
+
});
|
|
2889
|
+
|
|
2890
|
+
/**
|
|
2891
|
+
* Pure utility helpers for agent drag payload size guarding.
|
|
2892
|
+
*
|
|
2893
|
+
* Used by `flyAgentDraggable` at `dragstart` (Phase 2) and by app pre-flight code that
|
|
2894
|
+
* wants a clean payload before constructing the directive input. No Angular DI, no
|
|
2895
|
+
* runtime side effects — safe to import from anywhere.
|
|
2896
|
+
*
|
|
2897
|
+
* Performance notes:
|
|
2898
|
+
* - The `TextEncoder` is lazily allocated and cached. The constructor runs once per
|
|
2899
|
+
* module instance even under federation (singleton DS).
|
|
2900
|
+
* - String trimming uses `Intl.Segmenter` when available (all evergreen browsers
|
|
2901
|
+
* including Safari 14.1+) so we cut on grapheme boundaries — emoji ZWJ sequences,
|
|
2902
|
+
* combining marks, regional indicator pairs all stay intact. Fallback is
|
|
2903
|
+
* UTF-16 code-unit slice, which is a graceful degradation: it can split a
|
|
2904
|
+
* surrogate pair, but the resulting JSON is still syntactically valid and
|
|
2905
|
+
* downstream renderers handle the replacement char.
|
|
2906
|
+
*/
|
|
2907
|
+
/**
|
|
2908
|
+
* Thrown by {@link trimAgentPayload} when even a fully string-trimmed envelope still
|
|
2909
|
+
* exceeds `maxJsonBytes` (e.g. an envelope with thousands of nested empty objects /
|
|
2910
|
+
* array entries / property keys). Subclasses `RangeError` so callers using the standard
|
|
2911
|
+
* `instanceof RangeError` check still match, while exposing the structured fields
|
|
2912
|
+
* (`actualBytes`, `limitBytes`, `reason: 'json_too_large'`) needed for telemetry and
|
|
2913
|
+
* symmetric handling with {@link AgentPayloadValidationResult} failures.
|
|
2914
|
+
*/
|
|
2915
|
+
class AgentPayloadOversizeError extends RangeError {
|
|
2916
|
+
actualBytes;
|
|
2917
|
+
limitBytes;
|
|
2918
|
+
reason = 'json_too_large';
|
|
2919
|
+
constructor(message, actualBytes, limitBytes) {
|
|
2920
|
+
super(message);
|
|
2921
|
+
this.actualBytes = actualBytes;
|
|
2922
|
+
this.limitBytes = limitBytes;
|
|
2923
|
+
this.name = 'AgentPayloadOversizeError';
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
/** Cap on the dotted `fieldPath` string so a 10 MB property-key payload can't lock dev tools when `console.warn`d. */
|
|
2927
|
+
const FIELD_PATH_MAX_CHARS = 256;
|
|
2928
|
+
/** Append a path segment, capping the result at {@link FIELD_PATH_MAX_CHARS} chars (with `…` marker). */
|
|
2929
|
+
function appendPath(base, segment) {
|
|
2930
|
+
const next = base + segment;
|
|
2931
|
+
if (next.length <= FIELD_PATH_MAX_CHARS)
|
|
2932
|
+
return next;
|
|
2933
|
+
return next.slice(0, FIELD_PATH_MAX_CHARS - 1) + '…';
|
|
2934
|
+
}
|
|
2935
|
+
let _cachedEncoder = null;
|
|
2936
|
+
function getEncoder() {
|
|
2937
|
+
return (_cachedEncoder ??= new TextEncoder());
|
|
2938
|
+
}
|
|
2939
|
+
/** UTF-8 byte length of `s`. Cheap; cached encoder. */
|
|
2940
|
+
function utf8ByteLength(s) {
|
|
2941
|
+
return getEncoder().encode(s).length;
|
|
2942
|
+
}
|
|
2943
|
+
/**
|
|
2944
|
+
* Trim `input` so the UTF-8 byte length of the result is `<= maxBytes`. When trimming
|
|
2945
|
+
* occurs, append `ellipsis` (default `…`, U+2026) — its bytes are included in the cap.
|
|
2946
|
+
*
|
|
2947
|
+
* Idempotent: `trimAgentString(trimAgentString(s, n), n)` equals `trimAgentString(s, n)`.
|
|
2948
|
+
*
|
|
2949
|
+
* Edge cases:
|
|
2950
|
+
* - `maxBytes` shorter than the ellipsis itself returns an empty string.
|
|
2951
|
+
* - `Intl.Segmenter` (when present) ensures we never split a grapheme cluster.
|
|
2952
|
+
* - When the encoded length is already within the cap, returns `input` unchanged.
|
|
2953
|
+
*/
|
|
2954
|
+
function trimAgentString(input, maxBytes, ellipsis = '…') {
|
|
2955
|
+
if (maxBytes <= 0)
|
|
2956
|
+
return '';
|
|
2957
|
+
const enc = getEncoder();
|
|
2958
|
+
const inputLen = enc.encode(input).length;
|
|
2959
|
+
if (inputLen <= maxBytes)
|
|
2960
|
+
return input;
|
|
2961
|
+
const ellipsisLen = enc.encode(ellipsis).length;
|
|
2962
|
+
if (maxBytes < ellipsisLen)
|
|
2963
|
+
return '';
|
|
2964
|
+
const budget = maxBytes - ellipsisLen;
|
|
2965
|
+
// Segmenter path: walk graphemes, accumulate until next would overflow.
|
|
2966
|
+
const segmenterCtor = typeof Intl !== 'undefined' && Intl.Segmenter;
|
|
2967
|
+
if (segmenterCtor) {
|
|
2968
|
+
const segmenter = new segmenterCtor(undefined, { granularity: 'grapheme' });
|
|
2969
|
+
let used = 0;
|
|
2970
|
+
let acc = '';
|
|
2971
|
+
for (const seg of segmenter.segment(input)) {
|
|
2972
|
+
const segLen = enc.encode(seg.segment).length;
|
|
2973
|
+
if (used + segLen > budget)
|
|
2974
|
+
break;
|
|
2975
|
+
acc += seg.segment;
|
|
2976
|
+
used += segLen;
|
|
2977
|
+
}
|
|
2978
|
+
return acc + ellipsis;
|
|
2979
|
+
}
|
|
2980
|
+
// Fallback: code-unit slice; verify byte length and shrink until under budget.
|
|
2981
|
+
let cut = input.length;
|
|
2982
|
+
while (cut > 0 && enc.encode(input.slice(0, cut)).length > budget) {
|
|
2983
|
+
cut--;
|
|
2984
|
+
}
|
|
2985
|
+
return input.slice(0, cut) + ellipsis;
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Validate an {@link AgentDragPayload} against the supplied limits (or
|
|
2989
|
+
* {@link DEFAULT_AGENT_PAYLOAD_LIMITS} when omitted). Returns `{ ok: true }` or a
|
|
2990
|
+
* structured failure with the offending field path and byte counts.
|
|
2991
|
+
*
|
|
2992
|
+
* Order of checks (cheap → expensive):
|
|
2993
|
+
* 1. `version` matches the frozen {@link AGENT_PAYLOAD_VERSION}.
|
|
2994
|
+
* 2. `kind` is a non-empty string.
|
|
2995
|
+
* 3. `plainTextFallback` fits its cap.
|
|
2996
|
+
* 4. Every string in `payload` (recursively) fits the per-field cap.
|
|
2997
|
+
* 5. The full `JSON.stringify(envelope)` fits the JSON cap.
|
|
2998
|
+
*
|
|
2999
|
+
* The walker stops on the first oversize string field — the intent is to surface
|
|
3000
|
+
* actionable feedback, not enumerate every offender.
|
|
3001
|
+
*/
|
|
3002
|
+
function validateAgentPayload(payload, limits) {
|
|
3003
|
+
const eff = { ...DEFAULT_AGENT_PAYLOAD_LIMITS, ...(limits ?? {}) };
|
|
3004
|
+
if (payload.version !== AGENT_PAYLOAD_VERSION) {
|
|
3005
|
+
return { ok: false, reason: 'invalid_version' };
|
|
3006
|
+
}
|
|
3007
|
+
if (typeof payload.kind !== 'string' || payload.kind.length === 0) {
|
|
3008
|
+
return { ok: false, reason: 'invalid_kind' };
|
|
3009
|
+
}
|
|
3010
|
+
const fbBytes = utf8ByteLength(payload.plainTextFallback ?? '');
|
|
3011
|
+
if (fbBytes > eff.maxPlainTextFallbackBytes) {
|
|
3012
|
+
return {
|
|
3013
|
+
ok: false,
|
|
3014
|
+
reason: 'fallback_too_large',
|
|
3015
|
+
fieldPath: 'plainTextFallback',
|
|
3016
|
+
actualBytes: fbBytes,
|
|
3017
|
+
limitBytes: eff.maxPlainTextFallbackBytes,
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
const fieldFailure = walkStringsForOverage(payload.payload, 'payload', eff.maxStringFieldBytes);
|
|
3021
|
+
if (fieldFailure)
|
|
3022
|
+
return fieldFailure;
|
|
3023
|
+
const json = safeStringify(payload);
|
|
3024
|
+
const jsonBytes = utf8ByteLength(json);
|
|
3025
|
+
if (jsonBytes > eff.maxJsonBytes) {
|
|
3026
|
+
return {
|
|
3027
|
+
ok: false,
|
|
3028
|
+
reason: 'json_too_large',
|
|
3029
|
+
actualBytes: jsonBytes,
|
|
3030
|
+
limitBytes: eff.maxJsonBytes,
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
3033
|
+
return { ok: true };
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* Returns a NEW payload with all string fields trimmed to fit the per-field cap, the
|
|
3037
|
+
* `plainTextFallback` trimmed to its cap, and the full envelope re-checked against the
|
|
3038
|
+
* JSON cap. Idempotent — a second call against the trimmed result yields equal output.
|
|
3039
|
+
*
|
|
3040
|
+
* Throws {@link AgentPayloadOversizeError} only if even an empty trim couldn't shrink it
|
|
3041
|
+
* under the cap (e.g. an envelope with 1000+ nested empty objects). The error subclasses
|
|
3042
|
+
* `RangeError`, so legacy `instanceof RangeError` checks still match. Apps that hit this
|
|
3043
|
+
* should re-shape `payload` rather than call again.
|
|
3044
|
+
*/
|
|
3045
|
+
function trimAgentPayload(payload, limits) {
|
|
3046
|
+
const eff = { ...DEFAULT_AGENT_PAYLOAD_LIMITS, ...(limits ?? {}) };
|
|
3047
|
+
const trimmedFallback = trimAgentString(payload.plainTextFallback ?? '', eff.maxPlainTextFallbackBytes);
|
|
3048
|
+
const trimmedPayload = mapStrings(payload.payload, (s) => trimAgentString(s, eff.maxStringFieldBytes));
|
|
3049
|
+
const next = {
|
|
3050
|
+
...payload,
|
|
3051
|
+
plainTextFallback: trimmedFallback,
|
|
3052
|
+
payload: trimmedPayload,
|
|
3053
|
+
};
|
|
3054
|
+
const jsonBytes = utf8ByteLength(safeStringify(next));
|
|
3055
|
+
if (jsonBytes > eff.maxJsonBytes) {
|
|
3056
|
+
throw new AgentPayloadOversizeError(`AgentDragPayload exceeds maxJsonBytes (${jsonBytes} > ${eff.maxJsonBytes}) after string trimming. Re-shape the payload.`, jsonBytes, eff.maxJsonBytes);
|
|
3057
|
+
}
|
|
3058
|
+
return next;
|
|
3059
|
+
}
|
|
3060
|
+
// ─── Internal helpers ───────────────────────────────────────────────────────
|
|
3061
|
+
/**
|
|
3062
|
+
* Recursively walks `value`, returning the first string field that exceeds `maxBytes`
|
|
3063
|
+
* (as a structured failure). Returns `null` when every string fits.
|
|
3064
|
+
*
|
|
3065
|
+
* Path uses dotted property-access notation: `payload.body`, `payload.tags[2]`. Cycles
|
|
3066
|
+
* are detected via a WeakSet so attacker-crafted self-references don't loop forever.
|
|
3067
|
+
* Path strings are capped at {@link FIELD_PATH_MAX_CHARS} characters via {@link appendPath}
|
|
3068
|
+
* to prevent a malicious payload with multi-MB property keys from producing a multi-MB
|
|
3069
|
+
* `fieldPath` that locks up dev tools when logged.
|
|
3070
|
+
*/
|
|
3071
|
+
function walkStringsForOverage(value, path, maxBytes, seen = new WeakSet()) {
|
|
3072
|
+
if (typeof value === 'string') {
|
|
3073
|
+
const bytes = utf8ByteLength(value);
|
|
3074
|
+
if (bytes > maxBytes) {
|
|
3075
|
+
return {
|
|
3076
|
+
ok: false,
|
|
3077
|
+
reason: 'field_too_large',
|
|
3078
|
+
fieldPath: path,
|
|
3079
|
+
actualBytes: bytes,
|
|
3080
|
+
limitBytes: maxBytes,
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
return null;
|
|
3084
|
+
}
|
|
3085
|
+
if (value === null || typeof value !== 'object')
|
|
3086
|
+
return null;
|
|
3087
|
+
if (seen.has(value))
|
|
3088
|
+
return null;
|
|
3089
|
+
seen.add(value);
|
|
3090
|
+
if (Array.isArray(value)) {
|
|
3091
|
+
for (let i = 0; i < value.length; i++) {
|
|
3092
|
+
const r = walkStringsForOverage(value[i], appendPath(path, `[${i}]`), maxBytes, seen);
|
|
3093
|
+
if (r)
|
|
3094
|
+
return r;
|
|
3095
|
+
}
|
|
3096
|
+
return null;
|
|
3097
|
+
}
|
|
3098
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3099
|
+
const r = walkStringsForOverage(v, appendPath(path, `.${k}`), maxBytes, seen);
|
|
3100
|
+
if (r)
|
|
3101
|
+
return r;
|
|
3102
|
+
}
|
|
3103
|
+
return null;
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Recursively maps every string leaf via `fn`, preserving object/array structure.
|
|
3107
|
+
* Cycles are short-circuited to `null` rather than throwing — the caller already
|
|
3108
|
+
* has a JSON.stringify cap that will catch any pathological input.
|
|
3109
|
+
*/
|
|
3110
|
+
function mapStrings(value, fn, seen = new WeakSet()) {
|
|
3111
|
+
if (typeof value === 'string')
|
|
3112
|
+
return fn(value);
|
|
3113
|
+
if (value === null || typeof value !== 'object')
|
|
3114
|
+
return value;
|
|
3115
|
+
if (seen.has(value))
|
|
3116
|
+
return null;
|
|
3117
|
+
seen.add(value);
|
|
3118
|
+
if (Array.isArray(value)) {
|
|
3119
|
+
return value.map((v) => mapStrings(v, fn, seen));
|
|
3120
|
+
}
|
|
3121
|
+
const out = {};
|
|
3122
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3123
|
+
out[k] = mapStrings(v, fn, seen);
|
|
3124
|
+
}
|
|
3125
|
+
return out;
|
|
3126
|
+
}
|
|
3127
|
+
/** `JSON.stringify` with a cycle-safe replacer — defends against attacker-crafted graphs. */
|
|
3128
|
+
function safeStringify(value) {
|
|
3129
|
+
const seen = new WeakSet();
|
|
3130
|
+
return JSON.stringify(value, (_k, v) => {
|
|
3131
|
+
if (v !== null && typeof v === 'object') {
|
|
3132
|
+
if (seen.has(v))
|
|
3133
|
+
return '[Circular]';
|
|
3134
|
+
seen.add(v);
|
|
3135
|
+
}
|
|
3136
|
+
return v;
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
/**
|
|
3141
|
+
* Strict whitelist for the ghost element id. Letter-led ASCII, allows word/hyphen/colon/dot,
|
|
3142
|
+
* capped at 128 chars. Anything else is rejected before reaching `getElementById` so a
|
|
3143
|
+
* future maintainer can't swap to `querySelector` and quietly reintroduce a CSS-injection
|
|
3144
|
+
* vector. (`getElementById` itself is string-equality-safe; the whitelist is defence-in-depth.)
|
|
3145
|
+
*/
|
|
3146
|
+
const GHOST_ID_PATTERN = /^[A-Za-z][\w\-:.]{0,127}$/;
|
|
3147
|
+
/**
|
|
3148
|
+
* Single canonical way to declare a drag source for the agent input.
|
|
3149
|
+
*
|
|
3150
|
+
* Apply on any element a remote wants to make draggable into the `<fly-agent-input>`:
|
|
3151
|
+
*
|
|
3152
|
+
* ```html
|
|
3153
|
+
* <article [flyAgentDraggable]="() => buildPayload(trend)" flyAgentDraggableGhostId="trend-ghost">…</article>
|
|
3154
|
+
* ```
|
|
3155
|
+
*
|
|
3156
|
+
* On `dragstart` the directive:
|
|
3157
|
+
* 1. Resolves the payload (calls the builder fn if supplied; otherwise reads the
|
|
3158
|
+
* static value).
|
|
3159
|
+
* 2. Validates against {@link DEFAULT_AGENT_PAYLOAD_LIMITS} via
|
|
3160
|
+
* {@link validateAgentPayload}. On failure it `console.warn`s with the structured
|
|
3161
|
+
* reason and writes ONLY the `text/plain` fallback — the receiver still sees a
|
|
3162
|
+
* legible drop, but the typed envelope is skipped so a misconfigured app can't
|
|
3163
|
+
* corrupt downstream consumers with an oversize payload.
|
|
3164
|
+
* 3. Writes the typed MIME (`application/x-fly-agent-payload+json`) plus the
|
|
3165
|
+
* `text/plain` fallback so non-Fly drop targets still get useful text.
|
|
3166
|
+
* 4. Sets `effectAllowed = 'copy'` (we never *move* domain objects across windows).
|
|
3167
|
+
* 5. When `flyAgentDraggableGhostId` resolves to an element in the DOM, calls
|
|
3168
|
+
* `setDragImage` to use that element as the drag preview; otherwise the browser
|
|
3169
|
+
* default applies.
|
|
3170
|
+
*
|
|
3171
|
+
* Notes:
|
|
3172
|
+
* - Builder form is preferred over static — it captures fresh state at `dragstart`,
|
|
3173
|
+
* not at directive init. A static `AgentDragPayload` is fine for items whose
|
|
3174
|
+
* domain object never changes after the row mounts.
|
|
3175
|
+
* - The directive sets `[attr.draggable]="true"` so callers don't have to.
|
|
3176
|
+
*/
|
|
3177
|
+
class FlyAgentDraggableDirective {
|
|
3178
|
+
/**
|
|
3179
|
+
* Either a static payload or a builder evaluated at `dragstart` (preferred — captures
|
|
3180
|
+
* fresh state). `input.required` so misuse fails at template compile time.
|
|
3181
|
+
*/
|
|
3182
|
+
flyAgentDraggable = input.required(...(ngDevMode ? [{ debugName: "flyAgentDraggable" }] : /* istanbul ignore next */ []));
|
|
3183
|
+
/**
|
|
3184
|
+
* Optional ghost element id (an existing element in the DOM whose snapshot becomes
|
|
3185
|
+
* the drag image). Falls back to the default browser drag image when absent or the
|
|
3186
|
+
* element can't be resolved.
|
|
3187
|
+
*
|
|
3188
|
+
* Must be a valid HTML id (ASCII, letter-led). `getElementById` is intentional —
|
|
3189
|
+
* do NOT swap to `querySelector` without re-introducing this whitelist.
|
|
3190
|
+
*/
|
|
3191
|
+
flyAgentDraggableGhostId = input(undefined, ...(ngDevMode ? [{ debugName: "flyAgentDraggableGhostId" }] : /* istanbul ignore next */ []));
|
|
3192
|
+
doc = inject(DOCUMENT$1);
|
|
3193
|
+
onDragStart(ev) {
|
|
3194
|
+
const dt = ev.dataTransfer;
|
|
3195
|
+
if (!dt)
|
|
3196
|
+
return;
|
|
3197
|
+
const payload = this.resolvePayload();
|
|
3198
|
+
const validation = validateAgentPayload(payload);
|
|
3199
|
+
if (!validation.ok) {
|
|
3200
|
+
// eslint-disable-next-line no-console
|
|
3201
|
+
console.warn('[flyAgentDraggable] payload validation failed; degrading to text/plain', validation);
|
|
3202
|
+
dt.setData('text/plain', payload.plainTextFallback ?? '');
|
|
3203
|
+
dt.effectAllowed = 'copy';
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
dt.setData(AGENT_DRAG_MIME, JSON.stringify(payload));
|
|
3207
|
+
dt.setData('text/plain', payload.plainTextFallback ?? '');
|
|
3208
|
+
dt.effectAllowed = 'copy';
|
|
3209
|
+
const ghostId = this.flyAgentDraggableGhostId();
|
|
3210
|
+
if (ghostId) {
|
|
3211
|
+
if (!GHOST_ID_PATTERN.test(ghostId)) {
|
|
3212
|
+
// Don't echo the rejected id (caller-controlled, could be huge).
|
|
3213
|
+
// eslint-disable-next-line no-console
|
|
3214
|
+
console.warn('[flyAgentDraggable] ignoring ghost id', { reason: 'invalid_ghost_id' });
|
|
3215
|
+
}
|
|
3216
|
+
else {
|
|
3217
|
+
const ghost = this.doc.getElementById(ghostId);
|
|
3218
|
+
if (ghost) {
|
|
3219
|
+
// Center the drag image; ResizeObserver-aware sizing is left to the host element.
|
|
3220
|
+
dt.setDragImage(ghost, ghost.clientWidth / 2, ghost.clientHeight / 2);
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
resolvePayload() {
|
|
3226
|
+
const raw = this.flyAgentDraggable();
|
|
3227
|
+
return typeof raw === 'function' ? raw() : raw;
|
|
3228
|
+
}
|
|
3229
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyAgentDraggableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3230
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: FlyAgentDraggableDirective, isStandalone: true, selector: "[flyAgentDraggable]", inputs: { flyAgentDraggable: { classPropertyName: "flyAgentDraggable", publicName: "flyAgentDraggable", isSignal: true, isRequired: true, transformFunction: null }, flyAgentDraggableGhostId: { classPropertyName: "flyAgentDraggableGhostId", publicName: "flyAgentDraggableGhostId", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "dragstart": "onDragStart($event)" }, properties: { "attr.draggable": "\"true\"" } }, ngImport: i0 });
|
|
3231
|
+
}
|
|
3232
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyAgentDraggableDirective, decorators: [{
|
|
3233
|
+
type: Directive,
|
|
3234
|
+
args: [{
|
|
3235
|
+
selector: '[flyAgentDraggable]',
|
|
3236
|
+
standalone: true,
|
|
3237
|
+
host: {
|
|
3238
|
+
'[attr.draggable]': '"true"',
|
|
3239
|
+
},
|
|
3240
|
+
}]
|
|
3241
|
+
}], propDecorators: { flyAgentDraggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyAgentDraggable", required: true }] }], flyAgentDraggableGhostId: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyAgentDraggableGhostId", required: false }] }], onDragStart: [{
|
|
3242
|
+
type: HostListener,
|
|
3243
|
+
args: ['dragstart', ['$event']]
|
|
3244
|
+
}] } });
|
|
3245
|
+
|
|
3246
|
+
/**
|
|
3247
|
+
* Verbatim TypeScript port of the C# `LanguageDTO[] _AllLanguages` array supplied by
|
|
3248
|
+
* product. **31 entries**, in the canonical order. Three RTL flagged: `he-IL`, `ur-IN`,
|
|
3249
|
+
* `ar-AE`. Includes `cmn-Hant-TW` for Chinese and `ka-GE` for Georgian.
|
|
3250
|
+
*
|
|
3251
|
+
* The order matches the C# source byte-for-byte — do not re-sort. Downstream tests
|
|
3252
|
+
* (`fly-locale-catalog.spec.ts`) pin the count, the unique-key constraints, and the
|
|
3253
|
+
* RTL set; if you reorder or add an entry, those tests will guide the fix.
|
|
3254
|
+
*
|
|
3255
|
+
* The strings carry Cyrillic, Hebrew, Vietnamese, Thai, Indonesian, Bengali, Devanagari,
|
|
3256
|
+
* Urdu, Catalan, Polish, Korean, Russian, Japanese, Han, Turkish, Romanian, Norwegian,
|
|
3257
|
+
* Hungarian, Greek, Finnish, Danish, Dutch, Swedish, Portuguese, Italian, German,
|
|
3258
|
+
* Spanish, French, English, Arabic, and Georgian. The file is saved as UTF-8 without
|
|
3259
|
+
* BOM (matches every other locale file in the repo). The `…` (U+2026) ellipsis is
|
|
3260
|
+
* preserved verbatim.
|
|
3261
|
+
*/
|
|
3262
|
+
const FLY_LOCALE_CATALOG = Object.freeze([
|
|
3263
|
+
{
|
|
3264
|
+
prefix: 'UK',
|
|
3265
|
+
dialect: 'uk-UA',
|
|
3266
|
+
englishName: 'Ukrainian',
|
|
3267
|
+
arabicName: 'الأوكرانية',
|
|
3268
|
+
nativeName: 'Українська',
|
|
3269
|
+
detected: 'Виявлена мова',
|
|
3270
|
+
searching: 'Шукаючи',
|
|
3271
|
+
elevating: 'Підняття до',
|
|
3272
|
+
translating: 'Переклад на',
|
|
3273
|
+
generatingAnswersForYou: 'Створення відповідей для вас...',
|
|
3274
|
+
learnMore: 'Дізнайтеся більше',
|
|
3275
|
+
waitingForResponseMsg: 'Будь ласка, наберіться терпіння, ми ще думаємо',
|
|
3276
|
+
isRtl: false,
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
prefix: 'HE',
|
|
3280
|
+
dialect: 'he-IL',
|
|
3281
|
+
englishName: 'Hebrew',
|
|
3282
|
+
arabicName: 'العبرية',
|
|
3283
|
+
nativeName: 'עברית',
|
|
3284
|
+
detected: 'שפה מזוהה',
|
|
3285
|
+
searching: 'מחפש אחר',
|
|
3286
|
+
elevating: 'מרומם ל',
|
|
3287
|
+
translating: 'תרגום ל',
|
|
3288
|
+
generatingAnswersForYou: 'מייצר עבורך תשובות...',
|
|
3289
|
+
learnMore: 'למידע נוסף',
|
|
3290
|
+
waitingForResponseMsg: 'אנא התאזרו בסבלנות, אנחנו עדיין חושבים',
|
|
3291
|
+
isRtl: true,
|
|
3292
|
+
},
|
|
3293
|
+
{
|
|
3294
|
+
prefix: 'VI',
|
|
3295
|
+
dialect: 'vi-VN',
|
|
3296
|
+
englishName: 'Vietnamese',
|
|
3297
|
+
arabicName: 'الفيتنامية',
|
|
3298
|
+
nativeName: 'Tiếng Việt',
|
|
3299
|
+
detected: 'Ngôn ngữ được phát hiện',
|
|
3300
|
+
searching: 'Tìm kiếm',
|
|
3301
|
+
elevating: 'Nâng lên',
|
|
3302
|
+
translating: 'Dịch sang',
|
|
3303
|
+
generatingAnswersForYou: 'Đang tạo câu trả lời cho bạn...',
|
|
3304
|
+
learnMore: 'Tìm hiểu thêm',
|
|
3305
|
+
waitingForResponseMsg: 'Vui lòng kiên nhẫn, chúng tôi vẫn đang suy nghĩ',
|
|
3306
|
+
isRtl: false,
|
|
3307
|
+
},
|
|
3308
|
+
{
|
|
3309
|
+
prefix: 'TH',
|
|
3310
|
+
dialect: 'th-TH',
|
|
3311
|
+
englishName: 'Thai',
|
|
3312
|
+
arabicName: 'التايلاندية',
|
|
3313
|
+
nativeName: 'ไทย',
|
|
3314
|
+
detected: 'ภาษาที่ตรวจพบ',
|
|
3315
|
+
searching: 'ค้นหา',
|
|
3316
|
+
elevating: 'ยกระดับเป็น',
|
|
3317
|
+
translating: 'การแปล',
|
|
3318
|
+
generatingAnswersForYou: 'กำลังสร้างคำตอบให้กับคุณ...',
|
|
3319
|
+
learnMore: 'เรียนรู้เพิ่มเติม',
|
|
3320
|
+
waitingForResponseMsg: 'โปรดอดใจรอ, พวกเรายังคงกำลังคิดอยู่',
|
|
3321
|
+
isRtl: false,
|
|
3322
|
+
},
|
|
3323
|
+
{
|
|
3324
|
+
prefix: 'ID',
|
|
3325
|
+
dialect: 'id-ID',
|
|
3326
|
+
englishName: 'Indonesian',
|
|
3327
|
+
arabicName: 'الإندونيسية',
|
|
3328
|
+
nativeName: 'Bahasa Indonesia',
|
|
3329
|
+
detected: 'Bahasa yang terdeteksi',
|
|
3330
|
+
searching: 'Mencari',
|
|
3331
|
+
elevating: 'Meninggalkan ke',
|
|
3332
|
+
translating: 'Menerjemahkan ke',
|
|
3333
|
+
generatingAnswersForYou: 'Menghasilkan jawaban untuk Anda...',
|
|
3334
|
+
learnMore: 'Cari tahu lebih lanjut',
|
|
3335
|
+
waitingForResponseMsg: 'Harap bersabar, kami masih sedang berpikir',
|
|
3336
|
+
isRtl: false,
|
|
3337
|
+
},
|
|
3338
|
+
{
|
|
3339
|
+
prefix: 'BN',
|
|
3340
|
+
dialect: 'bn-BD',
|
|
3341
|
+
englishName: 'Bengali',
|
|
3342
|
+
arabicName: 'البنغالية',
|
|
3343
|
+
nativeName: 'বাংলা',
|
|
3344
|
+
detected: 'সনাক্ত করা ভাষা',
|
|
3345
|
+
searching: 'খুঁজছি',
|
|
3346
|
+
elevating: 'উন্নত',
|
|
3347
|
+
translating: 'অনুবাদ',
|
|
3348
|
+
generatingAnswersForYou: 'আপনার জন্য উত্তর তৈরি করা হচ্ছে...',
|
|
3349
|
+
learnMore: 'আরো জানুন',
|
|
3350
|
+
waitingForResponseMsg: 'অনুগ্রহ করে ধৈর্য ধরুন, আমরা এখনও চিন্তা করছি',
|
|
3351
|
+
isRtl: false,
|
|
3352
|
+
},
|
|
3353
|
+
{
|
|
3354
|
+
prefix: 'HI',
|
|
3355
|
+
dialect: 'hi-IN',
|
|
3356
|
+
englishName: 'Hindi',
|
|
3357
|
+
arabicName: 'الهندية',
|
|
3358
|
+
nativeName: 'हिन्दी',
|
|
3359
|
+
detected: 'पता चला भाषा',
|
|
3360
|
+
searching: 'के लिए खोज रहे हैं',
|
|
3361
|
+
elevating: 'के लिए ऊंचा',
|
|
3362
|
+
translating: 'अनुवाद करना',
|
|
3363
|
+
generatingAnswersForYou: 'आपके लिए उत्तर तैयार किया जा रहा है...',
|
|
3364
|
+
learnMore: 'और जानें',
|
|
3365
|
+
waitingForResponseMsg: 'कृपया धैर्य रखें, हम अभी सोच रहे हैं',
|
|
3366
|
+
isRtl: false,
|
|
3367
|
+
},
|
|
3368
|
+
{
|
|
3369
|
+
prefix: 'UR',
|
|
3370
|
+
dialect: 'ur-IN',
|
|
3371
|
+
englishName: 'Urdu',
|
|
3372
|
+
arabicName: 'الأردية',
|
|
3373
|
+
nativeName: 'اردو',
|
|
3374
|
+
detected: 'پتہ چلا زبان',
|
|
3375
|
+
searching: 'کے لیے تلاش',
|
|
3376
|
+
elevating: 'بلند کرنا',
|
|
3377
|
+
translating: 'ترجمہ کرنا',
|
|
3378
|
+
generatingAnswersForYou: 'آپ کے لیے جوابات تیار کیے جا رہے ہیں...',
|
|
3379
|
+
learnMore: 'مزید جانیں',
|
|
3380
|
+
waitingForResponseMsg: 'براہ کرم صبر کریں، ہم ابھی سوچ رہے ہیں۔',
|
|
3381
|
+
isRtl: true,
|
|
3382
|
+
},
|
|
3383
|
+
{
|
|
3384
|
+
prefix: 'CA',
|
|
3385
|
+
dialect: 'ca-ES',
|
|
3386
|
+
englishName: 'Catalan',
|
|
3387
|
+
arabicName: 'الكتالانية',
|
|
3388
|
+
nativeName: 'Català',
|
|
3389
|
+
detected: 'Idioma detectat',
|
|
3390
|
+
searching: 'Buscant',
|
|
3391
|
+
elevating: 'Elevant a',
|
|
3392
|
+
translating: 'Traduir a',
|
|
3393
|
+
generatingAnswersForYou: 'Generant respostes per a tu...',
|
|
3394
|
+
learnMore: 'Aprèn més',
|
|
3395
|
+
waitingForResponseMsg: 'Si us plau, tingueu paciència, encara estem pensant',
|
|
3396
|
+
isRtl: false,
|
|
3397
|
+
},
|
|
3398
|
+
{
|
|
3399
|
+
prefix: 'PL',
|
|
3400
|
+
dialect: 'pl-PL',
|
|
3401
|
+
englishName: 'Polish',
|
|
3402
|
+
arabicName: 'البولندية',
|
|
3403
|
+
nativeName: 'Polski',
|
|
3404
|
+
detected: 'Wykryty język',
|
|
3405
|
+
searching: 'Szukać',
|
|
3406
|
+
elevating: 'Podniesienie do',
|
|
3407
|
+
translating: 'Tłumaczenie na',
|
|
3408
|
+
generatingAnswersForYou: 'Generowanie odpowiedzi dla Ciebie...',
|
|
3409
|
+
learnMore: 'Dowiedz się więcej',
|
|
3410
|
+
waitingForResponseMsg: 'Proszę o cierpliwość, nadal myślimy',
|
|
3411
|
+
isRtl: false,
|
|
3412
|
+
},
|
|
3413
|
+
{
|
|
3414
|
+
prefix: 'KO',
|
|
3415
|
+
dialect: 'ko-KR',
|
|
3416
|
+
englishName: 'Korean',
|
|
3417
|
+
arabicName: 'الكورية',
|
|
3418
|
+
nativeName: '한국어',
|
|
3419
|
+
detected: '탐지 언어',
|
|
3420
|
+
searching: '검색',
|
|
3421
|
+
elevating: '상승',
|
|
3422
|
+
translating: '번역',
|
|
3423
|
+
generatingAnswersForYou: '답변을 생성하는 중...',
|
|
3424
|
+
learnMore: '더 알아보기',
|
|
3425
|
+
waitingForResponseMsg: '잠시만 기다려 주세요, 저희는 아직 생각 중입니다',
|
|
3426
|
+
isRtl: false,
|
|
3427
|
+
},
|
|
3428
|
+
{
|
|
3429
|
+
prefix: 'RU',
|
|
3430
|
+
dialect: 'ru-RU',
|
|
3431
|
+
englishName: 'Russian',
|
|
3432
|
+
arabicName: 'الروسية',
|
|
3433
|
+
nativeName: 'Русский',
|
|
3434
|
+
detected: 'Обнаруженный язык',
|
|
3435
|
+
searching: 'В поисках',
|
|
3436
|
+
elevating: 'Поднимаясь до',
|
|
3437
|
+
translating: 'Перевод',
|
|
3438
|
+
generatingAnswersForYou: 'Генерация ответов для вас...',
|
|
3439
|
+
learnMore: 'Узнать больше',
|
|
3440
|
+
waitingForResponseMsg: 'Пожалуйста, подождите, мы всё ещё думаем',
|
|
3441
|
+
isRtl: false,
|
|
3442
|
+
},
|
|
3443
|
+
{
|
|
3444
|
+
prefix: 'JA',
|
|
3445
|
+
dialect: 'ja-JP',
|
|
3446
|
+
englishName: 'Japanese',
|
|
3447
|
+
arabicName: 'اليابانية',
|
|
3448
|
+
nativeName: '日本語',
|
|
3449
|
+
detected: '検出言語',
|
|
3450
|
+
searching: '探している',
|
|
3451
|
+
elevating: '昇格',
|
|
3452
|
+
translating: '翻訳',
|
|
3453
|
+
generatingAnswersForYou: 'あなたのための答えを生成中...',
|
|
3454
|
+
learnMore: 'もっと詳しく',
|
|
3455
|
+
waitingForResponseMsg: '少々お待ちください、ただいま考え中です',
|
|
3456
|
+
isRtl: false,
|
|
3457
|
+
},
|
|
3458
|
+
{
|
|
3459
|
+
prefix: 'ZH',
|
|
3460
|
+
dialect: 'cmn-Hant-TW',
|
|
3461
|
+
englishName: 'Chinese',
|
|
3462
|
+
arabicName: 'الصينية',
|
|
3463
|
+
nativeName: '中文',
|
|
3464
|
+
detected: '检测语言',
|
|
3465
|
+
searching: '搜索',
|
|
3466
|
+
elevating: '提升到',
|
|
3467
|
+
translating: '翻译成',
|
|
3468
|
+
generatingAnswersForYou: '为您生成答案...',
|
|
3469
|
+
learnMore: '了解更多',
|
|
3470
|
+
waitingForResponseMsg: '请耐心等待,我们还在思考',
|
|
3471
|
+
isRtl: false,
|
|
3472
|
+
},
|
|
3473
|
+
{
|
|
3474
|
+
prefix: 'TR',
|
|
3475
|
+
dialect: 'tr-TR',
|
|
3476
|
+
englishName: 'Turkish',
|
|
3477
|
+
arabicName: 'التركية',
|
|
3478
|
+
nativeName: 'Türkçe',
|
|
3479
|
+
detected: 'Tespit edilen dil',
|
|
3480
|
+
searching: 'Arama',
|
|
3481
|
+
elevating: 'Yükseltme',
|
|
3482
|
+
translating: 'Çevirme',
|
|
3483
|
+
generatingAnswersForYou: 'Sizin için yanıtlar üretiliyor...',
|
|
3484
|
+
learnMore: 'Daha fazla bilgi edin',
|
|
3485
|
+
waitingForResponseMsg: 'Lütfen sabırlı olun, hâlâ düşünüyoruz',
|
|
3486
|
+
isRtl: false,
|
|
3487
|
+
},
|
|
3488
|
+
{
|
|
3489
|
+
prefix: 'RO',
|
|
3490
|
+
dialect: 'ro-RO',
|
|
3491
|
+
englishName: 'Romanian',
|
|
3492
|
+
arabicName: 'الرومانية',
|
|
3493
|
+
nativeName: 'Română',
|
|
3494
|
+
detected: 'Limba detectată',
|
|
3495
|
+
searching: 'Căutare de',
|
|
3496
|
+
elevating: 'Elevator la',
|
|
3497
|
+
translating: 'Traducerea în',
|
|
3498
|
+
generatingAnswersForYou: 'Generarea de răspunsuri pentru tine...',
|
|
3499
|
+
learnMore: 'Află mai multe',
|
|
3500
|
+
waitingForResponseMsg: 'Vă rugăm să aveți răbdare, încă ne gândim',
|
|
3501
|
+
isRtl: false,
|
|
3502
|
+
},
|
|
3503
|
+
{
|
|
3504
|
+
prefix: 'NO',
|
|
3505
|
+
dialect: 'nb-NO',
|
|
3506
|
+
englishName: 'Norwegian',
|
|
3507
|
+
arabicName: 'النرويجية',
|
|
3508
|
+
nativeName: 'Norsk',
|
|
3509
|
+
detected: 'Oppdaget språk',
|
|
3510
|
+
searching: 'Leter etter',
|
|
3511
|
+
elevating: 'Løfter til',
|
|
3512
|
+
translating: 'Oversette til',
|
|
3513
|
+
generatingAnswersForYou: 'Genererer svar for deg...',
|
|
3514
|
+
learnMore: 'Lær mer',
|
|
3515
|
+
waitingForResponseMsg: 'Vennligst vær tålmodig, vi tenker fortsatt',
|
|
3516
|
+
isRtl: false,
|
|
3517
|
+
},
|
|
3518
|
+
{
|
|
3519
|
+
prefix: 'HU',
|
|
3520
|
+
dialect: 'hu-HU',
|
|
3521
|
+
englishName: 'Hungarian',
|
|
3522
|
+
arabicName: 'الهنغارية',
|
|
3523
|
+
nativeName: 'Magyar',
|
|
3524
|
+
detected: 'Detektált nyelv',
|
|
3525
|
+
searching: 'Keresés-ra,-re',
|
|
3526
|
+
elevating: 'Felemelve',
|
|
3527
|
+
translating: 'Fordítás',
|
|
3528
|
+
generatingAnswersForYou: 'Válaszok generálása az Ön számára...',
|
|
3529
|
+
learnMore: 'Tudj meg többet',
|
|
3530
|
+
waitingForResponseMsg: 'Kérjük, legyen türelemmel, még gondolkodunk',
|
|
3531
|
+
isRtl: false,
|
|
3532
|
+
},
|
|
3533
|
+
{
|
|
3534
|
+
prefix: 'EL',
|
|
3535
|
+
dialect: 'el-GR',
|
|
3536
|
+
englishName: 'Greek',
|
|
3537
|
+
arabicName: 'اليونانية',
|
|
3538
|
+
nativeName: 'Ελληνικά',
|
|
3539
|
+
detected: 'Εντοπισμένη γλώσσα',
|
|
3540
|
+
searching: 'Ψάχνοντας για',
|
|
3541
|
+
elevating: 'Ανυψώ',
|
|
3542
|
+
translating: 'Μεταφράζοντας σε',
|
|
3543
|
+
generatingAnswersForYou: 'Δημιουργία απαντήσεων για εσάς...',
|
|
3544
|
+
learnMore: 'Μάθε περισσότερα',
|
|
3545
|
+
waitingForResponseMsg: 'Παρακαλώ περιμένετε με υπομονή, σκεφτόμαστε ακόμα',
|
|
3546
|
+
isRtl: false,
|
|
3547
|
+
},
|
|
3548
|
+
{
|
|
3549
|
+
prefix: 'FI',
|
|
3550
|
+
dialect: 'fi-FI',
|
|
3551
|
+
englishName: 'Finnish',
|
|
3552
|
+
arabicName: 'الفنلندية',
|
|
3553
|
+
nativeName: 'Suomi',
|
|
3554
|
+
detected: 'Havaittu kieli',
|
|
3555
|
+
searching: 'Etsimässä',
|
|
3556
|
+
elevating: 'Nousee jhk',
|
|
3557
|
+
translating: 'Kääntäen',
|
|
3558
|
+
generatingAnswersForYou: 'Luodaan vastauksia sinulle...',
|
|
3559
|
+
learnMore: 'Lue lisää',
|
|
3560
|
+
waitingForResponseMsg: 'Ole hyvä ja ole kärsivällinen, ajattelemme yhä',
|
|
3561
|
+
isRtl: false,
|
|
3562
|
+
},
|
|
3563
|
+
{
|
|
3564
|
+
prefix: 'DA',
|
|
3565
|
+
dialect: 'da-DK',
|
|
3566
|
+
englishName: 'Danish',
|
|
3567
|
+
arabicName: 'الدانماركية',
|
|
3568
|
+
nativeName: 'Dansk',
|
|
3569
|
+
detected: 'Opdaget sprog',
|
|
3570
|
+
searching: 'Leder efter',
|
|
3571
|
+
elevating: 'Hæve til',
|
|
3572
|
+
translating: 'Oversættelse til',
|
|
3573
|
+
generatingAnswersForYou: 'Generer svar til dig...',
|
|
3574
|
+
learnMore: 'Lær mere',
|
|
3575
|
+
waitingForResponseMsg: 'Vent venligst tålmodigt, vi tænker stadig',
|
|
3576
|
+
isRtl: false,
|
|
3577
|
+
},
|
|
3578
|
+
{
|
|
3579
|
+
prefix: 'NL',
|
|
3580
|
+
dialect: 'nl-NL',
|
|
3581
|
+
englishName: 'Dutch',
|
|
3582
|
+
arabicName: 'الهولندية',
|
|
3583
|
+
nativeName: 'Nederlands',
|
|
3584
|
+
detected: 'Gedetecteerde taal',
|
|
3585
|
+
searching: 'Op zoek naar',
|
|
3586
|
+
elevating: 'Verheffen tot',
|
|
3587
|
+
translating: 'Vertalen naar',
|
|
3588
|
+
generatingAnswersForYou: 'Antwoorden voor u genereren...',
|
|
3589
|
+
learnMore: 'Lees meer',
|
|
3590
|
+
waitingForResponseMsg: 'Gelieve geduld te hebben, we zijn nog aan het nadenken',
|
|
3591
|
+
isRtl: false,
|
|
3592
|
+
},
|
|
3593
|
+
{
|
|
3594
|
+
prefix: 'SV',
|
|
3595
|
+
dialect: 'sv-SE',
|
|
3596
|
+
englishName: 'Swedish',
|
|
3597
|
+
arabicName: 'السويدية',
|
|
3598
|
+
nativeName: 'Svenska',
|
|
3599
|
+
detected: 'Upptäckt språk',
|
|
3600
|
+
searching: 'Söker efter',
|
|
3601
|
+
elevating: 'Upphöjande till',
|
|
3602
|
+
translating: 'Översätta till',
|
|
3603
|
+
generatingAnswersForYou: 'Genererar svar åt dig...',
|
|
3604
|
+
learnMore: 'Läs mer',
|
|
3605
|
+
waitingForResponseMsg: 'Vänligen ha tålamod, vi tänker fortfarande',
|
|
3606
|
+
isRtl: false,
|
|
3607
|
+
},
|
|
3608
|
+
{
|
|
3609
|
+
prefix: 'PT',
|
|
3610
|
+
dialect: 'pt-PT',
|
|
3611
|
+
englishName: 'Portuguese',
|
|
3612
|
+
arabicName: 'البرتغالية',
|
|
3613
|
+
nativeName: 'Português',
|
|
3614
|
+
detected: 'Idioma detectado',
|
|
3615
|
+
searching: 'Procurando por',
|
|
3616
|
+
elevating: 'Elevando para',
|
|
3617
|
+
translating: 'Traduzindo para',
|
|
3618
|
+
generatingAnswersForYou: 'Gerando respostas para você...',
|
|
3619
|
+
learnMore: 'Saiba mais',
|
|
3620
|
+
waitingForResponseMsg: 'Por favor, tenha paciência, ainda estamos a pensar',
|
|
3621
|
+
isRtl: false,
|
|
3622
|
+
},
|
|
3623
|
+
{
|
|
3624
|
+
prefix: 'IT',
|
|
3625
|
+
dialect: 'it-IT',
|
|
3626
|
+
englishName: 'Italian',
|
|
3627
|
+
arabicName: 'الإيطالية',
|
|
3628
|
+
nativeName: 'Italiano',
|
|
3629
|
+
detected: 'Lingua rilevata',
|
|
3630
|
+
searching: 'Alla ricerca di',
|
|
3631
|
+
elevating: 'Elevare a',
|
|
3632
|
+
translating: 'Tradurre in',
|
|
3633
|
+
generatingAnswersForYou: 'Generazione di risposte per te...',
|
|
3634
|
+
learnMore: 'Scopri di più',
|
|
3635
|
+
waitingForResponseMsg: 'Per favore, abbi pazienza, stiamo ancora pensando',
|
|
3636
|
+
isRtl: false,
|
|
3637
|
+
},
|
|
3638
|
+
{
|
|
3639
|
+
prefix: 'DE',
|
|
3640
|
+
dialect: 'de-DE',
|
|
3641
|
+
englishName: 'German',
|
|
3642
|
+
arabicName: 'الألمانية',
|
|
3643
|
+
nativeName: 'Deutsch',
|
|
3644
|
+
detected: 'Erkannte Sprache',
|
|
3645
|
+
searching: 'Auf der Suche nach',
|
|
3646
|
+
elevating: 'Erhöhung zu',
|
|
3647
|
+
translating: 'Übersetzung auf',
|
|
3648
|
+
generatingAnswersForYou: 'Wir generieren Antworten für Sie...',
|
|
3649
|
+
learnMore: 'Erfahre mehr',
|
|
3650
|
+
waitingForResponseMsg: 'Bitte haben Sie Geduld, wir denken noch nach',
|
|
3651
|
+
isRtl: false,
|
|
3652
|
+
},
|
|
3653
|
+
{
|
|
3654
|
+
prefix: 'ES',
|
|
3655
|
+
dialect: 'es-AR',
|
|
3656
|
+
englishName: 'Spanish',
|
|
3657
|
+
arabicName: 'الأسبانية',
|
|
3658
|
+
nativeName: 'Español',
|
|
3659
|
+
detected: 'Idioma detectado',
|
|
3660
|
+
searching: 'Buscando',
|
|
3661
|
+
elevating: 'Elevando a',
|
|
3662
|
+
translating: 'Traduciendo a',
|
|
3663
|
+
generatingAnswersForYou: 'Generando respuestas para ti...',
|
|
3664
|
+
learnMore: 'Aprende más',
|
|
3665
|
+
waitingForResponseMsg: 'Por favor, ten paciencia, todavía estamos pensando',
|
|
3666
|
+
isRtl: false,
|
|
3667
|
+
},
|
|
3668
|
+
{
|
|
3669
|
+
prefix: 'FR',
|
|
3670
|
+
dialect: 'fr-FR',
|
|
3671
|
+
englishName: 'French',
|
|
3672
|
+
arabicName: 'الفرنسية',
|
|
3673
|
+
nativeName: 'Français',
|
|
3674
|
+
detected: 'Langue détectée',
|
|
3675
|
+
searching: 'À la recherche de',
|
|
3676
|
+
elevating: 'Élever à',
|
|
3677
|
+
translating: 'Traduire par',
|
|
3678
|
+
generatingAnswersForYou: 'Générer des réponses pour vous...',
|
|
3679
|
+
learnMore: 'En savoir plus',
|
|
3680
|
+
waitingForResponseMsg: 'Veuillez patienter, nous réfléchissons encore',
|
|
3681
|
+
isRtl: false,
|
|
3682
|
+
},
|
|
3683
|
+
{
|
|
3684
|
+
prefix: 'EN',
|
|
3685
|
+
dialect: 'en-US',
|
|
3686
|
+
englishName: 'English',
|
|
3687
|
+
arabicName: 'الإنجليزية',
|
|
3688
|
+
nativeName: 'English',
|
|
3689
|
+
detected: 'Detected language',
|
|
3690
|
+
searching: 'Searching for',
|
|
3691
|
+
elevating: 'Elevating to',
|
|
3692
|
+
translating: 'Translating to',
|
|
3693
|
+
generatingAnswersForYou: 'Generating answers for you...',
|
|
3694
|
+
learnMore: 'Learn more',
|
|
3695
|
+
waitingForResponseMsg: 'Please bear with us, we are still thinking',
|
|
3696
|
+
isRtl: false,
|
|
3697
|
+
},
|
|
3698
|
+
{
|
|
3699
|
+
prefix: 'AR',
|
|
3700
|
+
dialect: 'ar-AE',
|
|
3701
|
+
englishName: 'Arabic',
|
|
3702
|
+
arabicName: 'العربية',
|
|
3703
|
+
nativeName: 'العربية',
|
|
3704
|
+
detected: 'اللغة المكتشفة',
|
|
3705
|
+
searching: 'البحث عن',
|
|
3706
|
+
elevating: 'رفع إلى',
|
|
3707
|
+
translating: 'ترجمة إلى',
|
|
3708
|
+
generatingAnswersForYou: 'جارٍ إنشاء إجابات لك...',
|
|
3709
|
+
learnMore: 'تعرف على المزيد',
|
|
3710
|
+
waitingForResponseMsg: 'نرجو منك التحلّي بالصبر، نحن ما زلنا نفكر',
|
|
3711
|
+
isRtl: true,
|
|
3712
|
+
},
|
|
3713
|
+
{
|
|
3714
|
+
prefix: 'KA',
|
|
3715
|
+
dialect: 'ka-GE',
|
|
3716
|
+
englishName: 'Georgian',
|
|
3717
|
+
arabicName: 'الجورجية',
|
|
3718
|
+
nativeName: 'ქართული',
|
|
3719
|
+
detected: 'გამოვლენილი ენა',
|
|
3720
|
+
searching: 'ძიება',
|
|
3721
|
+
elevating: 'ამაღლება',
|
|
3722
|
+
translating: 'თარგმნა',
|
|
3723
|
+
generatingAnswersForYou: 'პასუხების გენერირება თქვენთვის...',
|
|
3724
|
+
learnMore: 'გაიგე მეტი',
|
|
3725
|
+
waitingForResponseMsg: 'გთხოვთ მოითმინოთ, ჯერ კიდევ ვფიქრობთ',
|
|
3726
|
+
isRtl: false,
|
|
3727
|
+
},
|
|
3728
|
+
]);
|
|
3729
|
+
/**
|
|
3730
|
+
* Lookup by BCP-47 dialect (e.g. `'ar-AE'`). Case-insensitive per BCP-47 §2.1.1
|
|
3731
|
+
* (subtags are case-insensitive on the wire) — accepts `'en-US'`, `'en-us'`,
|
|
3732
|
+
* `'EN-US'`, etc. all as the canonical EN entry. The catalog itself keeps its
|
|
3733
|
+
* canonical casing for display.
|
|
3734
|
+
*
|
|
3735
|
+
* Returns `undefined` when not found; callers decide whether to fall back to `'en-US'`.
|
|
3736
|
+
*/
|
|
3737
|
+
function findLocaleByDialect(dialect) {
|
|
3738
|
+
if (typeof dialect !== 'string' || dialect.length === 0)
|
|
3739
|
+
return undefined;
|
|
3740
|
+
const lower = dialect.toLowerCase();
|
|
3741
|
+
return FLY_LOCALE_CATALOG.find((e) => e.dialect.toLowerCase() === lower);
|
|
3742
|
+
}
|
|
3743
|
+
/**
|
|
3744
|
+
* Lookup by short prefix (e.g. `'AR'`). Prefixes are unique in the catalog (asserted by tests).
|
|
3745
|
+
* Case-insensitive — accepts `'AR'`, `'ar'`, `'Ar'`. Catalog stores prefixes uppercase
|
|
3746
|
+
* for display.
|
|
3747
|
+
*/
|
|
3748
|
+
function findLocaleByPrefix(prefix) {
|
|
3749
|
+
if (typeof prefix !== 'string' || prefix.length === 0)
|
|
3750
|
+
return undefined;
|
|
3751
|
+
const lower = prefix.toLowerCase();
|
|
3752
|
+
return FLY_LOCALE_CATALOG.find((e) => e.prefix.toLowerCase() === lower);
|
|
3753
|
+
}
|
|
3754
|
+
/**
|
|
3755
|
+
* Type-narrowing helper for entries flagged RTL.
|
|
3756
|
+
*
|
|
3757
|
+
* Named with the `Entry` suffix to avoid colliding with the existing
|
|
3758
|
+
* `isRtlLocale(lang: string)` from `i18n.service.ts`, which answers a different
|
|
3759
|
+
* question (is the *UI* locale code RTL?). Both helpers coexist in `public-api`.
|
|
3760
|
+
*/
|
|
3761
|
+
function isRtlLocaleEntry(entry) {
|
|
3762
|
+
return entry.isRtl === true;
|
|
3763
|
+
}
|
|
3764
|
+
|
|
2368
3765
|
/*
|
|
2369
3766
|
* @mohamedatia/fly-design-system — Public API
|
|
2370
3767
|
* https://www.npmjs.com/package/@mohamedatia/fly-design-system
|
|
@@ -2429,6 +3826,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
2429
3826
|
* own preset pickers. `OuTerm.chartId` and `OuTerm.includeDescendants` are now
|
|
2430
3827
|
* REQUIRED (no longer optional) so wire round-trips are deterministic — emit explicit
|
|
2431
3828
|
* `null` / `boolean` rather than `undefined`. New `AudienceErrorCodes` mirror added.
|
|
3829
|
+
* v2.4.0: SharePanelComponent + AudienceBuilderComponent — `defaultChartSystemKey` and
|
|
3830
|
+
* `defaultChartId` inputs let hosts pre-select a specific chart in the OU picker
|
|
3831
|
+
* without knowing the tenant-scoped Guid. `ShareOrgChartOption` gains an optional
|
|
3832
|
+
* `systemKey` field so platform-managed charts (`'apps'`, `'default-company'`) are
|
|
3833
|
+
* identifiable across tenants. New `SHARE_ORG_CHART_SYSTEM_KEY_APPS` /
|
|
3834
|
+
* `SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT` constants exported.
|
|
3835
|
+
* v2.5.0: Agent input contracts (Phase 1 of the `<fly-agent-input>` plan) — additive only.
|
|
3836
|
+
* `AgentCommandRegistry` + `AgentDropRegistry` (singletons via `providedIn: 'root'`,
|
|
3837
|
+
* shared across federation), `flyAgentDraggable` directive, payload-guard helpers
|
|
3838
|
+
* (`validateAgentPayload`, `trimAgentPayload`, `trimAgentString`, `utf8ByteLength`),
|
|
3839
|
+
* and the cross-cutting `FLY_LOCALE_CATALOG` (31 entries, frozen — verbatim port of
|
|
3840
|
+
* `Fly.Shared.Core.LanguageDTO[]`). Three RTL flagged: `he-IL`, `ur-IN`, `ar-AE`.
|
|
3841
|
+
* The catalog's RTL helper is exported as `isRtlLocaleEntry` to avoid colliding with
|
|
3842
|
+
* the existing `isRtlLocale(lang: string)` from `i18n.service.ts`.
|
|
3843
|
+
* No UI consumers yet — Phase 2 wires the registries into the shell input component.
|
|
3844
|
+
* v2.5.1: New `MessageBoxService.showAcknowledged()` entry point returning
|
|
3845
|
+
* `DialogResultWithAcknowledgement` for "Don't ask again"-style flows.
|
|
3846
|
+
* The `dontAskAgain` config (`MessageBoxDontAskAgainConfig`) lives ONLY on
|
|
3847
|
+
* `MessageBoxOptionsWithAcknowledgement` — the base `MessageBoxOptions`
|
|
3848
|
+
* no longer carries it, so the type system enforces "checkbox ⇒ caller
|
|
3849
|
+
* MUST use `showAcknowledged()`" and eliminates the silent-discard path
|
|
3850
|
+
* on `show()`. Existing `show()` callers are unaffected. Case-insensitive
|
|
3851
|
+
* `findLocaleByDialect` / `findLocaleByPrefix` (BCP-47 §2.1.1). See
|
|
3852
|
+
* `.workflow/plans/agent-input-component.md` Phase 2b-1.
|
|
2432
3853
|
* See docs/ExternalAppsGuide/03-frontend-app.md.
|
|
2433
3854
|
*/
|
|
2434
3855
|
/**
|
|
@@ -2447,5 +3868,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
2447
3868
|
* Generated bundle index. Do not edit.
|
|
2448
3869
|
*/
|
|
2449
3870
|
|
|
2450
|
-
export { AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_FLY_THEME_MODE, DialogResult, FLY_THEME_MODE_IDS, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyThemeService, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_PANEL_DEFAULT_FILE_LEVELS, SharePanelComponent, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WindowManagerService, isRtlLocale, normalizeFlyTheme };
|
|
3871
|
+
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentCommandRegistry, AgentDropRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DialogResult, FLY_LOCALE_CATALOG, FLY_THEME_MODE_IDS, FlyAgentDraggableDirective, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyRemoteRouter, FlyThemeService, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, SharePanelComponent, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WindowManagerService, findLocaleByDialect, findLocaleByPrefix, isRtlLocale, isRtlLocaleEntry, normalizeFlyTheme, trimAgentPayload, trimAgentString, utf8ByteLength, validateAgentPayload };
|
|
2451
3872
|
//# sourceMappingURL=mohamedatia-fly-design-system.mjs.map
|