@mintplayer/ng-spark 22.31.0 → 22.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -1
- package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-account.mjs +18 -18
- package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +16 -8
- package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-core.mjs +6 -6
- package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-login.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-register.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-client-operations.mjs +24 -24
- package/fesm2022/mintplayer-ng-spark-column-filter.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-contributions.mjs +9 -9
- package/fesm2022/mintplayer-ng-spark-grid.mjs +220 -49
- package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-history.mjs +9 -9
- package/fesm2022/mintplayer-ng-spark-icon.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +9 -9
- package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +6 -6
- package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-models.mjs +44 -1
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-moderation.mjs +21 -21
- package/fesm2022/mintplayer-ng-spark-notifications.mjs +687 -0
- package/fesm2022/mintplayer-ng-spark-notifications.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs +179 -0
- package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-organizations.mjs +202 -0
- package/fesm2022/mintplayer-ng-spark-organizations.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-pipes.mjs +125 -85
- package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-create.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs +90 -26
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs +84 -12
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +9 -9
- package/fesm2022/mintplayer-ng-spark-pwa.mjs +74 -6
- package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-query-list.mjs +10 -109
- package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-renderers.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-score-renderers.mjs +801 -0
- package/fesm2022/mintplayer-ng-spark-score-renderers.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-services.mjs +424 -72
- package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-share-page.mjs +334 -0
- package/fesm2022/mintplayer-ng-spark-share-page.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-share.mjs +10 -0
- package/fesm2022/mintplayer-ng-spark-share.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-shell.mjs +38 -36
- package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +9 -9
- package/package.json +25 -1
- package/types/mintplayer-ng-spark-grid.d.ts +77 -26
- package/types/mintplayer-ng-spark-models.d.ts +150 -17
- package/types/mintplayer-ng-spark-notifications.d.ts +188 -0
- package/types/mintplayer-ng-spark-organizations-accept.d.ts +40 -0
- package/types/mintplayer-ng-spark-organizations.d.ts +72 -0
- package/types/mintplayer-ng-spark-pipes.d.ts +10 -0
- package/types/mintplayer-ng-spark-po-detail.d.ts +26 -0
- package/types/mintplayer-ng-spark-po-edit.d.ts +37 -0
- package/types/mintplayer-ng-spark-pwa.d.ts +48 -2
- package/types/mintplayer-ng-spark-query-list.d.ts +8 -23
- package/types/mintplayer-ng-spark-score-renderers.d.ts +340 -0
- package/types/mintplayer-ng-spark-services.d.ts +146 -6
- package/types/mintplayer-ng-spark-share-page.d.ts +67 -0
- package/types/mintplayer-ng-spark-share.d.ts +26 -0
|
@@ -1,9 +1,74 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { ApplicationRef, DestroyRef, Injectable, InjectionToken, PLATFORM_ID, inject, isDevMode, makeEnvironmentProviders, provideAppInitializer, signal } from "@angular/core";
|
|
3
|
-
import { SwUpdate, provideServiceWorker } from "@angular/service-worker";
|
|
3
|
+
import { SwPush, SwUpdate, provideServiceWorker } from "@angular/service-worker";
|
|
4
4
|
import { DOCUMENT, Location, isPlatformBrowser } from "@angular/common";
|
|
5
5
|
import { NavigationStart, Router } from "@angular/router";
|
|
6
6
|
import { Subscription, filter, first } from "rxjs";
|
|
7
|
+
function sparkInAppUrl(url, document, location) {
|
|
8
|
+
if (!url) return null;
|
|
9
|
+
let parsed;
|
|
10
|
+
try {
|
|
11
|
+
parsed = new URL(url, document.baseURI);
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
16
|
+
if (parsed.origin !== document.location?.origin) return null;
|
|
17
|
+
const path = location ? location.normalize(parsed.pathname) : parsed.pathname;
|
|
18
|
+
return (path.startsWith("/") ? path : "/" + path) + parsed.search + parsed.hash;
|
|
19
|
+
}
|
|
20
|
+
var SparkNotificationClicks = class SparkNotificationClicks {
|
|
21
|
+
swPush = inject(SwPush);
|
|
22
|
+
router = inject(Router, { optional: true });
|
|
23
|
+
location = inject(Location, { optional: true });
|
|
24
|
+
document = inject(DOCUMENT);
|
|
25
|
+
platformId = inject(PLATFORM_ID);
|
|
26
|
+
destroyRef = inject(DestroyRef);
|
|
27
|
+
started = false;
|
|
28
|
+
start() {
|
|
29
|
+
if (this.started) return;
|
|
30
|
+
this.started = true;
|
|
31
|
+
if (!isPlatformBrowser(this.platformId) || !this.swPush.isEnabled || !this.router) return;
|
|
32
|
+
const subscription = this.swPush.notificationClicks.subscribe(({ notification }) => {
|
|
33
|
+
const data = notification?.data;
|
|
34
|
+
this.navigate(typeof data?.url === "string" ? data.url : null);
|
|
35
|
+
});
|
|
36
|
+
this.destroyRef.onDestroy(() => subscription.unsubscribe());
|
|
37
|
+
}
|
|
38
|
+
navigate(url) {
|
|
39
|
+
const target = sparkInAppUrl(url, this.document, this.location);
|
|
40
|
+
if (!target || !this.router || this.router.url === target) return;
|
|
41
|
+
this.router.navigateByUrl(target).catch(() => this.assign(new URL(url, this.document.baseURI).href));
|
|
42
|
+
}
|
|
43
|
+
assign(href) {
|
|
44
|
+
this.document.defaultView?.location.assign(href);
|
|
45
|
+
}
|
|
46
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
47
|
+
minVersion: "12.0.0",
|
|
48
|
+
version: "22.2.2",
|
|
49
|
+
ngImport: i0,
|
|
50
|
+
type: SparkNotificationClicks,
|
|
51
|
+
deps: [],
|
|
52
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
53
|
+
});
|
|
54
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
55
|
+
minVersion: "12.0.0",
|
|
56
|
+
version: "22.2.2",
|
|
57
|
+
ngImport: i0,
|
|
58
|
+
type: SparkNotificationClicks,
|
|
59
|
+
providedIn: "root"
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
63
|
+
minVersion: "12.0.0",
|
|
64
|
+
version: "22.2.2",
|
|
65
|
+
ngImport: i0,
|
|
66
|
+
type: SparkNotificationClicks,
|
|
67
|
+
decorators: [{
|
|
68
|
+
type: Injectable,
|
|
69
|
+
args: [{ providedIn: "root" }]
|
|
70
|
+
}]
|
|
71
|
+
});
|
|
7
72
|
const SPARK_SW_PAGE_LOADER = new InjectionToken("SPARK_SW_PAGE_LOADER", {
|
|
8
73
|
providedIn: "root",
|
|
9
74
|
factory: () => {
|
|
@@ -121,7 +186,7 @@ var SparkServiceWorkerUpdates = class SparkServiceWorkerUpdates {
|
|
|
121
186
|
}
|
|
122
187
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
123
188
|
minVersion: "12.0.0",
|
|
124
|
-
version: "22.2.
|
|
189
|
+
version: "22.2.2",
|
|
125
190
|
ngImport: i0,
|
|
126
191
|
type: SparkServiceWorkerUpdates,
|
|
127
192
|
deps: [],
|
|
@@ -129,7 +194,7 @@ var SparkServiceWorkerUpdates = class SparkServiceWorkerUpdates {
|
|
|
129
194
|
});
|
|
130
195
|
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
131
196
|
minVersion: "12.0.0",
|
|
132
|
-
version: "22.2.
|
|
197
|
+
version: "22.2.2",
|
|
133
198
|
ngImport: i0,
|
|
134
199
|
type: SparkServiceWorkerUpdates,
|
|
135
200
|
providedIn: "root"
|
|
@@ -137,7 +202,7 @@ var SparkServiceWorkerUpdates = class SparkServiceWorkerUpdates {
|
|
|
137
202
|
};
|
|
138
203
|
i0.ɵɵngDeclareClassMetadata({
|
|
139
204
|
minVersion: "12.0.0",
|
|
140
|
-
version: "22.2.
|
|
205
|
+
version: "22.2.2",
|
|
141
206
|
ngImport: i0,
|
|
142
207
|
type: SparkServiceWorkerUpdates,
|
|
143
208
|
decorators: [{
|
|
@@ -155,8 +220,11 @@ function provideSparkServiceWorker(options = {}) {
|
|
|
155
220
|
return makeEnvironmentProviders([provideServiceWorker(options.script ?? "ngsw-worker.js", {
|
|
156
221
|
enabled: options.enabled ?? !isDevMode(),
|
|
157
222
|
registrationStrategy: options.registrationStrategy ?? "registerWhenStable:30000"
|
|
158
|
-
}), provideAppInitializer(() =>
|
|
223
|
+
}), provideAppInitializer(() => {
|
|
224
|
+
inject(SparkServiceWorkerUpdates).start();
|
|
225
|
+
inject(SparkNotificationClicks).start();
|
|
226
|
+
})]);
|
|
159
227
|
}
|
|
160
|
-
export { SPARK_SW_PAGE_LOADER, SPARK_SW_SCHEDULE, SparkServiceWorkerUpdates, provideSparkServiceWorker };
|
|
228
|
+
export { SPARK_SW_PAGE_LOADER, SPARK_SW_SCHEDULE, SparkNotificationClicks, SparkServiceWorkerUpdates, provideSparkServiceWorker, sparkInAppUrl };
|
|
161
229
|
|
|
162
230
|
//# sourceMappingURL=mintplayer-ng-spark-pwa.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-pwa.mjs","names":[],"sources":["../../pwa/src/spark-service-worker-updates.ts","../../pwa/src/provide-spark-service-worker.ts"],"sourcesContent":["import { DOCUMENT, Location, isPlatformBrowser } from '@angular/common';\nimport {\n ApplicationRef,\n DestroyRef,\n Injectable,\n InjectionToken,\n PLATFORM_ID,\n inject,\n signal,\n} from '@angular/core';\nimport { NavigationStart, Router } from '@angular/router';\nimport { SwUpdate, VersionReadyEvent } from '@angular/service-worker';\nimport { Subscription, filter, first } from 'rxjs';\n\n/** A service-worker version as `ngsw.json` describes it. `appData` is the config's `appData`. */\nexport interface SparkAppVersion {\n hash: string;\n appData?: Record<string, unknown>;\n}\n\n/** The two page loads the update policy performs. Replaceable for tests. */\nexport interface SparkPageLoader {\n assign(url: string): void;\n reload(): void;\n}\n\n/** How the update policy loads a page: `window.location` by default. */\nexport const SPARK_SW_PAGE_LOADER = new InjectionToken<SparkPageLoader>('SPARK_SW_PAGE_LOADER', {\n providedIn: 'root',\n factory: () => {\n const location = inject(DOCUMENT).defaultView?.location;\n return {\n assign: (url: string) => location?.assign(url),\n reload: () => location?.reload(),\n };\n },\n});\n\n/** Update-check timing. Defaults: every 6 h, and on becoming visible at most once a minute. */\nexport interface SparkServiceWorkerSchedule {\n checkIntervalMs: number;\n visibleCheckThrottleMs: number;\n}\n\nexport const SPARK_SW_SCHEDULE = new InjectionToken<SparkServiceWorkerSchedule>('SPARK_SW_SCHEDULE', {\n providedIn: 'root',\n factory: () => ({ checkIntervalMs: 6 * 60 * 60 * 1000, visibleCheckThrottleMs: 60 * 1000 }),\n});\n\n/**\n * Remembers, per tab, the version a full load was made for. If the page that load produced still\n * reports the same version as ready (another tab keeps the old version alive, or the worker did not\n * hand this client the new one), the policy does not reload again: one attempt per version.\n */\nconst RELOADED_FOR_KEY = 'spark-sw-reloaded-for';\n\n/**\n * The update policy of `provideSparkServiceWorker()` (#464, Q1b).\n *\n * - `VERSION_READY`: the **next** router navigation becomes a full page load of its target, so the\n * user lands on the page they asked for, in the new version, without losing anything on the page\n * they are leaving (the navigation was leaving it anyway).\n * - `unrecoverable`: the running version can no longer be served, so the page reloads at once.\n * - Checks: once the application is stable, every 6 h, and when the tab becomes visible again\n * (at most once a minute).\n * - **A hidden tab never reloads.** Spark has no unsaved-changes tracking to consult, so a reload\n * nobody is looking at could throw away an open edit. An `unrecoverable` in a hidden tab waits\n * until the tab is visible; a navigation in a hidden tab stays a normal in-app navigation.\n *\n * Nothing happens when the service worker is disabled (development builds, unsupported browsers)\n * or on the server.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkServiceWorkerUpdates {\n private readonly swUpdate = inject(SwUpdate);\n private readonly router = inject(Router, { optional: true });\n private readonly location = inject(Location, { optional: true });\n private readonly appRef = inject(ApplicationRef);\n private readonly document = inject(DOCUMENT);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly destroyRef = inject(DestroyRef);\n private readonly pageLoader = inject(SPARK_SW_PAGE_LOADER);\n private readonly schedule = inject(SPARK_SW_SCHEDULE);\n\n private readonly _updateReady = signal(false);\n private readonly _currentVersion = signal<SparkAppVersion | null>(null);\n private readonly _latestVersion = signal<SparkAppVersion | null>(null);\n\n /** True once a new version is downloaded and the next navigation will load it. */\n readonly updateReady = this._updateReady.asReadonly();\n /** The running version, known once an update was reported. */\n readonly currentVersion = this._currentVersion.asReadonly();\n /** The version the next navigation loads (`appData.build` is the ng-spark version). */\n readonly latestVersion = this._latestVersion.asReadonly();\n\n private started = false;\n private leaving = false;\n private reloadWhenVisible = false;\n private lastCheck = 0;\n\n /** Starts the policy. Idempotent; called by the app initializer `provideSparkServiceWorker()` adds. */\n start(): void {\n if (this.started) return;\n this.started = true;\n\n if (!isPlatformBrowser(this.platformId) || !this.swUpdate.isEnabled) return;\n\n const subscriptions = new Subscription();\n this.destroyRef.onDestroy(() => subscriptions.unsubscribe());\n\n subscriptions.add(\n this.swUpdate.versionUpdates\n .pipe(filter((event): event is VersionReadyEvent => event.type === 'VERSION_READY'))\n .subscribe((event) => this.onVersionReady(event)),\n );\n\n subscriptions.add(this.swUpdate.unrecoverable.subscribe(() => this.onUnrecoverable()));\n\n if (this.router) {\n subscriptions.add(\n this.router.events\n .pipe(filter((event): event is NavigationStart => event instanceof NavigationStart))\n .subscribe((event) => this.onNavigationStart(event)),\n );\n }\n\n // The first check waits for stability, so it never competes with the initial render.\n subscriptions.add(\n this.appRef.isStable.pipe(first((stable) => stable)).subscribe(() => {\n this.check();\n const interval = setInterval(() => this.check(), this.schedule.checkIntervalMs);\n subscriptions.add(() => clearInterval(interval));\n }),\n );\n\n const onVisibilityChange = () => {\n if (this.isHidden()) return;\n if (this.reloadWhenVisible) {\n this.reloadWhenVisible = false;\n this.reload();\n return;\n }\n if (Date.now() - this.lastCheck >= this.schedule.visibleCheckThrottleMs) this.check();\n };\n this.document.addEventListener('visibilitychange', onVisibilityChange);\n subscriptions.add(() => this.document.removeEventListener('visibilitychange', onVisibilityChange));\n }\n\n private onVersionReady(event: VersionReadyEvent): void {\n const latest = toVersion(event.latestVersion);\n this._currentVersion.set(toVersion(event.currentVersion));\n this._latestVersion.set(latest);\n\n if (this.readReloadedFor() === latest.hash) {\n // This page is the full load made for that version and it is still not the one served.\n // Reloading again would loop; the next deploy, or the next fresh tab, picks it up.\n return;\n }\n this._updateReady.set(true);\n }\n\n private onUnrecoverable(): void {\n if (this.isHidden()) {\n this.reloadWhenVisible = true;\n return;\n }\n this.reload();\n }\n\n private onNavigationStart(event: NavigationStart): void {\n if (!this._updateReady() || this.leaving || this.isHidden()) return;\n\n this.leaving = true;\n this.writeReloadedFor(this._latestVersion()?.hash);\n this.router?.currentNavigation()?.abort();\n\n if (event.navigationTrigger === 'popstate') {\n // Back/forward: the address bar already shows the target, and assign() would add a\n // duplicate history entry.\n this.pageLoader.reload();\n return;\n }\n\n // event.url is relative to the router's base; prepareExternalUrl adds the <base href>.\n const target = this.location ? this.location.prepareExternalUrl(event.url) : event.url;\n this.pageLoader.assign(target);\n }\n\n private check(): void {\n this.lastCheck = Date.now();\n this.swUpdate.checkForUpdate().catch(() => {\n // Offline, or the worker is gone. The next scheduled check tries again.\n });\n }\n\n private reload(): void {\n if (this.leaving) return;\n this.leaving = true;\n this.pageLoader.reload();\n }\n\n private isHidden(): boolean {\n return this.document.visibilityState === 'hidden';\n }\n\n private readReloadedFor(): string | null {\n try {\n return this.document.defaultView?.sessionStorage.getItem(RELOADED_FOR_KEY) ?? null;\n } catch {\n return null;\n }\n }\n\n private writeReloadedFor(hash: string | undefined): void {\n if (!hash) return;\n try {\n this.document.defaultView?.sessionStorage.setItem(RELOADED_FOR_KEY, hash);\n } catch {\n // Storage blocked: the guard is best effort; one reload per navigation is still bounded.\n }\n }\n}\n\nfunction toVersion(version: { hash: string; appData?: object }): SparkAppVersion {\n return { hash: version.hash, appData: version.appData as Record<string, unknown> | undefined };\n}\n","import { EnvironmentProviders, inject, isDevMode, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';\nimport { SwRegistrationOptions, provideServiceWorker } from '@angular/service-worker';\n\nimport { SparkServiceWorkerUpdates } from './spark-service-worker-updates';\n\nexport interface SparkServiceWorkerOptions {\n /** Whether the worker registers. Default: `!isDevMode()`, so only production builds register it. */\n enabled?: boolean;\n /** The worker script, relative to the base href. Default: `ngsw-worker.js`. */\n script?: string;\n /** When the worker registers. Default: `registerWhenStable:30000`. */\n registrationStrategy?: SwRegistrationOptions['registrationStrategy'];\n}\n\n/**\n * Registers Angular's service worker with Spark's update policy (#464, Q1b); see\n * {@link SparkServiceWorkerUpdates} for what happens when a new version is deployed.\n *\n * The application also needs an `ngsw-config.json` with Spark's navigation exclusions and\n * `serviceWorker` in its production build configuration; see `docs/guide-pwa.md`.\n */\nexport function provideSparkServiceWorker(options: SparkServiceWorkerOptions = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n provideServiceWorker(options.script ?? 'ngsw-worker.js', {\n enabled: options.enabled ?? !isDevMode(),\n registrationStrategy: options.registrationStrategy ?? 'registerWhenStable:30000',\n }),\n provideAppInitializer(() => inject(SparkServiceWorkerUpdates).start()),\n ]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-pwa.mjs","names":[],"sources":["../../pwa/src/spark-in-app-url.ts","../../pwa/src/spark-notification-clicks.ts","../../pwa/src/spark-service-worker-updates.ts","../../pwa/src/provide-spark-service-worker.ts"],"sourcesContent":["import { Location } from '@angular/common';\n\n/**\n * The router URL for `url` when it points into this application, else `null`.\n *\n * `url` may be app-relative (`/runs/42`) or absolute. It is resolved against the document's base URI;\n * only an `http(s)` URL on the page's own origin is in-app, and its path loses the `<base href>`\n * (through `Location.normalize`) because the router works below it. Everything else (another origin,\n * `javascript:`, `mailto:`, an unparseable value) is `null`, so a caller never hands data-borne URLs\n * to the router or to `location.assign` unchecked.\n */\nexport function sparkInAppUrl(url: string | null | undefined, document: Document, location: Location | null): string | null {\n if (!url) return null;\n let parsed: URL;\n try {\n parsed = new URL(url, document.baseURI);\n } catch {\n return null;\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null;\n if (parsed.origin !== document.location?.origin) return null;\n const path = location ? location.normalize(parsed.pathname) : parsed.pathname;\n return (path.startsWith('/') ? path : '/' + path) + parsed.search + parsed.hash;\n}\n","import { DOCUMENT, Location, isPlatformBrowser } from '@angular/common';\nimport { DestroyRef, Injectable, PLATFORM_ID, inject } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { SwPush } from '@angular/service-worker';\nimport { sparkInAppUrl } from './spark-in-app-url';\n\n/**\n * Notification clicks while the application is open (`docs/seo_analyzer_PRD.md` X10, D30): a click on\n * a push whose `notification.data.url` is on this origin becomes an in-app router navigation, so the\n * page keeps its state instead of loading again.\n *\n * What it does NOT do, because Angular's service worker (ngsw) already does it: show the push, and\n * open the page when the application is closed. The server sends Angular's payload,\n * `{ notification: { title, body, tag, data: { url, notificationId, onActionClick: { default: {\n * operation, url } } } } }`, and ngsw performs `onActionClick.default` itself on the click, then\n * broadcasts the click to the open pages, which is what {@link SwPush.notificationClicks} reports here.\n *\n * ⚠️ The operation decides whether this handler matters. ngsw runs it BEFORE the broadcast:\n * `navigateLastFocusedOrOpen` makes the focused page load the URL in full, so this handler then\n * finds the router already there and does nothing; `focusLastFocusedOrOpen` focuses the open page\n * without loading it (and opens the URL when no page is open), leaving the navigation to this handler.\n *\n * Active only in a browser with the service worker enabled; started by `provideSparkServiceWorker()`.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkNotificationClicks {\n private readonly swPush = inject(SwPush);\n private readonly router = inject(Router, { optional: true });\n private readonly location = inject(Location, { optional: true });\n private readonly document = inject(DOCUMENT);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly destroyRef = inject(DestroyRef);\n\n private started = false;\n\n /** Starts listening. Idempotent; called by the app initializer `provideSparkServiceWorker()` adds. */\n start(): void {\n if (this.started) return;\n this.started = true;\n if (!isPlatformBrowser(this.platformId) || !this.swPush.isEnabled || !this.router) return;\n\n const subscription = this.swPush.notificationClicks.subscribe(({ notification }) => {\n const data = notification?.data as { url?: unknown } | undefined;\n this.navigate(typeof data?.url === 'string' ? data.url : null);\n });\n this.destroyRef.onDestroy(() => subscription.unsubscribe());\n }\n\n private navigate(url: string | null): void {\n const target = sparkInAppUrl(url, this.document, this.location);\n // Another origin is ngsw's to open (onActionClick); the router cannot reach it.\n if (!target || !this.router || this.router.url === target) return;\n // A same-origin link no route matches is the server's (a PDF download): load it as a page.\n this.router.navigateByUrl(target).catch(() => this.assign(new URL(url!, this.document.baseURI).href));\n }\n\n /** A full page load (overridden in specs: jsdom cannot navigate). */\n protected assign(href: string): void {\n this.document.defaultView?.location.assign(href);\n }\n}\n","import { DOCUMENT, Location, isPlatformBrowser } from '@angular/common';\nimport {\n ApplicationRef,\n DestroyRef,\n Injectable,\n InjectionToken,\n PLATFORM_ID,\n inject,\n signal,\n} from '@angular/core';\nimport { NavigationStart, Router } from '@angular/router';\nimport { SwUpdate, VersionReadyEvent } from '@angular/service-worker';\nimport { Subscription, filter, first } from 'rxjs';\n\n/** A service-worker version as `ngsw.json` describes it. `appData` is the config's `appData`. */\nexport interface SparkAppVersion {\n hash: string;\n appData?: Record<string, unknown>;\n}\n\n/** The two page loads the update policy performs. Replaceable for tests. */\nexport interface SparkPageLoader {\n assign(url: string): void;\n reload(): void;\n}\n\n/** How the update policy loads a page: `window.location` by default. */\nexport const SPARK_SW_PAGE_LOADER = new InjectionToken<SparkPageLoader>('SPARK_SW_PAGE_LOADER', {\n providedIn: 'root',\n factory: () => {\n const location = inject(DOCUMENT).defaultView?.location;\n return {\n assign: (url: string) => location?.assign(url),\n reload: () => location?.reload(),\n };\n },\n});\n\n/** Update-check timing. Defaults: every 6 h, and on becoming visible at most once a minute. */\nexport interface SparkServiceWorkerSchedule {\n checkIntervalMs: number;\n visibleCheckThrottleMs: number;\n}\n\nexport const SPARK_SW_SCHEDULE = new InjectionToken<SparkServiceWorkerSchedule>('SPARK_SW_SCHEDULE', {\n providedIn: 'root',\n factory: () => ({ checkIntervalMs: 6 * 60 * 60 * 1000, visibleCheckThrottleMs: 60 * 1000 }),\n});\n\n/**\n * Remembers, per tab, the version a full load was made for. If the page that load produced still\n * reports the same version as ready (another tab keeps the old version alive, or the worker did not\n * hand this client the new one), the policy does not reload again: one attempt per version.\n */\nconst RELOADED_FOR_KEY = 'spark-sw-reloaded-for';\n\n/**\n * The update policy of `provideSparkServiceWorker()` (#464, Q1b).\n *\n * - `VERSION_READY`: the **next** router navigation becomes a full page load of its target, so the\n * user lands on the page they asked for, in the new version, without losing anything on the page\n * they are leaving (the navigation was leaving it anyway).\n * - `unrecoverable`: the running version can no longer be served, so the page reloads at once.\n * - Checks: once the application is stable, every 6 h, and when the tab becomes visible again\n * (at most once a minute).\n * - **A hidden tab never reloads.** Spark has no unsaved-changes tracking to consult, so a reload\n * nobody is looking at could throw away an open edit. An `unrecoverable` in a hidden tab waits\n * until the tab is visible; a navigation in a hidden tab stays a normal in-app navigation.\n *\n * Nothing happens when the service worker is disabled (development builds, unsupported browsers)\n * or on the server.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkServiceWorkerUpdates {\n private readonly swUpdate = inject(SwUpdate);\n private readonly router = inject(Router, { optional: true });\n private readonly location = inject(Location, { optional: true });\n private readonly appRef = inject(ApplicationRef);\n private readonly document = inject(DOCUMENT);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly destroyRef = inject(DestroyRef);\n private readonly pageLoader = inject(SPARK_SW_PAGE_LOADER);\n private readonly schedule = inject(SPARK_SW_SCHEDULE);\n\n private readonly _updateReady = signal(false);\n private readonly _currentVersion = signal<SparkAppVersion | null>(null);\n private readonly _latestVersion = signal<SparkAppVersion | null>(null);\n\n /** True once a new version is downloaded and the next navigation will load it. */\n readonly updateReady = this._updateReady.asReadonly();\n /** The running version, known once an update was reported. */\n readonly currentVersion = this._currentVersion.asReadonly();\n /** The version the next navigation loads (`appData.build` is the ng-spark version). */\n readonly latestVersion = this._latestVersion.asReadonly();\n\n private started = false;\n private leaving = false;\n private reloadWhenVisible = false;\n private lastCheck = 0;\n\n /** Starts the policy. Idempotent; called by the app initializer `provideSparkServiceWorker()` adds. */\n start(): void {\n if (this.started) return;\n this.started = true;\n\n if (!isPlatformBrowser(this.platformId) || !this.swUpdate.isEnabled) return;\n\n const subscriptions = new Subscription();\n this.destroyRef.onDestroy(() => subscriptions.unsubscribe());\n\n subscriptions.add(\n this.swUpdate.versionUpdates\n .pipe(filter((event): event is VersionReadyEvent => event.type === 'VERSION_READY'))\n .subscribe((event) => this.onVersionReady(event)),\n );\n\n subscriptions.add(this.swUpdate.unrecoverable.subscribe(() => this.onUnrecoverable()));\n\n if (this.router) {\n subscriptions.add(\n this.router.events\n .pipe(filter((event): event is NavigationStart => event instanceof NavigationStart))\n .subscribe((event) => this.onNavigationStart(event)),\n );\n }\n\n // The first check waits for stability, so it never competes with the initial render.\n subscriptions.add(\n this.appRef.isStable.pipe(first((stable) => stable)).subscribe(() => {\n this.check();\n const interval = setInterval(() => this.check(), this.schedule.checkIntervalMs);\n subscriptions.add(() => clearInterval(interval));\n }),\n );\n\n const onVisibilityChange = () => {\n if (this.isHidden()) return;\n if (this.reloadWhenVisible) {\n this.reloadWhenVisible = false;\n this.reload();\n return;\n }\n if (Date.now() - this.lastCheck >= this.schedule.visibleCheckThrottleMs) this.check();\n };\n this.document.addEventListener('visibilitychange', onVisibilityChange);\n subscriptions.add(() => this.document.removeEventListener('visibilitychange', onVisibilityChange));\n }\n\n private onVersionReady(event: VersionReadyEvent): void {\n const latest = toVersion(event.latestVersion);\n this._currentVersion.set(toVersion(event.currentVersion));\n this._latestVersion.set(latest);\n\n if (this.readReloadedFor() === latest.hash) {\n // This page is the full load made for that version and it is still not the one served.\n // Reloading again would loop; the next deploy, or the next fresh tab, picks it up.\n return;\n }\n this._updateReady.set(true);\n }\n\n private onUnrecoverable(): void {\n if (this.isHidden()) {\n this.reloadWhenVisible = true;\n return;\n }\n this.reload();\n }\n\n private onNavigationStart(event: NavigationStart): void {\n if (!this._updateReady() || this.leaving || this.isHidden()) return;\n\n this.leaving = true;\n this.writeReloadedFor(this._latestVersion()?.hash);\n this.router?.currentNavigation()?.abort();\n\n if (event.navigationTrigger === 'popstate') {\n // Back/forward: the address bar already shows the target, and assign() would add a\n // duplicate history entry.\n this.pageLoader.reload();\n return;\n }\n\n // event.url is relative to the router's base; prepareExternalUrl adds the <base href>.\n const target = this.location ? this.location.prepareExternalUrl(event.url) : event.url;\n this.pageLoader.assign(target);\n }\n\n private check(): void {\n this.lastCheck = Date.now();\n this.swUpdate.checkForUpdate().catch(() => {\n // Offline, or the worker is gone. The next scheduled check tries again.\n });\n }\n\n private reload(): void {\n if (this.leaving) return;\n this.leaving = true;\n this.pageLoader.reload();\n }\n\n private isHidden(): boolean {\n return this.document.visibilityState === 'hidden';\n }\n\n private readReloadedFor(): string | null {\n try {\n return this.document.defaultView?.sessionStorage.getItem(RELOADED_FOR_KEY) ?? null;\n } catch {\n return null;\n }\n }\n\n private writeReloadedFor(hash: string | undefined): void {\n if (!hash) return;\n try {\n this.document.defaultView?.sessionStorage.setItem(RELOADED_FOR_KEY, hash);\n } catch {\n // Storage blocked: the guard is best effort; one reload per navigation is still bounded.\n }\n }\n}\n\nfunction toVersion(version: { hash: string; appData?: object }): SparkAppVersion {\n return { hash: version.hash, appData: version.appData as Record<string, unknown> | undefined };\n}\n","import { EnvironmentProviders, inject, isDevMode, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';\nimport { SwRegistrationOptions, provideServiceWorker } from '@angular/service-worker';\n\nimport { SparkNotificationClicks } from './spark-notification-clicks';\nimport { SparkServiceWorkerUpdates } from './spark-service-worker-updates';\n\nexport interface SparkServiceWorkerOptions {\n /** Whether the worker registers. Default: `!isDevMode()`, so only production builds register it. */\n enabled?: boolean;\n /** The worker script, relative to the base href. Default: `ngsw-worker.js`. */\n script?: string;\n /** When the worker registers. Default: `registerWhenStable:30000`. */\n registrationStrategy?: SwRegistrationOptions['registrationStrategy'];\n}\n\n/**\n * Registers Angular's service worker with Spark's update policy (#464, Q1b); see\n * {@link SparkServiceWorkerUpdates} for what happens when a new version is deployed, and\n * {@link SparkNotificationClicks} for a push notification clicked while the application is open.\n *\n * The application also needs an `ngsw-config.json` with Spark's navigation exclusions and\n * `serviceWorker` in its production build configuration; see `docs/guide-pwa.md`.\n */\nexport function provideSparkServiceWorker(options: SparkServiceWorkerOptions = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n provideServiceWorker(options.script ?? 'ngsw-worker.js', {\n enabled: options.enabled ?? !isDevMode(),\n registrationStrategy: options.registrationStrategy ?? 'registerWhenStable:30000',\n }),\n provideAppInitializer(() => {\n inject(SparkServiceWorkerUpdates).start();\n inject(SparkNotificationClicks).start();\n }),\n ]);\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { ChangeDetectionStrategy, Component,
|
|
2
|
+
import { ChangeDetectionStrategy, Component, computed, inject, input, output, signal, viewChild } from "@angular/core";
|
|
3
3
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|
4
4
|
import * as i1 from "@angular/common";
|
|
5
5
|
import { CommonModule, NgComponentOutlet, NgTemplateOutlet } from "@angular/common";
|
|
@@ -10,18 +10,15 @@ import { BsAlertComponent } from "@mintplayer/ng-bootstrap/alert";
|
|
|
10
10
|
import { BsGridColumnDirective, BsGridComponent, BsGridRowDirective } from "@mintplayer/ng-bootstrap/grid";
|
|
11
11
|
import { BsPriorityNavComponent, BsPriorityNavItemDirective } from "@mintplayer/ng-bootstrap/priority-nav";
|
|
12
12
|
import { BsSpinnerComponent } from "@mintplayer/ng-bootstrap/spinner";
|
|
13
|
-
import { SparkLanguageService, SparkService
|
|
13
|
+
import { SparkLanguageService, SparkService } from "@mintplayer/ng-spark/services";
|
|
14
14
|
import { ResolveTranslationPipe, TranslateKeyPipe } from "@mintplayer/ng-spark/pipes";
|
|
15
15
|
import { SparkIconComponent } from "@mintplayer/ng-spark/icon";
|
|
16
16
|
import { SparkQueryGridComponent, SparkSearchBoxComponent, sparkActionClass } from "@mintplayer/ng-spark/grid";
|
|
17
|
-
import { isDateDataType, parseWireDate } from "@mintplayer/ng-spark/models";
|
|
18
17
|
import { SPARK_QUERY_LIST_ACTIONS, orderSparkExtensions, parseSparkDeletedParam } from "@mintplayer/ng-spark/panels";
|
|
19
18
|
var SparkQueryListComponent = class SparkQueryListComponent {
|
|
20
19
|
route = inject(ActivatedRoute);
|
|
21
20
|
router = inject(Router);
|
|
22
21
|
sparkService = inject(SparkService);
|
|
23
|
-
streamingService = inject(SparkStreamingService);
|
|
24
|
-
destroyRef = inject(DestroyRef);
|
|
25
22
|
lang = inject(SparkLanguageService);
|
|
26
23
|
extraActionsTemplate = input(null, ...ngDevMode ? [{ debugName: "extraActionsTemplate" }] : /* istanbul ignore next */ []);
|
|
27
24
|
showCustomActions = input(true, ...ngDevMode ? [{ debugName: "showCustomActions" }] : /* istanbul ignore next */ []);
|
|
@@ -42,6 +39,7 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
42
39
|
resultCount = computed(() => this.grid()?.resultCount() ?? null, ...ngDevMode ? [{ debugName: "resultCount" }] : /* istanbul ignore next */ []);
|
|
43
40
|
isVirtualScrolling = computed(() => this.grid()?.isVirtualScrolling() ?? false, ...ngDevMode ? [{ debugName: "isVirtualScrolling" }] : /* istanbul ignore next */ []);
|
|
44
41
|
gridError = computed(() => this.grid()?.errorMessage() ?? null, ...ngDevMode ? [{ debugName: "gridError" }] : /* istanbul ignore next */ []);
|
|
42
|
+
isLive = computed(() => this.grid()?.isLive() ?? false, ...ngDevMode ? [{ debugName: "isLive" }] : /* istanbul ignore next */ []);
|
|
45
43
|
permissions = computed(() => this.grid()?.permissions() ?? null, ...ngDevMode ? [{ debugName: "permissions" }] : /* istanbul ignore next */ []);
|
|
46
44
|
listActions = orderSparkExtensions(inject(SPARK_QUERY_LIST_ACTIONS, { optional: true }), (a) => a.priority ?? 60);
|
|
47
45
|
deletedMode = signal("exclude", ...ngDevMode ? [{ debugName: "deletedMode" }] : /* istanbul ignore next */ []);
|
|
@@ -98,12 +96,6 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
98
96
|
isActionEnabled(action) {
|
|
99
97
|
return this.grid()?.isActionEnabled(action) ?? false;
|
|
100
98
|
}
|
|
101
|
-
isStreaming = signal(false, ...ngDevMode ? [{ debugName: "isStreaming" }] : /* istanbul ignore next */ []);
|
|
102
|
-
streamingSub = null;
|
|
103
|
-
streamColumns = signal([], ...ngDevMode ? [{ debugName: "streamColumns" }] : /* istanbul ignore next */ []);
|
|
104
|
-
allItems = signal([], ...ngDevMode ? [{ debugName: "allItems" }] : /* istanbul ignore next */ []);
|
|
105
|
-
streamItems = signal([], ...ngDevMode ? [{ debugName: "streamItems" }] : /* istanbul ignore next */ []);
|
|
106
|
-
gridData = computed(() => this.query()?.isStreamingQuery ? this.streamItems() : null, ...ngDevMode ? [{ debugName: "gridData" }] : /* istanbul ignore next */ []);
|
|
107
99
|
constructor() {
|
|
108
100
|
this.route.paramMap.pipe(takeUntilDestroyed()).subscribe((params) => {
|
|
109
101
|
this.onParamsChange(params).catch((e) => this.reportLoadFailure(e));
|
|
@@ -112,18 +104,6 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
112
104
|
this.deletedMode.set(parseSparkDeletedParam(query.get("deleted")) ?? "exclude");
|
|
113
105
|
this.applyUrlScope(query);
|
|
114
106
|
});
|
|
115
|
-
this.destroyRef.onDestroy(() => this.disconnectStreaming());
|
|
116
|
-
effect(() => {
|
|
117
|
-
const q = this.query();
|
|
118
|
-
if (q?.isStreamingQuery) this.connectStreaming(q.id);
|
|
119
|
-
else this.disconnectStreaming();
|
|
120
|
-
});
|
|
121
|
-
effect(() => {
|
|
122
|
-
this.searchTerm();
|
|
123
|
-
this.grid()?.settings();
|
|
124
|
-
this.allItems();
|
|
125
|
-
if (this.isStreaming()) this.applyFilter();
|
|
126
|
-
});
|
|
127
107
|
}
|
|
128
108
|
urlParentId = signal("", ...ngDevMode ? [{ debugName: "urlParentId" }] : /* istanbul ignore next */ []);
|
|
129
109
|
urlParentType = signal("", ...ngDevMode ? [{ debugName: "urlParentType" }] : /* istanbul ignore next */ []);
|
|
@@ -179,10 +159,6 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
179
159
|
async onParamsChange(params) {
|
|
180
160
|
this.errorMessage.set(null);
|
|
181
161
|
this.queryId.set(null);
|
|
182
|
-
this.allItems.set([]);
|
|
183
|
-
this.streamItems.set([]);
|
|
184
|
-
this.streamColumns.set([]);
|
|
185
|
-
this.disconnectStreaming();
|
|
186
162
|
const queryId = params.get("queryId");
|
|
187
163
|
const typeParam = params.get("type");
|
|
188
164
|
if (queryId) {
|
|
@@ -206,7 +182,7 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
206
182
|
else this.reportLoadFailure({ status: 404 });
|
|
207
183
|
}
|
|
208
184
|
reportLoadFailure(err) {
|
|
209
|
-
this.errorMessage.set(err?.status === 404 ? this.lang.t("spark.query.unavailable"
|
|
185
|
+
this.errorMessage.set(err?.status === 404 ? this.lang.t("spark.query.unavailable", "This list is not available.") : err?.error?.error || err?.message || "An unexpected error occurred");
|
|
210
186
|
}
|
|
211
187
|
async onCustomAction(action) {
|
|
212
188
|
await this.grid()?.onCustomAction(action);
|
|
@@ -223,70 +199,9 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
223
199
|
clearSearch() {
|
|
224
200
|
this.searchTerm.set("");
|
|
225
201
|
}
|
|
226
|
-
connectStreaming(queryId) {
|
|
227
|
-
if (this.streamingSub) return;
|
|
228
|
-
this.isStreaming.set(true);
|
|
229
|
-
this.streamingSub = this.streamingService.connectToStreamingQuery(queryId).subscribe({
|
|
230
|
-
next: (message) => this.handleStreamingMessage(message),
|
|
231
|
-
error: (err) => {
|
|
232
|
-
this.errorMessage.set(err?.message || "Streaming connection failed");
|
|
233
|
-
this.isStreaming.set(false);
|
|
234
|
-
},
|
|
235
|
-
complete: () => this.isStreaming.set(false)
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
disconnectStreaming() {
|
|
239
|
-
if (this.streamingSub) {
|
|
240
|
-
this.streamingSub.unsubscribe();
|
|
241
|
-
this.streamingSub = null;
|
|
242
|
-
}
|
|
243
|
-
this.isStreaming.set(false);
|
|
244
|
-
}
|
|
245
|
-
handleStreamingMessage(message) {
|
|
246
|
-
switch (message.type) {
|
|
247
|
-
case "snapshot":
|
|
248
|
-
this.errorMessage.set(null);
|
|
249
|
-
this.streamColumns.set(message.columns);
|
|
250
|
-
this.allItems.set(message.data);
|
|
251
|
-
break;
|
|
252
|
-
case "patch":
|
|
253
|
-
if (message.updated.length > 0) this.allItems.update((items) => items.map((item) => {
|
|
254
|
-
const patch = message.updated.find((u) => u.id === item.id);
|
|
255
|
-
if (!patch) return item;
|
|
256
|
-
return {
|
|
257
|
-
...item,
|
|
258
|
-
values: item.values.map((v) => v.key in patch.values ? {
|
|
259
|
-
...v,
|
|
260
|
-
value: patch.values[v.key]
|
|
261
|
-
} : v)
|
|
262
|
-
};
|
|
263
|
-
}));
|
|
264
|
-
break;
|
|
265
|
-
case "error": this.errorMessage.set(message.message);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
applyFilter() {
|
|
269
|
-
let items = this.allItems();
|
|
270
|
-
const term = this.searchTerm().toLowerCase();
|
|
271
|
-
if (term) items = items.filter((item) => item.values.some((v) => String(v.value ?? "").toLowerCase().includes(term)));
|
|
272
|
-
const sortCols = this.grid()?.settings().sortColumns ?? [];
|
|
273
|
-
if (sortCols.length > 0) {
|
|
274
|
-
const dataTypes = new Map(this.streamColumns().map((c) => [c.name, c.dataType]));
|
|
275
|
-
items = [...items].sort((a, b) => {
|
|
276
|
-
for (const col of sortCols) {
|
|
277
|
-
const aVal = a.values.find((v) => v.key === col.property)?.value;
|
|
278
|
-
const bVal = b.values.find((v) => v.key === col.property)?.value;
|
|
279
|
-
const cmp = compareStreamValues(aVal, bVal, dataTypes.get(col.property));
|
|
280
|
-
if (cmp !== 0) return col.direction === "descending" ? -cmp : cmp;
|
|
281
|
-
}
|
|
282
|
-
return 0;
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
this.streamItems.set(items);
|
|
286
|
-
}
|
|
287
202
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
288
203
|
minVersion: "12.0.0",
|
|
289
|
-
version: "22.2.
|
|
204
|
+
version: "22.2.2",
|
|
290
205
|
ngImport: i0,
|
|
291
206
|
type: SparkQueryListComponent,
|
|
292
207
|
deps: [],
|
|
@@ -294,7 +209,7 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
294
209
|
});
|
|
295
210
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
296
211
|
minVersion: "17.0.0",
|
|
297
|
-
version: "22.2.
|
|
212
|
+
version: "22.2.2",
|
|
298
213
|
type: SparkQueryListComponent,
|
|
299
214
|
isStandalone: true,
|
|
300
215
|
selector: "spark-query-list",
|
|
@@ -342,7 +257,7 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
342
257
|
isSignal: true
|
|
343
258
|
}],
|
|
344
259
|
ngImport: i0,
|
|
345
|
-
template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rules. -->\n @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" [attr.title]=\"toolbarActionUnavailableReason(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (
|
|
260
|
+
template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rules. -->\n @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" [attr.title]=\"toolbarActionUnavailableReason(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isLive()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid. It fetches and pages server-side, over HTTP or, for a live query, over /spark/live\n (the grid owns that, so embedded grids follow live queries too). This used to be two\n near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n",
|
|
346
261
|
styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--spark-shell-main-bg, var(--bs-body-bg));border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}:host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 calc(-1 * var(--spark-main-padding, 1.5rem)) calc(-1 * var(--spark-main-padding, 1.5rem));--spark-content-inset-inline: 0px}:host.virtual-scrolling spark-query-grid{display:flex;flex-direction:column;min-height:0}:host.virtual-scrolling ::ng-deep bs-datatable{display:flex;flex-direction:column;min-height:0;--mp-datatable-virtual-max-height: 100%}:host.virtual-scrolling ::ng-deep bs-datatable mp-datatable{flex:1 1 auto;min-height:0}tr:hover{background-color:rgba(var(--bs-emphasis-color-rgb),.05)}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}.spark-live-badge{font-size:.6em}.spark-url-filter-clear{background:none;border:0;padding:0 0 0 .25rem;color:inherit;line-height:1}\n"],
|
|
347
262
|
dependencies: [
|
|
348
263
|
{
|
|
@@ -461,6 +376,7 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
461
376
|
"parentType",
|
|
462
377
|
"presetFilters",
|
|
463
378
|
"data",
|
|
379
|
+
"live",
|
|
464
380
|
"search",
|
|
465
381
|
"columns",
|
|
466
382
|
"reloadToken",
|
|
@@ -507,7 +423,7 @@ var SparkQueryListComponent = class SparkQueryListComponent {
|
|
|
507
423
|
};
|
|
508
424
|
i0.ɵɵngDeclareClassMetadata({
|
|
509
425
|
minVersion: "12.0.0",
|
|
510
|
-
version: "22.2.
|
|
426
|
+
version: "22.2.2",
|
|
511
427
|
ngImport: i0,
|
|
512
428
|
type: SparkQueryListComponent,
|
|
513
429
|
decorators: [{
|
|
@@ -534,7 +450,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
534
450
|
],
|
|
535
451
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
536
452
|
host: { "[class.virtual-scrolling]": "isVirtualScrolling()" },
|
|
537
|
-
template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rules. -->\n @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" [attr.title]=\"toolbarActionUnavailableReason(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (
|
|
453
|
+
template: "<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rules. -->\n @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" [attr.title]=\"toolbarActionUnavailableReason(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isLive()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid. It fetches and pages server-side, over HTTP or, for a live query, over /spark/live\n (the grid owns that, so embedded grids follow live queries too). This used to be two\n near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n",
|
|
538
454
|
styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--spark-shell-main-bg, var(--bs-body-bg));border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}:host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 calc(-1 * var(--spark-main-padding, 1.5rem)) calc(-1 * var(--spark-main-padding, 1.5rem));--spark-content-inset-inline: 0px}:host.virtual-scrolling spark-query-grid{display:flex;flex-direction:column;min-height:0}:host.virtual-scrolling ::ng-deep bs-datatable{display:flex;flex-direction:column;min-height:0;--mp-datatable-virtual-max-height: 100%}:host.virtual-scrolling ::ng-deep bs-datatable mp-datatable{flex:1 1 auto;min-height:0}tr:hover{background-color:rgba(var(--bs-emphasis-color-rgb),.05)}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}.spark-live-badge{font-size:.6em}.spark-url-filter-clear{background:none;border:0;padding:0 0 0 .25rem;color:inherit;line-height:1}\n"]
|
|
539
455
|
}]
|
|
540
456
|
}],
|
|
@@ -590,21 +506,6 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
590
506
|
}]
|
|
591
507
|
}
|
|
592
508
|
});
|
|
593
|
-
function compareStreamValues(a, b, dataType) {
|
|
594
|
-
const aMissing = a === null || a === void 0 || a === "";
|
|
595
|
-
const bMissing = b === null || b === void 0 || b === "";
|
|
596
|
-
if (aMissing || bMissing) return aMissing === bMissing ? 0 : aMissing ? -1 : 1;
|
|
597
|
-
if (isDateDataType(dataType)) {
|
|
598
|
-
const aDate = parseWireDate(a);
|
|
599
|
-
const bDate = parseWireDate(b);
|
|
600
|
-
if (aDate && bDate) return aDate.getTime() - bDate.getTime();
|
|
601
|
-
} else if (dataType === "number" || dataType === "decimal") {
|
|
602
|
-
const aNumber = Number(a);
|
|
603
|
-
const bNumber = Number(b);
|
|
604
|
-
if (!isNaN(aNumber) && !isNaN(bNumber)) return aNumber - bNumber;
|
|
605
|
-
}
|
|
606
|
-
return String(a).localeCompare(String(b));
|
|
607
|
-
}
|
|
608
509
|
export { SparkQueryListComponent };
|
|
609
510
|
|
|
610
511
|
//# sourceMappingURL=mintplayer-ng-spark-query-list.mjs.map
|