@eclipse-docks/core 0.7.85 → 0.7.87
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.
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
export interface SplashLogoOptions {
|
|
3
|
+
/**
|
|
4
|
+
* URL for the splash logo (served from the app `public/` folder by default).
|
|
5
|
+
* @default '/logo-loading.svg'
|
|
6
|
+
*/
|
|
7
|
+
src?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Accessible name for the logo. When set, the image is exposed to assistive tech
|
|
10
|
+
* (and `aria-hidden` is not applied).
|
|
11
|
+
*/
|
|
12
|
+
alt?: string;
|
|
13
|
+
/**
|
|
14
|
+
* @default 192
|
|
15
|
+
*/
|
|
16
|
+
width?: number;
|
|
17
|
+
/**
|
|
18
|
+
* @default 64
|
|
19
|
+
*/
|
|
20
|
+
height?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface AppSplashPluginOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Logo dimensions and accessibility (`src` defaults to `/logo-loading.svg` when omitted).
|
|
25
|
+
*/
|
|
26
|
+
logo?: SplashLogoOptions;
|
|
27
|
+
/**
|
|
28
|
+
* Short tagline shown under the logo on the splash screen (static HTML text).
|
|
29
|
+
* The status line under the progress bar still follows `app-load-progress` events.
|
|
30
|
+
*/
|
|
31
|
+
description?: string;
|
|
32
|
+
/**
|
|
33
|
+
* When false, the plugin does not modify `index.html`.
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Injects the Docks startup splash (overlay, progress line, `app-load-progress` / `app-loaded` listeners)
|
|
40
|
+
* into `index.html` after `#app-root`, and adds the loading-bar keyframes in `<head>` when missing.
|
|
41
|
+
*
|
|
42
|
+
* Downstream apps only need a `<div id="app-root"></div>` and this plugin; branding stays in `public/logo-loading.svg` (or `logo`).
|
|
43
|
+
*/
|
|
44
|
+
export declare function appSplashPlugin(options?: AppSplashPluginOptions): Plugin;
|
|
45
|
+
//# sourceMappingURL=vite-plugin-app-splash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin-app-splash.d.ts","sourceRoot":"","sources":["../src/vite-plugin-app-splash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAE3C,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAgED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,MAAM,CAgC5E"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/vite-plugin-app-splash.ts
|
|
2
|
+
function escapeHtml(s) {
|
|
3
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4
|
+
}
|
|
5
|
+
function resolveLogo(options) {
|
|
6
|
+
const logo = options.logo ?? {};
|
|
7
|
+
const src = logo.src ?? "/logo-loading.svg";
|
|
8
|
+
const width = logo.width ?? 192;
|
|
9
|
+
const height = logo.height ?? 64;
|
|
10
|
+
return {
|
|
11
|
+
src,
|
|
12
|
+
alt: logo.alt ?? "",
|
|
13
|
+
width,
|
|
14
|
+
height
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function buildSplashFragment(logo, description) {
|
|
18
|
+
const w = logo.width;
|
|
19
|
+
const h = logo.height;
|
|
20
|
+
return `
|
|
21
|
+
<div id="app-loading-overlay" style="position:fixed;inset:0;z-index:1000;background:var(--wa-background,#1a1a1a);pointer-events:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2rem;">
|
|
22
|
+
<img ${logo.alt.length > 0 ? `src="${escapeHtml(logo.src)}" alt="${escapeHtml(logo.alt)}" width="${w}" height="${h}" decoding="async" style="display:block;width:${w}px;height:${h}px;max-width:${w}px;max-height:${h}px;min-width:${w}px;min-height:${h}px;flex-shrink:0;"` : `src="${escapeHtml(logo.src)}" alt="" width="${w}" height="${h}" decoding="async" style="display:block;width:${w}px;height:${h}px;max-width:${w}px;max-height:${h}px;min-width:${w}px;min-height:${h}px;flex-shrink:0;" aria-hidden="true"`}/>
|
|
23
|
+
${description !== void 0 && description.trim().length > 0 ? `
|
|
24
|
+
<p id="app-splash-description" style="margin:0;font-size:0.875rem;color:var(--wa-text-secondary,#888);text-align:center;max-width:min(360px,90vw);line-height:1.4;">${escapeHtml(description.trim())}</p>` : ""}
|
|
25
|
+
<div style="width:240px;height:3px;background:var(--wa-layer-2,#2a2a2a);border-radius:2px;overflow:hidden;">
|
|
26
|
+
<div style="width:40%;height:100%;background:var(--wa-color-primary,#2299dd);border-radius:2px;animation:app-loading-bar 1.5s ease-in-out infinite;" aria-hidden="true"></div>
|
|
27
|
+
</div>
|
|
28
|
+
<div id="app-load-progress" style="font-size:0.8125rem;color:var(--wa-text-secondary,#888);" aria-live="polite">Starting…</div>
|
|
29
|
+
</div>
|
|
30
|
+
<script>
|
|
31
|
+
window.addEventListener('app-load-progress', function(e) {
|
|
32
|
+
var el = document.getElementById('app-load-progress');
|
|
33
|
+
if (el && e.detail && e.detail.message) el.textContent = e.detail.message;
|
|
34
|
+
});
|
|
35
|
+
window.addEventListener('app-loaded', function() {
|
|
36
|
+
var el = document.getElementById('app-loading-overlay');
|
|
37
|
+
if (el) { el.style.opacity = '0'; el.style.transition = 'opacity 0.15s'; setTimeout(function() { el.remove(); }, 160); }
|
|
38
|
+
}, { once: true });
|
|
39
|
+
<\/script>`;
|
|
40
|
+
}
|
|
41
|
+
var APP_ROOT_RE = /<div\b[^>]*\bid=["']app-root["'][^>]*>\s*<\/div>/i;
|
|
42
|
+
var KEYFRAMES_STYLE = `<style>@keyframes app-loading-bar{0%{transform:translateX(0)}50%{transform:translateX(144px)}100%{transform:translateX(0)}}</style>`;
|
|
43
|
+
/**
|
|
44
|
+
* Injects the Docks startup splash (overlay, progress line, `app-load-progress` / `app-loaded` listeners)
|
|
45
|
+
* into `index.html` after `#app-root`, and adds the loading-bar keyframes in `<head>` when missing.
|
|
46
|
+
*
|
|
47
|
+
* Downstream apps only need a `<div id="app-root"></div>` and this plugin; branding stays in `public/logo-loading.svg` (or `logo`).
|
|
48
|
+
*/
|
|
49
|
+
function appSplashPlugin(options = {}) {
|
|
50
|
+
const enabled = options.enabled ?? true;
|
|
51
|
+
let logger;
|
|
52
|
+
return {
|
|
53
|
+
name: "eclipse-docks-app-splash",
|
|
54
|
+
configResolved(config) {
|
|
55
|
+
logger = config.logger;
|
|
56
|
+
},
|
|
57
|
+
transformIndexHtml: {
|
|
58
|
+
order: "pre",
|
|
59
|
+
handler(html) {
|
|
60
|
+
if (!enabled) return html;
|
|
61
|
+
if (/id=["']app-loading-overlay["']/.test(html)) return html;
|
|
62
|
+
if (!APP_ROOT_RE.test(html)) {
|
|
63
|
+
logger?.warn("[@eclipse-docks/app-splash] No <div id=\"app-root\"></div> found; splash not injected.");
|
|
64
|
+
return html;
|
|
65
|
+
}
|
|
66
|
+
const logo = resolveLogo(options);
|
|
67
|
+
let out = html;
|
|
68
|
+
if (!out.includes("app-loading-bar")) out = out.replace(/<\/head>/i, `${KEYFRAMES_STYLE}</head>`);
|
|
69
|
+
out = out.replace(APP_ROOT_RE, (m) => `${m}${buildSplashFragment(logo, options.description)}`);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { appSplashPlugin };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=vite-plugin-app-splash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin-app-splash.js","names":[],"sources":["../src/vite-plugin-app-splash.ts"],"sourcesContent":["import type { Logger, Plugin } from 'vite';\n\nexport interface SplashLogoOptions {\n /**\n * URL for the splash logo (served from the app `public/` folder by default).\n * @default '/logo-loading.svg'\n */\n src?: string;\n /**\n * Accessible name for the logo. When set, the image is exposed to assistive tech\n * (and `aria-hidden` is not applied).\n */\n alt?: string;\n /**\n * @default 192\n */\n width?: number;\n /**\n * @default 64\n */\n height?: number;\n}\n\nexport interface AppSplashPluginOptions {\n /**\n * Logo dimensions and accessibility (`src` defaults to `/logo-loading.svg` when omitted).\n */\n logo?: SplashLogoOptions;\n /**\n * Short tagline shown under the logo on the splash screen (static HTML text).\n * The status line under the progress bar still follows `app-load-progress` events.\n */\n description?: string;\n /**\n * When false, the plugin does not modify `index.html`.\n * @default true\n */\n enabled?: boolean;\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\nfunction resolveLogo(\n options: AppSplashPluginOptions,\n): { src: string; alt: string; width: number; height: number } {\n const logo = options.logo ?? {};\n const src = logo.src ?? '/logo-loading.svg';\n const width = logo.width ?? 192;\n const height = logo.height ?? 64;\n const alt = logo.alt ?? '';\n return { src, alt, width, height };\n}\n\nfunction buildSplashFragment(\n logo: { src: string; alt: string; width: number; height: number },\n description: string | undefined,\n): string {\n const w = logo.width;\n const h = logo.height;\n const imgAttrs =\n logo.alt.length > 0\n ? `src=\"${escapeHtml(logo.src)}\" alt=\"${escapeHtml(logo.alt)}\" width=\"${w}\" height=\"${h}\" decoding=\"async\" style=\"display:block;width:${w}px;height:${h}px;max-width:${w}px;max-height:${h}px;min-width:${w}px;min-height:${h}px;flex-shrink:0;\"`\n : `src=\"${escapeHtml(logo.src)}\" alt=\"\" width=\"${w}\" height=\"${h}\" decoding=\"async\" style=\"display:block;width:${w}px;height:${h}px;max-width:${w}px;max-height:${h}px;min-width:${w}px;min-height:${h}px;flex-shrink:0;\" aria-hidden=\"true\"`;\n\n const desc =\n description !== undefined && description.trim().length > 0\n ? `\n <p id=\"app-splash-description\" style=\"margin:0;font-size:0.875rem;color:var(--wa-text-secondary,#888);text-align:center;max-width:min(360px,90vw);line-height:1.4;\">${escapeHtml(description.trim())}</p>`\n : '';\n\n return `\n <div id=\"app-loading-overlay\" style=\"position:fixed;inset:0;z-index:1000;background:var(--wa-background,#1a1a1a);pointer-events:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2rem;\">\n <img ${imgAttrs}/>\n ${desc}\n <div style=\"width:240px;height:3px;background:var(--wa-layer-2,#2a2a2a);border-radius:2px;overflow:hidden;\">\n <div style=\"width:40%;height:100%;background:var(--wa-color-primary,#2299dd);border-radius:2px;animation:app-loading-bar 1.5s ease-in-out infinite;\" aria-hidden=\"true\"></div>\n </div>\n <div id=\"app-load-progress\" style=\"font-size:0.8125rem;color:var(--wa-text-secondary,#888);\" aria-live=\"polite\">Starting…</div>\n </div>\n <script>\n window.addEventListener('app-load-progress', function(e) {\n var el = document.getElementById('app-load-progress');\n if (el && e.detail && e.detail.message) el.textContent = e.detail.message;\n });\n window.addEventListener('app-loaded', function() {\n var el = document.getElementById('app-loading-overlay');\n if (el) { el.style.opacity = '0'; el.style.transition = 'opacity 0.15s'; setTimeout(function() { el.remove(); }, 160); }\n }, { once: true });\n </script>`;\n}\n\nconst APP_ROOT_RE =\n /<div\\b[^>]*\\bid=[\"']app-root[\"'][^>]*>\\s*<\\/div>/i;\n\nconst KEYFRAMES_STYLE = `<style>@keyframes app-loading-bar{0%{transform:translateX(0)}50%{transform:translateX(144px)}100%{transform:translateX(0)}}</style>`;\n\n/**\n * Injects the Docks startup splash (overlay, progress line, `app-load-progress` / `app-loaded` listeners)\n * into `index.html` after `#app-root`, and adds the loading-bar keyframes in `<head>` when missing.\n *\n * Downstream apps only need a `<div id=\"app-root\"></div>` and this plugin; branding stays in `public/logo-loading.svg` (or `logo`).\n */\nexport function appSplashPlugin(options: AppSplashPluginOptions = {}): Plugin {\n const enabled = options.enabled ?? true;\n let logger: Logger | undefined;\n\n return {\n name: 'eclipse-docks-app-splash',\n configResolved(config) {\n logger = config.logger;\n },\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n if (!enabled) return html;\n if (/id=[\"']app-loading-overlay[\"']/.test(html)) return html;\n\n if (!APP_ROOT_RE.test(html)) {\n logger?.warn(\n '[@eclipse-docks/app-splash] No <div id=\"app-root\"></div> found; splash not injected.',\n );\n return html;\n }\n\n const logo = resolveLogo(options);\n let out = html;\n if (!out.includes('app-loading-bar')) {\n out = out.replace(/<\\/head>/i, `${KEYFRAMES_STYLE}</head>`);\n }\n out = out.replace(APP_ROOT_RE, (m) => `${m}${buildSplashFragment(logo, options.description)}`);\n return out;\n },\n },\n };\n}\n"],"mappings":";AAwCA,SAAS,WAAW,GAAmB;AACrC,QAAO,EACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS;;AAG5B,SAAS,YACP,SAC6D;CAC7D,MAAM,OAAO,QAAQ,QAAQ,EAAE;CAC/B,MAAM,MAAM,KAAK,OAAO;CACxB,MAAM,QAAQ,KAAK,SAAS;CAC5B,MAAM,SAAS,KAAK,UAAU;AAE9B,QAAO;EAAE;EAAK,KADF,KAAK,OAAO;EACL;EAAO;EAAQ;;AAGpC,SAAS,oBACP,MACA,aACQ;CACR,MAAM,IAAI,KAAK;CACf,MAAM,IAAI,KAAK;AAYf,QAAO;;WAVL,KAAK,IAAI,SAAS,IACd,QAAQ,WAAW,KAAK,IAAI,CAAC,SAAS,WAAW,KAAK,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,gDAAgD,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,sBAC5N,QAAQ,WAAW,KAAK,IAAI,CAAC,kBAAkB,EAAE,YAAY,EAAE,gDAAgD,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,uCAUzL;MAPhB,gBAAgB,KAAA,KAAa,YAAY,MAAM,CAAC,SAAS,IACrD;0KACkK,WAAW,YAAY,MAAM,CAAC,CAAC,QACjM,GAKG;;;;;;;;;;;;;;;;;AAkBX,IAAM,cACJ;AAEF,IAAM,kBAAkB;;;;;;;AAQxB,SAAgB,gBAAgB,UAAkC,EAAE,EAAU;CAC5E,MAAM,UAAU,QAAQ,WAAW;CACnC,IAAI;AAEJ,QAAO;EACL,MAAM;EACN,eAAe,QAAQ;AACrB,YAAS,OAAO;;EAElB,oBAAoB;GAClB,OAAO;GACP,QAAQ,MAAM;AACZ,QAAI,CAAC,QAAS,QAAO;AACrB,QAAI,iCAAiC,KAAK,KAAK,CAAE,QAAO;AAExD,QAAI,CAAC,YAAY,KAAK,KAAK,EAAE;AAC3B,aAAQ,KACN,yFACD;AACD,YAAO;;IAGT,MAAM,OAAO,YAAY,QAAQ;IACjC,IAAI,MAAM;AACV,QAAI,CAAC,IAAI,SAAS,kBAAkB,CAClC,OAAM,IAAI,QAAQ,aAAa,GAAG,gBAAgB,SAAS;AAE7D,UAAM,IAAI,QAAQ,cAAc,MAAM,GAAG,IAAI,oBAAoB,MAAM,QAAQ,YAAY,GAAG;AAC9F,WAAO;;GAEV;EACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eclipse-docks/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.87",
|
|
4
4
|
"description": "Eclipse Docks platform core: registries, services, parts, widgets, and API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "EPL-2.0",
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
"./vite-plugin-local-aliases": {
|
|
56
56
|
"types": "./dist/vite-plugin-local-aliases.d.ts",
|
|
57
57
|
"import": "./dist/vite-plugin-local-aliases.js"
|
|
58
|
+
},
|
|
59
|
+
"./vite-plugin-app-splash": {
|
|
60
|
+
"types": "./dist/vite-plugin-app-splash.d.ts",
|
|
61
|
+
"import": "./dist/vite-plugin-app-splash.js"
|
|
58
62
|
}
|
|
59
63
|
},
|
|
60
64
|
"peerDependencies": {
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { Logger, Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
export interface SplashLogoOptions {
|
|
4
|
+
/**
|
|
5
|
+
* URL for the splash logo (served from the app `public/` folder by default).
|
|
6
|
+
* @default '/logo-loading.svg'
|
|
7
|
+
*/
|
|
8
|
+
src?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Accessible name for the logo. When set, the image is exposed to assistive tech
|
|
11
|
+
* (and `aria-hidden` is not applied).
|
|
12
|
+
*/
|
|
13
|
+
alt?: string;
|
|
14
|
+
/**
|
|
15
|
+
* @default 192
|
|
16
|
+
*/
|
|
17
|
+
width?: number;
|
|
18
|
+
/**
|
|
19
|
+
* @default 64
|
|
20
|
+
*/
|
|
21
|
+
height?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AppSplashPluginOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Logo dimensions and accessibility (`src` defaults to `/logo-loading.svg` when omitted).
|
|
27
|
+
*/
|
|
28
|
+
logo?: SplashLogoOptions;
|
|
29
|
+
/**
|
|
30
|
+
* Short tagline shown under the logo on the splash screen (static HTML text).
|
|
31
|
+
* The status line under the progress bar still follows `app-load-progress` events.
|
|
32
|
+
*/
|
|
33
|
+
description?: string;
|
|
34
|
+
/**
|
|
35
|
+
* When false, the plugin does not modify `index.html`.
|
|
36
|
+
* @default true
|
|
37
|
+
*/
|
|
38
|
+
enabled?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function escapeHtml(s: string): string {
|
|
42
|
+
return s
|
|
43
|
+
.replace(/&/g, '&')
|
|
44
|
+
.replace(/</g, '<')
|
|
45
|
+
.replace(/>/g, '>')
|
|
46
|
+
.replace(/"/g, '"');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveLogo(
|
|
50
|
+
options: AppSplashPluginOptions,
|
|
51
|
+
): { src: string; alt: string; width: number; height: number } {
|
|
52
|
+
const logo = options.logo ?? {};
|
|
53
|
+
const src = logo.src ?? '/logo-loading.svg';
|
|
54
|
+
const width = logo.width ?? 192;
|
|
55
|
+
const height = logo.height ?? 64;
|
|
56
|
+
const alt = logo.alt ?? '';
|
|
57
|
+
return { src, alt, width, height };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildSplashFragment(
|
|
61
|
+
logo: { src: string; alt: string; width: number; height: number },
|
|
62
|
+
description: string | undefined,
|
|
63
|
+
): string {
|
|
64
|
+
const w = logo.width;
|
|
65
|
+
const h = logo.height;
|
|
66
|
+
const imgAttrs =
|
|
67
|
+
logo.alt.length > 0
|
|
68
|
+
? `src="${escapeHtml(logo.src)}" alt="${escapeHtml(logo.alt)}" width="${w}" height="${h}" decoding="async" style="display:block;width:${w}px;height:${h}px;max-width:${w}px;max-height:${h}px;min-width:${w}px;min-height:${h}px;flex-shrink:0;"`
|
|
69
|
+
: `src="${escapeHtml(logo.src)}" alt="" width="${w}" height="${h}" decoding="async" style="display:block;width:${w}px;height:${h}px;max-width:${w}px;max-height:${h}px;min-width:${w}px;min-height:${h}px;flex-shrink:0;" aria-hidden="true"`;
|
|
70
|
+
|
|
71
|
+
const desc =
|
|
72
|
+
description !== undefined && description.trim().length > 0
|
|
73
|
+
? `
|
|
74
|
+
<p id="app-splash-description" style="margin:0;font-size:0.875rem;color:var(--wa-text-secondary,#888);text-align:center;max-width:min(360px,90vw);line-height:1.4;">${escapeHtml(description.trim())}</p>`
|
|
75
|
+
: '';
|
|
76
|
+
|
|
77
|
+
return `
|
|
78
|
+
<div id="app-loading-overlay" style="position:fixed;inset:0;z-index:1000;background:var(--wa-background,#1a1a1a);pointer-events:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2rem;">
|
|
79
|
+
<img ${imgAttrs}/>
|
|
80
|
+
${desc}
|
|
81
|
+
<div style="width:240px;height:3px;background:var(--wa-layer-2,#2a2a2a);border-radius:2px;overflow:hidden;">
|
|
82
|
+
<div style="width:40%;height:100%;background:var(--wa-color-primary,#2299dd);border-radius:2px;animation:app-loading-bar 1.5s ease-in-out infinite;" aria-hidden="true"></div>
|
|
83
|
+
</div>
|
|
84
|
+
<div id="app-load-progress" style="font-size:0.8125rem;color:var(--wa-text-secondary,#888);" aria-live="polite">Starting…</div>
|
|
85
|
+
</div>
|
|
86
|
+
<script>
|
|
87
|
+
window.addEventListener('app-load-progress', function(e) {
|
|
88
|
+
var el = document.getElementById('app-load-progress');
|
|
89
|
+
if (el && e.detail && e.detail.message) el.textContent = e.detail.message;
|
|
90
|
+
});
|
|
91
|
+
window.addEventListener('app-loaded', function() {
|
|
92
|
+
var el = document.getElementById('app-loading-overlay');
|
|
93
|
+
if (el) { el.style.opacity = '0'; el.style.transition = 'opacity 0.15s'; setTimeout(function() { el.remove(); }, 160); }
|
|
94
|
+
}, { once: true });
|
|
95
|
+
</script>`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const APP_ROOT_RE =
|
|
99
|
+
/<div\b[^>]*\bid=["']app-root["'][^>]*>\s*<\/div>/i;
|
|
100
|
+
|
|
101
|
+
const KEYFRAMES_STYLE = `<style>@keyframes app-loading-bar{0%{transform:translateX(0)}50%{transform:translateX(144px)}100%{transform:translateX(0)}}</style>`;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Injects the Docks startup splash (overlay, progress line, `app-load-progress` / `app-loaded` listeners)
|
|
105
|
+
* into `index.html` after `#app-root`, and adds the loading-bar keyframes in `<head>` when missing.
|
|
106
|
+
*
|
|
107
|
+
* Downstream apps only need a `<div id="app-root"></div>` and this plugin; branding stays in `public/logo-loading.svg` (or `logo`).
|
|
108
|
+
*/
|
|
109
|
+
export function appSplashPlugin(options: AppSplashPluginOptions = {}): Plugin {
|
|
110
|
+
const enabled = options.enabled ?? true;
|
|
111
|
+
let logger: Logger | undefined;
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
name: 'eclipse-docks-app-splash',
|
|
115
|
+
configResolved(config) {
|
|
116
|
+
logger = config.logger;
|
|
117
|
+
},
|
|
118
|
+
transformIndexHtml: {
|
|
119
|
+
order: 'pre',
|
|
120
|
+
handler(html) {
|
|
121
|
+
if (!enabled) return html;
|
|
122
|
+
if (/id=["']app-loading-overlay["']/.test(html)) return html;
|
|
123
|
+
|
|
124
|
+
if (!APP_ROOT_RE.test(html)) {
|
|
125
|
+
logger?.warn(
|
|
126
|
+
'[@eclipse-docks/app-splash] No <div id="app-root"></div> found; splash not injected.',
|
|
127
|
+
);
|
|
128
|
+
return html;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const logo = resolveLogo(options);
|
|
132
|
+
let out = html;
|
|
133
|
+
if (!out.includes('app-loading-bar')) {
|
|
134
|
+
out = out.replace(/<\/head>/i, `${KEYFRAMES_STYLE}</head>`);
|
|
135
|
+
}
|
|
136
|
+
out = out.replace(APP_ROOT_RE, (m) => `${m}${buildSplashFragment(logo, options.description)}`);
|
|
137
|
+
return out;
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|