Line data Source code
1 : import { AppFlavor, DASHQL_FLAVOR, FLAVORS } from './flavors.js';
2 :
3 : /// The query parameter selecting a flavor, e.g. `?flavor=sidb`
4 1 : export const FLAVOR_QUERY_PARAMETER = 'flavor';
5 :
6 : /// The result of resolving the flavor
7 : export interface FlavorResolution {
8 : /// The resolved flavor
9 : flavor: AppFlavor;
10 : /// A requested flavor id that is not known
11 : unknownId?: string;
12 : }
13 :
14 : /// Resolve the flavor from the query parameter, DashQL is the default
15 1 : export function resolveFlavor(requested: string | null): FlavorResolution {
16 1 : if (requested == null) {
17 1 : return { flavor: DASHQL_FLAVOR };
18 : }
19 1 : const flavor = FLAVORS.get(requested);
20 1 : if (flavor == null) {
21 1 : return { flavor: DASHQL_FLAVOR, unknownId: requested };
22 : }
23 1 : return { flavor };
24 1 : }
25 :
26 : /// Get the flavor requested by the query parameter of this page, null if it requests none.
27 : /// An unknown flavor requests the default one.
28 1 : export function getRequestedFlavor(): AppFlavor | null {
29 1 : const search = new URLSearchParams(globalThis.location?.search ?? '');
30 1 : const requested = search.get(FLAVOR_QUERY_PARAMETER);
31 1 : if (requested == null) {
32 1 : return null;
33 : }
34 1 : const resolution = resolveFlavor(requested);
35 1 : if (resolution.unknownId !== undefined) {
36 1 : console.warn(`unknown app flavor: ${resolution.unknownId}`);
37 : }
38 1 : return resolution.flavor;
39 1 : }
40 :
41 : /// Select the flavor of the app.
42 : /// The flavor requested by the url wins over the flavor of the app settings, DashQL is the default.
43 1 : export function selectFlavor(requested: AppFlavor | null, settingId: string | undefined): AppFlavor {
44 1 : return requested ?? (settingId == null ? null : FLAVORS.get(settingId)) ?? DASHQL_FLAVOR;
45 1 : }
46 :
47 : /// The url with the query parameter of a flavor, null if it already carries it.
48 : /// The default flavor needs no query parameter.
49 1 : export function withFlavorParameter(url: URL, flavor: AppFlavor): URL | null {
50 1 : if (flavor.id === DASHQL_FLAVOR.id || url.searchParams.get(FLAVOR_QUERY_PARAMETER) === flavor.id) {
51 1 : return null;
52 : }
53 1 : const next = new URL(url);
54 1 : next.searchParams.set(FLAVOR_QUERY_PARAMETER, flavor.id);
55 1 : return next;
56 1 : }
57 :
58 : /// The static favicons of the entry pages that a branded flavor replaced
59 1 : const STATIC_FAVICONS = new WeakMap<Document, Element[]>();
60 :
61 : /// Brand the document of an html entry page with a flavor.
62 : /// The static entry pages carry the DashQL favicons, they are only replaced for flavors with their own
63 : /// and restored when switching back to a flavor without.
64 1 : export function applyFlavorToDocument(flavor: AppFlavor, doc: Document = document) {
65 1 : doc.documentElement.dataset.flavor = flavor.id;
66 1 : doc.title = flavor.displayName;
67 1 : doc.querySelector('meta[name="apple-mobile-web-app-title"]')?.setAttribute('content', flavor.displayName);
68 1 : doc.head.querySelector('link[data-flavor-favicon]')?.remove();
69 1 : if (flavor.favicon != null) {
70 1 : const statics = [...doc.head.querySelectorAll('link[rel~="icon"], link[rel="apple-touch-icon"], link[rel="mask-icon"]')];
71 1 : if (statics.length > 0) {
72 1 : STATIC_FAVICONS.set(doc, statics);
73 : }
74 1 : for (const link of statics) {
75 1 : link.remove();
76 : }
77 1 : const icon = doc.createElement('link');
78 1 : icon.rel = 'icon';
79 1 : icon.href = flavor.favicon.href;
80 1 : icon.dataset.flavorFavicon = flavor.id;
81 1 : doc.head.appendChild(icon);
82 1 : } else {
83 1 : for (const link of STATIC_FAVICONS.get(doc) ?? []) {
84 1 : doc.head.appendChild(link);
85 : }
86 1 : STATIC_FAVICONS.delete(doc);
87 1 : }
88 1 : }
|