feat(ui): switch to Radix and Inter, cleanups (#2250)

* Inter is going to be one of the brand Jellyfin fonts, as discussed in Matrix's UI/UX channels. Check this: https://matrix.to/#/!xrSDQsdjElWFYUAMoG:matrix.org/$_ZCxjEgHmaYdFo6aiCfqdXSnhEvg8UAksk0NG5PUhZg?via=bonifacelabs.ca&via=t2bot.io&via=matrix.org (Previous messages are also relevant)

* Add radix-vue to use their components as base. There is only one modification I would like to have to their components (the ability to pass arbitrary props to Primitive) but it's something that's likely to be accepted upstream

* Minor cleanup in Carousel styles

* Use the client font as Jassub's font

* Install UnoCSS and it's resets (not enabled yet due to Vuetify inconsistencies)

Signed-off-by: Fernando Fernández <ferferga@hotmail.com>
This commit is contained in:
Fernando Fernández 2024-03-07 10:12:44 +01:00 committed by GitHub
parent 75bca13018
commit a379e25b01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1200 additions and 110 deletions

View File

@ -18,8 +18,8 @@
"typecheck": "vue-tsc --noEmit --skipLibCheck" "typecheck": "vue-tsc --noEmit --skipLibCheck"
}, },
"dependencies": { "dependencies": {
"@fontsource/roboto": "5.0.8",
"@jellyfin/sdk": "0.8.2", "@jellyfin/sdk": "0.8.2",
"@unocss/reset": "0.58.5",
"@vueuse/components": "10.9.0", "@vueuse/components": "10.9.0",
"@vueuse/core": "10.9.0", "@vueuse/core": "10.9.0",
"audiomotion-analyzer": "4.4.0", "audiomotion-analyzer": "4.4.0",
@ -31,9 +31,11 @@
"dompurify": "3.0.9", "dompurify": "3.0.9",
"fast-equals": "5.0.1", "fast-equals": "5.0.1",
"hls.js": "1.5.7", "hls.js": "1.5.7",
"inter-ui": "4.0.2",
"jassub": "1.7.15", "jassub": "1.7.15",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"marked": "12.0.0", "marked": "12.0.0",
"radix-vue": "1.4.9",
"sortablejs": "1.15.2", "sortablejs": "1.15.2",
"swiper": "11.0.7", "swiper": "11.0.7",
"uuid": "9.0.1", "uuid": "9.0.1",
@ -80,6 +82,7 @@
"sass": "1.71.1", "sass": "1.71.1",
"typescript": "5.3.3", "typescript": "5.3.3",
"typescript-eslint-parser-for-extra-files": "0.6.0", "typescript-eslint-parser-for-extra-files": "0.6.0",
"unocss": "0.58.5",
"unplugin-icons": "0.18.5", "unplugin-icons": "0.18.5",
"unplugin-vue-components": "0.26.0", "unplugin-vue-components": "0.26.0",
"unplugin-vue-router": "0.8.4", "unplugin-vue-router": "0.8.4",

View File

@ -1,16 +1,3 @@
.default-icon {
display: flex;
align-content: center;
justify-content: center;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.swiperContainer { .swiperContainer {
min-width: 100%; min-width: 100%;
min-height: 100%; min-height: 100%;

View File

@ -1,6 +1,5 @@
* { * {
-webkit-font-smoothing: antialiased; font-family: "InterVariable", system-ui !important;
-moz-osx-font-smoothing: grayscale;
cursor: var(--j-client-cursor); cursor: var(--j-client-cursor);
} }

View File

@ -10,10 +10,10 @@
class="elevation-2"> class="elevation-2">
<div <div
class="absolute-cover card-content d-flex justify-center align-center"> class="absolute-cover card-content d-flex justify-center align-center">
<JSlot class="card-image"> <RSlot class="card-image">
<slot <slot
name="image" /> name="image" />
</JSlot> </RSlot>
</div> </div>
<div <div
class="absolute-cover card-overlay d-flex justify-center align-center" class="absolute-cover card-overlay d-flex justify-center align-center"

View File

@ -11,7 +11,6 @@
<div <div
:class="useResponsiveClasses('slide-backdrop')" :class="useResponsiveClasses('slide-backdrop')"
data-swiper-parallax="-100"> data-swiper-parallax="-100">
<div class="default-icon" />
<BlurhashImage <BlurhashImage
:key="`${item.Id}-image`" :key="`${item.Id}-image`"
:item="getRelatedItem(item)" :item="getRelatedItem(item)"

View File

@ -1,10 +1,10 @@
<template> <template>
<JSlot <RSlot
@mouseenter="isHovering = true" @mouseenter="isHovering = true"
@mouseleave="isHovering = false"> @mouseleave="isHovering = false">
<slot <slot
:is-hovering="isHovering" /> :is-hovering="isHovering" />
</JSlot> </RSlot>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -1,70 +0,0 @@
<script lang="ts">
/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access */
import type { Arrayable } from '@vueuse/core';
import { cloneVNode, mergeProps, Fragment, type VNode } from 'vue';
/**
* Render multiple child nodes from a slot, including nested fragments.
*/
function renderSlotFragments(children?: VNode[]): VNode[] {
if (!children) {
return [];
}
return children.flatMap((child) => {
if (child.type === Fragment) {
return renderSlotFragments(child.children as VNode[]);
}
return [child];
});
}
export default {
inheritAttrs: false,
setup(_, { attrs, slots }) {
return (): Arrayable<VNode> | undefined => {
if (!slots.default) {
return;
}
const childrens = renderSlotFragments(slots.default());
const [firstChildren, ...otherChildren] = childrens;
if (Object.keys(attrs).length > 0) {
// Remove props ref from being inferred
delete firstChildren.props?.ref;
const mergedProps = mergeProps(attrs, firstChildren.props ?? {});
// Remove class to prevent duplicated
if (attrs.class && firstChildren.props?.class) {
delete firstChildren.props.class;
}
const cloned = cloneVNode(firstChildren, mergedProps);
/*
* Explicitly override props starting with `on`.
* It seems cloneVNode from Vue doesn't like overriding `onXXX` props. So
* we have to do it manually.
*/
for (const prop in mergedProps) {
if (prop.startsWith('on')) {
cloned.props ||= {};
cloned.props[prop] = mergedProps[prop];
}
}
return childrens.length === 1 ? cloned : [cloned, ...otherChildren];
}
return childrens;
};
}
};
/* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access */
</script>

View File

@ -16,8 +16,16 @@ import { vuetify } from '@/plugins/vuetify';
/** /**
* - GLOBAL STYLES - * - GLOBAL STYLES -
*/ */
import 'inter-ui/inter-variable.css';
/**
* TODO: Re-enable once Vuetify is gone
*/
/*
* import 'uno.css';
* import 'virtual:unocss-devtools';
*/
import '@unocss/reset/tailwind.css';
import '@/assets/styles/global.scss'; import '@/assets/styles/global.scss';
import '@fontsource/roboto';
/** /**
* - VUE PLUGINS, STORE AND DIRECTIVE - * - VUE PLUGINS, STORE AND DIRECTIVE -

View File

@ -5,7 +5,6 @@
* an agnostic way, regardless of where the media is being played (remotely or locally) * an agnostic way, regardless of where the media is being played (remotely or locally)
*/ */
import JASSUB from 'jassub'; import JASSUB from 'jassub';
import jassubDefaultFont from 'jassub/dist/default.woff2?url';
import jassubWorker from 'jassub/dist/jassub-worker.js?url'; import jassubWorker from 'jassub/dist/jassub-worker.js?url';
import jassubWasmUrl from 'jassub/dist/jassub-worker.wasm?url'; import jassubWasmUrl from 'jassub/dist/jassub-worker.wasm?url';
import { nextTick, reactive, watch } from 'vue'; import { nextTick, reactive, watch } from 'vue';
@ -98,10 +97,12 @@ class PlayerElementStore {
fonts: attachedFonts, fonts: attachedFonts,
workerUrl: jassubWorker, workerUrl: jassubWorker,
wasmUrl: jassubWasmUrl, wasmUrl: jassubWasmUrl,
availableFonts: { 'liberation sans': jassubDefaultFont }, fallbackFont: 'InterVariable',
// Both parameters needed for subs to work on iOS // Both parameters needed for subs to work on iOS
prescaleFactor: 0.8, prescaleFactor: 0.8,
onDemandRender: false onDemandRender: false,
// OffscreenCanvas doesn't work perfectly on Workers: https://github.com/ThaUnknown/jassub/issues/33
offscreenRender: false
}); });
} else if (jassub) { } else if (jassub) {
if (isArray(attachedFonts)) { if (isArray(attachedFonts)) {

View File

@ -98,7 +98,6 @@ declare module 'vue' {
ItemsCarouselTitle: typeof import('./../../src/components/Layout/Carousel/Item/ItemsCarouselTitle.vue')['default'] ItemsCarouselTitle: typeof import('./../../src/components/Layout/Carousel/Item/ItemsCarouselTitle.vue')['default']
JHover: typeof import('./../../src/components/lib/JHover.vue')['default'] JHover: typeof import('./../../src/components/lib/JHover.vue')['default']
JImg: typeof import('./../../src/components/lib/JImg.vue')['default'] JImg: typeof import('./../../src/components/lib/JImg.vue')['default']
JSlot: typeof import('./../../src/components/lib/JSlot.vue')['default']
LikeButton: typeof import('./../../src/components/Buttons/LikeButton.vue')['default'] LikeButton: typeof import('./../../src/components/Buttons/LikeButton.vue')['default']
LoadingIndicator: typeof import('./../../src/components/System/LoadingIndicator.vue')['default'] LoadingIndicator: typeof import('./../../src/components/System/LoadingIndicator.vue')['default']
LocaleSwitcher: typeof import('./../../src/components/System/LocaleSwitcher.vue')['default'] LocaleSwitcher: typeof import('./../../src/components/System/LocaleSwitcher.vue')['default']
@ -129,6 +128,7 @@ declare module 'vue' {
RepeatButton: typeof import('./../../src/components/Buttons/Playback/RepeatButton.vue')['default'] RepeatButton: typeof import('./../../src/components/Buttons/Playback/RepeatButton.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
RSlot: typeof import('radix-vue')['Slot']
ScrollToTopButton: typeof import('./../../src/components/Buttons/ScrollToTopButton.vue')['default'] ScrollToTopButton: typeof import('./../../src/components/Buttons/ScrollToTopButton.vue')['default']
SearchField: typeof import('./../../src/components/Layout/AppBar/SearchField.vue')['default'] SearchField: typeof import('./../../src/components/Layout/AppBar/SearchField.vue')['default']
SeasonTabs: typeof import('./../../src/components/Item/SeasonTabs.vue')['default'] SeasonTabs: typeof import('./../../src/components/Item/SeasonTabs.vue')['default']

View File

@ -12,6 +12,9 @@ import {
VueUseDirectiveResolver VueUseDirectiveResolver
} from 'unplugin-vue-components/resolvers'; } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite';
import RadixVueResolver from 'radix-vue/resolver';
import UnoCSS from 'unocss/vite';
import { presetUno } from 'unocss';
import VueRouter from 'unplugin-vue-router/vite'; import VueRouter from 'unplugin-vue-router/vite';
import { defineConfig, type UserConfig } from 'vite'; import { defineConfig, type UserConfig } from 'vite';
import { entrypoints, localeFilesFolder, srcRoot } from './scripts/paths'; import { entrypoints, localeFilesFolder, srcRoot } from './scripts/paths';
@ -41,7 +44,10 @@ export default defineConfig(({ mode }): UserConfig => {
IconsResolver(), IconsResolver(),
VueUseComponentsResolver(), VueUseComponentsResolver(),
Vuetify3Resolver(), Vuetify3Resolver(),
VueUseDirectiveResolver() VueUseDirectiveResolver(),
RadixVueResolver({
prefix: 'R'
})
] ]
}), }),
/** /**
@ -57,6 +63,11 @@ export default defineConfig(({ mode }): UserConfig => {
fullInstall: false, fullInstall: false,
forceStringify: true, forceStringify: true,
include: localeFilesFolder include: localeFilesFolder
}),
UnoCSS({
presets: [
presetUno()
]
}) })
], ],
build: { build: {

1178
package-lock.json generated

File diff suppressed because it is too large Load Diff