feat(jApp): initial implementation

* JApp is the main wrapper that will set all the scripted CSS vars for the whole application. It will also handle all the theming.
* Remove class-based approach in splashscreen and use CSS vars instead

Signed-off-by: Fernando Fernández <ferferga@hotmail.com>
This commit is contained in:
Fernando Fernández
2024-05-06 00:02:13 +02:00
parent fdfd1b2cfd
commit 646da43cb2
7 changed files with 73 additions and 57 deletions
+41 -36
View File
@@ -1,40 +1,42 @@
<template>
<Backdrop />
<VApp>
<RouterView v-slot="{ Component, route }">
<JTransition
:name="route.meta.transition?.enter ?? defaultTransition"
:mode="defaultTransitionMode"
:disabled="!mounted"
appear>
<Suspense @resolve="apploaded = true">
<div
:key="route.meta.layout"
class="uno-h-full j-transition">
<component
:is="getLayoutComponent(route.meta.layout)"
:key="route.meta.layout">
<JTransition
:name="route.meta.transition?.enter ?? defaultTransition"
:mode="defaultTransitionMode">
<Suspense suspensible>
<div
:key="route.name ?? route.path"
class="uno-h-full j-transition">
<component
:is="Component"
:key="route.name ?? route.path" />
</div>
</Suspense>
</JTransition>
</component>
</div>
<template v-if="!apploaded" #fallback>
<JSplashscreen />
</template>
</Suspense>
</JTransition>
</RouterView>
<JApp>
<RouterView v-slot="{ Component, route }">
<JTransition
:name="route.meta.transition?.enter ?? defaultTransition"
:mode="defaultTransitionMode"
:disabled="!mounted"
appear>
<Suspense @resolve="apploaded = true">
<div
:key="route.meta.layout"
class="uno-h-full j-transition">
<component
:is="getLayoutComponent(route.meta.layout)"
:key="route.meta.layout">
<JTransition
:name="route.meta.transition?.enter ?? defaultTransition"
:mode="defaultTransitionMode">
<Suspense suspensible>
<div
:key="route.name ?? route.path"
class="uno-h-full j-transition">
<component
:is="Component"
:key="route.name ?? route.path" />
</div>
</Suspense>
</JTransition>
</component>
</div>
<template v-if="!apploaded" #fallback>
<JSplashscreen />
</template>
</Suspense>
</JTransition>
</RouterView>
</JApp>
<Snackbar />
<ConfirmDialog />
</VApp>
@@ -57,11 +59,14 @@ const defaultTransition = 'slide-x-reverse';
const defaultTransitionMode = 'out-in';
/**
* When app is mounted, the classes we initialized in the pre-Vue splashscreen in body
* are now useless and can break the page if not removed
* When app is mounted, the classes and styles we initialized in the pre-Vue splashscreen in body
* are now useless and can break the page if not removed.
*
* We set all the styles and vars in JApp, so we want a body with 0 styling attributes.
*/
onMounted(() => {
document.body.removeAttribute('class');
document.body.removeAttribute('style');
mounted.value = true;
});
+1 -1
View File
@@ -1,10 +1,10 @@
* {
font-family: "InterVariable", system-ui !important;
cursor: var(--j-client-cursor);
}
html {
overscroll-behavior: none;
color-scheme: light dark;
}
html.no-forced-scrollbar {
+1 -8
View File
@@ -4,6 +4,7 @@
align-items: center;
justify-content: center;
flex-direction: column;
background-color: var(--j-background-color) !important;
}
.j-splash > img {
@@ -13,11 +14,3 @@
height: 30% !important;
object-fit: contain;
}
.j-splash.light {
background-color: #f2f2f2 !important;
}
.j-splash.dark {
background-color: #111827 !important;
}
+22
View File
@@ -0,0 +1,22 @@
<template>
<Teleport to="head">
<component is="style" data-jellyfin-css-vars>
:root {
<template v-if="isLoading">
cursor: wait;
</template>
--j-background-color: rgb(var(--v-theme-background));
}
</component>
</Teleport>
<slot />
</template>
<script setup lang="ts">
/**
* TODO: Investigate or propose an RFC to allow style tags inside SFCs
*/
import { useLoading } from '@/composables/use-loading';
const { isLoading } = useLoading();
</script>
+2 -9
View File
@@ -1,14 +1,7 @@
import { computed, ref, watch, type ComputedRef } from 'vue';
import { computed, shallowRef, type ComputedRef } from 'vue';
const requests = ref(0);
const requests = shallowRef(0);
const isLoading = computed(() => requests.value > 0);
const cssVarKey = '--j-client-cursor';
watch(isLoading, () => {
isLoading.value
? window.document.documentElement.style.setProperty(cssVarKey, 'wait')
: window.document.documentElement.style.removeProperty(cssVarKey);
});
/**
* Composable for triggering the linear progress that appears at the top of the page
+5 -3
View File
@@ -15,14 +15,16 @@ const parsedStore = destr<ClientSettingsState>(store);
const matchedDarkColorScheme = window.matchMedia(
'(prefers-color-scheme: dark)'
).matches;
let classToApply: 'light' | 'dark' = matchedDarkColorScheme ? 'dark' : 'light';
const darkColor = '#111827';
const lightColor = '#f2f2f2';
let colorToApply: typeof darkColor | typeof lightColor = matchedDarkColorScheme ? darkColor : lightColor;
if ('darkMode' in parsedStore) {
const storeDarkMode = parsedStore.darkMode;
if (isBool(storeDarkMode)) {
classToApply = parsedStore.darkMode === true ? 'dark' : 'light';
colorToApply = parsedStore.darkMode === true ? darkColor : lightColor;
}
}
document.body.classList.add(classToApply);
document.body.style.setProperty('--j-background-color', colorToApply);
+1
View File
@@ -96,6 +96,7 @@ declare module 'vue' {
ItemMenu: typeof import('./../../src/components/Item/ItemMenu.vue')['default']
ItemsCarousel: typeof import('./../../src/components/Layout/Carousel/Item/ItemsCarousel.vue')['default']
ItemsCarouselTitle: typeof import('./../../src/components/Layout/Carousel/Item/ItemsCarouselTitle.vue')['default']
JApp: typeof import('./../../src/components/lib/JApp.vue')['default']
JHover: typeof import('./../../src/components/lib/JHover.vue')['default']
JImg: typeof import('./../../src/components/lib/JImg.vue')['default']
JSafeHtml: typeof import('./../../src/components/lib/JSafeHtml.vue')['default']