Repository navigation
Expand file tree
/
Copy pathapp.vue
More file actions
120 lines (109 loc) · 4.33 KB
/
Copy pathapp.vue
File metadata and controls
120 lines (109 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
<script setup lang="ts">
import { computed, defineAsyncComponent } from "vue";
import { useI18n } from "vue-i18n";
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
import { useBranding } from "~/composables/useBranding";
import { useApplicationSettingsStore } from "~/stores/ApplicationSettings";
import { useAuthStore } from "~/stores/AuthStore";
import { usePluginsStore } from "~/stores/Plugins";
import { pageKeyWithoutTabQuery } from "~/utilities/pageKey";
import { afterReveal } from "~/utilities/afterReveal";
const MatchmakingConfirm = defineAsyncComponent(
() => import("~/components/matchmaking/MatchmakingConfirm.vue"),
);
const PlayWherePrompt = defineAsyncComponent(
() => import("~/components/matchmaking/PlayWherePrompt.vue"),
);
const MatchActiveAlert = defineAsyncComponent(
() => import("~/components/match/MatchActiveAlert.vue"),
);
const DraftActiveAlert = defineAsyncComponent(
() => import("~/components/draft-games/DraftActiveAlert.vue"),
);
const PlayerNameRegistration = defineAsyncComponent(
() => import("~/components/PlayerNameRegistration.vue"),
);
const StreamGlobal = defineAsyncComponent(
() => import("~/components/StreamGlobal.vue"),
);
// Its support probe paints emoji to a canvas and reads the pixels back -- ~76ms
// of main thread at 4x CPU. Where flags do need polyfilling the webfont loads
// asynchronously anyway, so nothing on screen waits for it.
afterReveal(() => polyfillCountryFlagEmojis());
const { brandName } = useBranding();
const { t } = useI18n();
// Single, stable manifest link. 5stack.gg keeps the static build manifest; every
// other (white-label) host points at the host-aware Nitro route. Setting it once
// here — instead of swapping a NuxtPwaManifest-injected link at runtime — avoids
// Chrome seeing the static "5stack" manifest first and prompting a name "update".
const manifestHref =
typeof window !== "undefined" && window.location.hostname === "5stack.gg"
? "/manifest.webmanifest"
: "/branding/manifest.webmanifest";
// Every avatar, banner and clip thumbnail is served from the API origin, which
// is a different host to the panel. The LCP element on /watch is one of those
// banners, and its request cannot even be issued until the page's GraphQL has
// resolved -- so without this the DNS + TCP + TLS handshake to that origin is
// paid at ~4.1s, right on the critical path. Warming it during boot moves that
// cost off the LCP chain entirely.
//
// Deliberately without `crossorigin`: the browser pools connections by
// credentials mode, and NuxtImg renders a bare <img src> with no crossorigin
// attribute. An anonymous-CORS socket is one those images cannot reuse, so the
// handshake this exists to remove would still be paid -- on a second
// connection, with the warmed one left idle.
const apiOrigin = (() => {
const domain = useRuntimeConfig().public.apiDomain;
if (!domain) {
return undefined;
}
return domain.startsWith("http") ? domain : `https://${domain}`;
})();
useHead({
title: () => brandName.value || "5Stack",
titleTemplate: (pageTitle?: string) => {
const base = brandName.value || "5Stack";
if (pageTitle && pageTitle !== base) {
return `${pageTitle} | ${base}`;
}
return `${base} | ${t("branding.site_title_suffix")}`;
},
link: [
{ rel: "manifest", href: manifestHref },
...(apiOrigin
? [
{ rel: "preconnect", href: apiOrigin },
{ rel: "dns-prefetch", href: apiOrigin },
]
: []),
],
// iOS home-screen label — plain brand name, no " | …" suffix.
meta: [
{
name: "apple-mobile-web-app-title",
content: () => brandName.value || "5Stack",
},
],
});
const authStore = useAuthStore();
const applicationSettingsStore = useApplicationSettingsStore();
// Started with the app, not when the nav mounts, so plugin links are known by
// the time the nav first paints.
usePluginsStore();
const me = computed(() => authStore.me);
const hasGlobalStream = computed(() => !!applicationSettingsStore.globalStream);
</script>
<template>
<StreamGlobal v-if="hasGlobalStream" />
<div v-if="me" style="display: contents">
<PlayerNameRegistration />
<MatchmakingConfirm />
<PlayWherePrompt />
<MatchActiveAlert />
<DraftActiveAlert />
</div>
<NuxtLayout>
<NuxtPage :page-key="pageKeyWithoutTabQuery" />
</NuxtLayout>
<Toaster />
</template>