@flyos/design-system 1.5.0 → 1.7.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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, signal, computed, Injectable, inject, TemplateRef, ViewContainerRef, effect, Input, Directive, ErrorHandler, isDevMode, Pipe, ChangeDetectionStrategy, Component, makeEnvironmentProviders, provideAppInitializer, PLATFORM_ID, DestroyRef, ElementRef, input, output, afterNextRender, HostListener, ViewChild, NgZone, viewChild, DOCUMENT as DOCUMENT$1, EventEmitter, Output, forwardRef, model, viewChildren, untracked, Injector, ViewEncapsulation, Renderer2, contentChildren, contentChild } from '@angular/core';
|
|
3
|
-
import { catchError, throwError, Subject, from, firstValueFrom, of, Observable, map, ReplaySubject, retry, timer } from 'rxjs';
|
|
3
|
+
import { catchError, throwError, Subject, from, firstValueFrom, of, Observable, map, ReplaySubject, retry, timer, tap } from 'rxjs';
|
|
4
4
|
import { HttpClient, HttpParams, HttpHeaders, HttpErrorResponse, HttpEventType } from '@angular/common/http';
|
|
5
5
|
import { switchMap, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators';
|
|
6
6
|
import { HubConnectionBuilder, HttpTransportType, LogLevel, HubConnectionState } from '@microsoft/signalr';
|
|
@@ -48,7 +48,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
|
|
48
48
|
// tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
|
|
49
49
|
// Used only for the diagnostic message; the duplicate-instance detection itself is
|
|
50
50
|
// version-agnostic, so a stale literal misnames a fork rather than hiding one.
|
|
51
|
-
const FLY_DS_VERSION = '1.
|
|
51
|
+
const FLY_DS_VERSION = '1.7.0';
|
|
52
52
|
const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
|
|
53
53
|
/**
|
|
54
54
|
* Records this design-system instance on the shared `scope` and returns the
|
|
@@ -4768,6 +4768,10 @@ function unloadRemoteStyles(appId) {
|
|
|
4768
4768
|
_inFlight.delete(appId);
|
|
4769
4769
|
}
|
|
4770
4770
|
|
|
4771
|
+
/** Accepts a bare class or a `{ default }` module namespace, like Angular's `loadComponent`. */
|
|
4772
|
+
function unwrapLoaded(loaded) {
|
|
4773
|
+
return typeof loaded === 'function' ? loaded : loaded.default;
|
|
4774
|
+
}
|
|
4771
4775
|
/**
|
|
4772
4776
|
* Outlet for FlyOS-embedded routing. Renders the component associated with the
|
|
4773
4777
|
* first matching route in the consumer's `FLY_REMOTE_ROUTES` table, reacting to
|
|
@@ -4787,13 +4791,13 @@ function unloadRemoteStyles(appId) {
|
|
|
4787
4791
|
* ```
|
|
4788
4792
|
*
|
|
4789
4793
|
* Why both? Standalone keeps full Angular routing — query params, guards,
|
|
4790
|
-
* resolvers,
|
|
4791
|
-
*
|
|
4792
|
-
*
|
|
4793
|
-
*
|
|
4794
|
+
* resolvers, RouterLink. Embedded gets a stripped-down outlet that matches
|
|
4795
|
+
* against the same route table the remote declared via `FLY_REMOTE_ROUTES`. The
|
|
4796
|
+
* same `router.navigate(['/foo', id])` call works in both modes; only the
|
|
4797
|
+
* rendering surface differs.
|
|
4794
4798
|
*
|
|
4795
4799
|
* Limitations vs. `<router-outlet>`:
|
|
4796
|
-
* -
|
|
4800
|
+
* - No `loadChildren` (route-level code splitting only — see `loadComponent`).
|
|
4797
4801
|
* - No guards / resolvers / data resolution.
|
|
4798
4802
|
* - No query-param / hash handling — only path segments.
|
|
4799
4803
|
* - No `RouterLink` directive — use `(click)="router.navigate(...)"`.
|
|
@@ -4803,19 +4807,97 @@ function unloadRemoteStyles(appId) {
|
|
|
4803
4807
|
* private readonly router = inject(FlyRemoteRouter);
|
|
4804
4808
|
* readonly id = computed(() => this.router.params()['id'] ?? '');
|
|
4805
4809
|
* ```
|
|
4810
|
+
*
|
|
4811
|
+
* ## Lazy routes
|
|
4812
|
+
*
|
|
4813
|
+
* A route may supply `loadComponent: () => import('./x').then(m => m.XComponent)`
|
|
4814
|
+
* instead of `component:`. The chunk is fetched on first match and cached for the
|
|
4815
|
+
* lifetime of the outlet, so revisiting a route is synchronous.
|
|
4816
|
+
*
|
|
4817
|
+
* Three behaviours are deliberate, and each exists because the obvious
|
|
4818
|
+
* alternative is worse:
|
|
4819
|
+
*
|
|
4820
|
+
* - **The previous component stays mounted while the next one loads.** Blanking
|
|
4821
|
+
* the outlet first would flash empty on every navigation, and a remote's chunks
|
|
4822
|
+
* are served from its own origin — usually a few milliseconds. This matches
|
|
4823
|
+
* Angular's Router, which does not tear down the current route until the next
|
|
4824
|
+
* one is ready.
|
|
4825
|
+
* - **A stale resolution never wins.** Navigating A → B → C while B is still in
|
|
4826
|
+
* flight must not render B over C. Each resolution carries a sequence token and
|
|
4827
|
+
* is discarded unless it is still the latest.
|
|
4828
|
+
* - **A failed load is reported to `ErrorHandler`, not swallowed.** The
|
|
4829
|
+
* chunk-404-after-redeploy self-heal (`provideFlyChunkReloadRecovery`) lives in
|
|
4830
|
+
* the root `ErrorHandler`, and a rejected promise inside an effect would reach
|
|
4831
|
+
* `unhandledrejection` instead — where that handler never sees it. Routing it
|
|
4832
|
+
* here is what lets a stale embedded remote recover. The failed loader is
|
|
4833
|
+
* evicted so a later attempt genuinely retries rather than reusing the
|
|
4834
|
+
* rejected promise.
|
|
4806
4835
|
*/
|
|
4807
4836
|
class FlyRemoteRouterOutletComponent {
|
|
4808
4837
|
router = inject(FlyRemoteRouter);
|
|
4838
|
+
errorHandler = inject(ErrorHandler);
|
|
4809
4839
|
/**
|
|
4810
4840
|
* Read directly from FlyRemoteRouter so the outlet re-renders whenever the
|
|
4811
4841
|
* URL changes (signal-based, OnPush-friendly).
|
|
4812
4842
|
*/
|
|
4813
4843
|
matched = this.router.matchedRoute;
|
|
4844
|
+
/** Loaders that have resolved — consulted synchronously, so a revisit never flashes. */
|
|
4845
|
+
loaded = new Map();
|
|
4846
|
+
/** Loaders currently in flight, so a param change mid-load doesn't start a second fetch. */
|
|
4847
|
+
inFlight = new Map();
|
|
4848
|
+
/** Bumped per resolution attempt; a late `.then` compares against it and bows out. */
|
|
4849
|
+
seq = 0;
|
|
4850
|
+
component = signal(null, ...(ngDevMode ? [{ debugName: "component" }] : /* istanbul ignore next */ []));
|
|
4851
|
+
/** The component class currently projected into `*ngComponentOutlet`. */
|
|
4852
|
+
rendered = this.component.asReadonly();
|
|
4853
|
+
constructor() {
|
|
4854
|
+
effect(() => {
|
|
4855
|
+
const route = this.matched()?.route ?? null;
|
|
4856
|
+
const token = ++this.seq;
|
|
4857
|
+
if (!route) {
|
|
4858
|
+
this.component.set(null);
|
|
4859
|
+
return;
|
|
4860
|
+
}
|
|
4861
|
+
if (route.component) {
|
|
4862
|
+
this.component.set(route.component);
|
|
4863
|
+
return;
|
|
4864
|
+
}
|
|
4865
|
+
const loader = route.loadComponent;
|
|
4866
|
+
const already = this.loaded.get(loader);
|
|
4867
|
+
if (already) {
|
|
4868
|
+
// Synchronous: no microtask gap, so re-matching the same lazy route on a
|
|
4869
|
+
// param change cannot blank or re-create the component.
|
|
4870
|
+
this.component.set(already);
|
|
4871
|
+
return;
|
|
4872
|
+
}
|
|
4873
|
+
let pending = this.inFlight.get(loader);
|
|
4874
|
+
if (!pending) {
|
|
4875
|
+
pending = Promise.resolve(loader()).then(unwrapLoaded);
|
|
4876
|
+
this.inFlight.set(loader, pending);
|
|
4877
|
+
}
|
|
4878
|
+
pending
|
|
4879
|
+
.then((cmp) => {
|
|
4880
|
+
this.loaded.set(loader, cmp);
|
|
4881
|
+
this.inFlight.delete(loader);
|
|
4882
|
+
if (token === this.seq)
|
|
4883
|
+
this.component.set(cmp);
|
|
4884
|
+
})
|
|
4885
|
+
.catch((err) => {
|
|
4886
|
+
// Evict so a retry re-runs the import instead of re-awaiting a rejection.
|
|
4887
|
+
this.inFlight.delete(loader);
|
|
4888
|
+
// Leave the previous component mounted — a blank outlet tells the user
|
|
4889
|
+
// nothing, and Angular's Router likewise keeps the current route on a
|
|
4890
|
+
// failed lazy load.
|
|
4891
|
+
if (token === this.seq)
|
|
4892
|
+
this.errorHandler.handleError(err);
|
|
4893
|
+
});
|
|
4894
|
+
});
|
|
4895
|
+
}
|
|
4814
4896
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyRemoteRouterOutletComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4815
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyRemoteRouterOutletComponent, isStandalone: true, selector: "fly-remote-router-outlet", ngImport: i0, template: `
|
|
4816
|
-
@if (
|
|
4817
|
-
<ng-container *ngComponentOutlet="
|
|
4818
|
-
}
|
|
4897
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyRemoteRouterOutletComponent, isStandalone: true, selector: "fly-remote-router-outlet", ngImport: i0, template: `
|
|
4898
|
+
@if (rendered(); as cmp) {
|
|
4899
|
+
<ng-container *ngComponentOutlet="cmp" />
|
|
4900
|
+
}
|
|
4819
4901
|
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4820
4902
|
}
|
|
4821
4903
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyRemoteRouterOutletComponent, decorators: [{
|
|
@@ -4825,13 +4907,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
4825
4907
|
standalone: true,
|
|
4826
4908
|
imports: [NgComponentOutlet],
|
|
4827
4909
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
4828
|
-
template: `
|
|
4829
|
-
@if (
|
|
4830
|
-
<ng-container *ngComponentOutlet="
|
|
4831
|
-
}
|
|
4910
|
+
template: `
|
|
4911
|
+
@if (rendered(); as cmp) {
|
|
4912
|
+
<ng-container *ngComponentOutlet="cmp" />
|
|
4913
|
+
}
|
|
4832
4914
|
`,
|
|
4833
4915
|
}]
|
|
4834
|
-
}] });
|
|
4916
|
+
}], ctorParameters: () => [] });
|
|
4835
4917
|
|
|
4836
4918
|
/**
|
|
4837
4919
|
* Shared mock AuthService base class for Business App developers.
|
|
@@ -15040,29 +15122,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
15040
15122
|
args: ['dragstart', ['$event']]
|
|
15041
15123
|
}] } });
|
|
15042
15124
|
|
|
15043
|
-
/**
|
|
15044
|
-
*
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
*
|
|
15050
|
-
*
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
const
|
|
15055
|
-
/**
|
|
15056
|
-
* Resolves a `423 Locked` / `SCAN_PENDING` error's `Retry-After` header to a
|
|
15057
|
-
*
|
|
15058
|
-
*
|
|
15059
|
-
*
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
const header = err.headers?.get('Retry-After');
|
|
15125
|
+
/**
|
|
15126
|
+
* Files-Manager answers a fresh (still-scanning) upload with `423 Locked` / `SCAN_PENDING` until
|
|
15127
|
+
* the background virus scan clears it.
|
|
15128
|
+
*/
|
|
15129
|
+
const FLY_SCAN_PENDING_STATUS = 423;
|
|
15130
|
+
/**
|
|
15131
|
+
* Retry budget. The scan usually clears within a few seconds, so six attempts comfortably covers
|
|
15132
|
+
* the common case without hammering the endpoint indefinitely on a stuck scan.
|
|
15133
|
+
*/
|
|
15134
|
+
const FLY_SCAN_PENDING_MAX_RETRIES = 6;
|
|
15135
|
+
/** Fallback delay when the response carries no (or an unparsable) `Retry-After` header. */
|
|
15136
|
+
const FLY_SCAN_PENDING_DEFAULT_DELAY_MS = 2000;
|
|
15137
|
+
/**
|
|
15138
|
+
* Resolves a `423 Locked` / `SCAN_PENDING` error's `Retry-After` header to a delay in
|
|
15139
|
+
* milliseconds. The header is advisory and, per RFC 9110, either a number of seconds or an
|
|
15140
|
+
* HTTP-date — both are handled. Falls back to {@link FLY_SCAN_PENDING_DEFAULT_DELAY_MS} when the
|
|
15141
|
+
* header is missing, unparsable, or already in the past.
|
|
15142
|
+
*/
|
|
15143
|
+
function flyScanPendingDelayMs(err) {
|
|
15144
|
+
const header = err?.headers?.get('Retry-After');
|
|
15064
15145
|
if (!header)
|
|
15065
|
-
return
|
|
15146
|
+
return FLY_SCAN_PENDING_DEFAULT_DELAY_MS;
|
|
15066
15147
|
const seconds = Number(header);
|
|
15067
15148
|
if (Number.isFinite(seconds) && seconds >= 0)
|
|
15068
15149
|
return seconds * 1000;
|
|
@@ -15072,8 +15153,36 @@ function scanPendingDelayMs(err) {
|
|
|
15072
15153
|
if (diff > 0)
|
|
15073
15154
|
return diff;
|
|
15074
15155
|
}
|
|
15075
|
-
return
|
|
15156
|
+
return FLY_SCAN_PENDING_DEFAULT_DELAY_MS;
|
|
15157
|
+
}
|
|
15158
|
+
/**
|
|
15159
|
+
* Rides out the `423 SCAN_PENDING` window on any authenticated Files-Manager request.
|
|
15160
|
+
*
|
|
15161
|
+
* A file is not readable for the first seconds of its life — Files-Manager holds it at `423` until
|
|
15162
|
+
* the virus scan clears. Without this, the first read after an upload or an export completes just
|
|
15163
|
+
* fails, which is exactly when a user is most likely to click.
|
|
15164
|
+
*
|
|
15165
|
+
* **This honours `Retry-After`.** Every hand-rolled copy of this policy across the platform used a
|
|
15166
|
+
* flat `timer(2000)`, which is both slower than necessary when the server says "200ms" and a
|
|
15167
|
+
* hammer when it says "30s". The header is advisory, so the flat fallback remains for responses
|
|
15168
|
+
* that omit it.
|
|
15169
|
+
*
|
|
15170
|
+
* Only `423` is retried. A terminal outcome — `403 SCAN_INFECTED`, `422 SCAN_FAILED`, `401`, or
|
|
15171
|
+
* anything else — re-throws immediately: the `delay` notifier erroring stops `retry` at once
|
|
15172
|
+
* rather than waiting out the budget.
|
|
15173
|
+
*
|
|
15174
|
+
* @example
|
|
15175
|
+
* this.http.get(url, { responseType: 'blob' }).pipe(flyScanRetry())
|
|
15176
|
+
*/
|
|
15177
|
+
function flyScanRetry(options = {}) {
|
|
15178
|
+
return retry({
|
|
15179
|
+
count: options.count ?? FLY_SCAN_PENDING_MAX_RETRIES,
|
|
15180
|
+
delay: (err) => err?.status === FLY_SCAN_PENDING_STATUS
|
|
15181
|
+
? timer(flyScanPendingDelayMs(err))
|
|
15182
|
+
: throwError(() => err),
|
|
15183
|
+
});
|
|
15076
15184
|
}
|
|
15185
|
+
|
|
15077
15186
|
/**
|
|
15078
15187
|
* Renders an authenticated image into a host `<img>` element by fetching the
|
|
15079
15188
|
* resource as a blob through Angular's `HttpClient` and binding the resulting
|
|
@@ -15122,7 +15231,7 @@ function scanPendingDelayMs(err) {
|
|
|
15122
15231
|
* few seconds of its life; the Files Manager download endpoint answers
|
|
15123
15232
|
* `423 Locked` / `SCAN_PENDING` (with an advisory `Retry-After` header) until
|
|
15124
15233
|
* the scan clears. Rather than treating that like a permission error, the
|
|
15125
|
-
* directive retries the fetch — up to {@link
|
|
15234
|
+
* directive retries the fetch — up to {@link FLY_SCAN_PENDING_MAX_RETRIES} times,
|
|
15126
15235
|
* honoring `Retry-After` when present (falling back to a flat 2s) — and swaps
|
|
15127
15236
|
* in the image automatically once the scan reports Clean. This mirrors the
|
|
15128
15237
|
* hand-rolled `retry({ count: 6, delay: … })` policy already used by the
|
|
@@ -15189,12 +15298,7 @@ class FlySecureSrcDirective {
|
|
|
15189
15298
|
// scan clears. Any other error (401/403/404, terminal 403
|
|
15190
15299
|
// SCAN_INFECTED, 422 SCAN_FAILED) re-throws immediately — the `delay`
|
|
15191
15300
|
// notifier erroring stops `retry` right away without waiting.
|
|
15192
|
-
|
|
15193
|
-
count: SCAN_PENDING_MAX_RETRIES,
|
|
15194
|
-
delay: (err) => err?.status === SCAN_PENDING_STATUS
|
|
15195
|
-
? timer(scanPendingDelayMs(err))
|
|
15196
|
-
: throwError(() => err),
|
|
15197
|
-
}))
|
|
15301
|
+
flyScanRetry())
|
|
15198
15302
|
.subscribe({
|
|
15199
15303
|
next: (blob) => {
|
|
15200
15304
|
const objectUrl = URL.createObjectURL(blob);
|
|
@@ -17328,6 +17432,130 @@ function flyExportFileName(contentDisposition, fallback) {
|
|
|
17328
17432
|
}
|
|
17329
17433
|
}
|
|
17330
17434
|
|
|
17435
|
+
/**
|
|
17436
|
+
* Downloads a Files-Manager file through the authenticated `HttpClient` and hands it to the
|
|
17437
|
+
* browser's save dialog.
|
|
17438
|
+
*
|
|
17439
|
+
* **Why this exists rather than an `<a href>`.** `/api/files/{id}/download` is bearer-gated and
|
|
17440
|
+
* Files-Manager exposes no signed or public URL, so a browser-native navigation carries no
|
|
17441
|
+
* `Authorization` header and answers **401**. That is not a hypothetical: three Circles templates
|
|
17442
|
+
* shipped plain `<a href>` download links that were simply broken, Thoughts hit the same 401 on
|
|
17443
|
+
* its story attachments, and in both apps other screens had already hand-rolled the correct blob
|
|
17444
|
+
* fetch inline. The design system shipped the *save* half (`flyDownloadBlob`) and the image-side
|
|
17445
|
+
* *fetch* half (`img[flySecureSrc]`) but nothing for a plain authenticated download, so every app
|
|
17446
|
+
* rediscovered the same 401 and wrote the same fix.
|
|
17447
|
+
*
|
|
17448
|
+
* It also rides out the `423 SCAN_PENDING` window via {@link flyScanRetry}: a freshly-produced
|
|
17449
|
+
* export or upload is still being virus-scanned for the first seconds of its life, which is
|
|
17450
|
+
* exactly when a user clicks.
|
|
17451
|
+
*
|
|
17452
|
+
* @example
|
|
17453
|
+
* private readonly downloads = inject(FlyFileDownloadService);
|
|
17454
|
+
* this.downloads.download(attachment.fileId, attachment.fileName);
|
|
17455
|
+
*/
|
|
17456
|
+
class FlyFileDownloadService {
|
|
17457
|
+
http = inject(HttpClient);
|
|
17458
|
+
/**
|
|
17459
|
+
* Fetches a file and saves it, subscribing internally — the fire-and-forget form for a click
|
|
17460
|
+
* handler that has no state to drive.
|
|
17461
|
+
*
|
|
17462
|
+
* Errors are left to the app's global `HttpClient` error handling. Use {@link fetch} instead
|
|
17463
|
+
* when the caller needs to show a spinner or a per-row failure state.
|
|
17464
|
+
*
|
|
17465
|
+
* @param fileIdOrUrl A bare Files-Manager file id, or a path starting with `/` used verbatim.
|
|
17466
|
+
* @param fileName Name offered to the OS save dialog, extension included.
|
|
17467
|
+
*/
|
|
17468
|
+
download(fileIdOrUrl, fileName) {
|
|
17469
|
+
this.fetch(fileIdOrUrl, fileName).subscribe({
|
|
17470
|
+
// Swallowing here would hide a 403 SCAN_INFECTED behind a button that silently does
|
|
17471
|
+
// nothing; the app's error interceptor is the right place to surface it.
|
|
17472
|
+
error: () => undefined,
|
|
17473
|
+
});
|
|
17474
|
+
}
|
|
17475
|
+
/**
|
|
17476
|
+
* The observable form: emits the blob after saving it, so a caller can drive its own loading
|
|
17477
|
+
* and error state. The save happens on emission — subscribing is what triggers the download.
|
|
17478
|
+
*
|
|
17479
|
+
* @param fileIdOrUrl A bare Files-Manager file id, or a path starting with `/` used verbatim.
|
|
17480
|
+
* @param fileName Name offered to the OS save dialog, extension included.
|
|
17481
|
+
*/
|
|
17482
|
+
fetch(fileIdOrUrl, fileName) {
|
|
17483
|
+
const url = fileIdOrUrl.startsWith('/') ? fileIdOrUrl : `/api/files/${fileIdOrUrl}/download`;
|
|
17484
|
+
return this.http
|
|
17485
|
+
.get(url, { responseType: 'blob' })
|
|
17486
|
+
.pipe(flyScanRetry(), tap((blob) => flyDownloadBlob(blob, fileName)));
|
|
17487
|
+
}
|
|
17488
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFileDownloadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
17489
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFileDownloadService, providedIn: 'root' });
|
|
17490
|
+
}
|
|
17491
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFileDownloadService, decorators: [{
|
|
17492
|
+
type: Injectable,
|
|
17493
|
+
args: [{ providedIn: 'root' }]
|
|
17494
|
+
}] });
|
|
17495
|
+
|
|
17496
|
+
/**
|
|
17497
|
+
* Unwraps `FlyApiResponse<T>` to its payload, or `null` when the call did not succeed.
|
|
17498
|
+
*
|
|
17499
|
+
* This is the STRICT reading: `success: false` yields `null` even if a partial `data` came with
|
|
17500
|
+
* it. Prefer it. Use {@link flyUnwrapLenient} only against an endpoint that is known to answer
|
|
17501
|
+
* unwrapped.
|
|
17502
|
+
*/
|
|
17503
|
+
function flyUnwrap(res) {
|
|
17504
|
+
return res.success ? res.data ?? null : null;
|
|
17505
|
+
}
|
|
17506
|
+
/**
|
|
17507
|
+
* Unwraps `FlyApiResponse<T>`, tolerating an endpoint that answered UNWRAPPED — i.e. returned the
|
|
17508
|
+
* payload as the whole body with no envelope around it.
|
|
17509
|
+
*
|
|
17510
|
+
* **This is a workaround for a backend that is inconsistent, not a preference.** Every endpoint is
|
|
17511
|
+
* supposed to wrap its payload; a handful do not, and call sites carried a defensive
|
|
17512
|
+
* `res.data ?? res` for them. That expression forced the surrounding chain to `any`, which
|
|
17513
|
+
* silenced the mismatch rather than naming it. This keeps the identical runtime behaviour while
|
|
17514
|
+
* staying typed — but a call site reaching for it is evidence of an endpoint worth fixing, and it
|
|
17515
|
+
* cannot distinguish "unwrapped payload" from "envelope with `success: false`".
|
|
17516
|
+
*/
|
|
17517
|
+
function flyUnwrapLenient(res) {
|
|
17518
|
+
return res.data ?? res;
|
|
17519
|
+
}
|
|
17520
|
+
/**
|
|
17521
|
+
* Normalizes the `FlyApiResponse<FlyPaged<T>>` envelope to the flat `{ items, total }` a list UI
|
|
17522
|
+
* reads. An absent `total` falls back to the row count, so a server that omits it still renders a
|
|
17523
|
+
* correct count for the page in hand rather than `0`.
|
|
17524
|
+
*/
|
|
17525
|
+
function flyToPage(res) {
|
|
17526
|
+
const items = res.data?.items ?? [];
|
|
17527
|
+
return { items, total: res.data?.total ?? items.length };
|
|
17528
|
+
}
|
|
17529
|
+
/**
|
|
17530
|
+
* Best-effort extraction of the message (usually an i18n key) out of an
|
|
17531
|
+
* `HttpErrorResponse.error` body returned by a FlyOS endpoint.
|
|
17532
|
+
*
|
|
17533
|
+
* The wire format is inconsistent by design: business-rule failures arrive as `{ errors: [key] }`,
|
|
17534
|
+
* the structured envelope as `{ error: { code, message } }`, framework failures as `{ message }`,
|
|
17535
|
+
* and some proxies hand back a bare string. Call sites used to inline this probe behind
|
|
17536
|
+
* `err?.error as any`, typing the whole chain as `any` and duplicating the logic verbatim.
|
|
17537
|
+
*
|
|
17538
|
+
* @returns the first `errors[]` entry, else `error.message`, else `message`, else the raw string
|
|
17539
|
+
* body — or `undefined` when none of those is present.
|
|
17540
|
+
*/
|
|
17541
|
+
function flyApiErrorMessage(err) {
|
|
17542
|
+
const raw = err?.error;
|
|
17543
|
+
if (typeof raw === 'string')
|
|
17544
|
+
return raw;
|
|
17545
|
+
if (raw && typeof raw === 'object') {
|
|
17546
|
+
const body = raw;
|
|
17547
|
+
if (Array.isArray(body.errors) && body.errors.length > 0)
|
|
17548
|
+
return String(body.errors[0]);
|
|
17549
|
+
const nested = body.error;
|
|
17550
|
+
if (nested && typeof nested === 'object' && typeof nested.message === 'string') {
|
|
17551
|
+
return nested.message;
|
|
17552
|
+
}
|
|
17553
|
+
if (typeof body.message === 'string')
|
|
17554
|
+
return body.message;
|
|
17555
|
+
}
|
|
17556
|
+
return undefined;
|
|
17557
|
+
}
|
|
17558
|
+
|
|
17331
17559
|
/**
|
|
17332
17560
|
* Shared presence-colour utility — one deterministic seed → colour mapping so
|
|
17333
17561
|
* every co-authoring surface (mind-maps, canvas-boards, and future Documents/
|
|
@@ -21849,5 +22077,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
21849
22077
|
* Generated bundle index. Do not edit.
|
|
21850
22078
|
*/
|
|
21851
22079
|
|
|
21852
|
-
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, overlayStack, parseCron, parseStepUpChallenge, parseTags, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
22080
|
+
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, overlayStack, parseCron, parseStepUpChallenge, parseTags, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
21853
22081
|
//# sourceMappingURL=flyos-design-system.mjs.map
|