feat: optimize CPU and memory usage
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2346>
This commit is contained in:
parent
d30d107ef3
commit
a69aeb07e2
|
@ -58,7 +58,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/eslint-plugin-vue-i18n": "2.0.0",
|
"@intlify/eslint-plugin-vue-i18n": "2.0.0",
|
||||||
"@intlify/vite-plugin-vue-i18n": "6.0.3",
|
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
||||||
"@types/diff": "5.0.2",
|
"@types/diff": "5.0.2",
|
||||||
"@types/dompurify": "2.4.0",
|
"@types/dompurify": "2.4.0",
|
||||||
"@types/howler": "2.2.7",
|
"@types/howler": "2.2.7",
|
||||||
|
@ -73,6 +73,7 @@
|
||||||
"@vitejs/plugin-vue": "4.0.0",
|
"@vitejs/plugin-vue": "4.0.0",
|
||||||
"@vitest/coverage-c8": "0.25.8",
|
"@vitest/coverage-c8": "0.25.8",
|
||||||
"@vue/compiler-sfc": "3.2.45",
|
"@vue/compiler-sfc": "3.2.45",
|
||||||
|
"@vue/devtools": "^6.5.0",
|
||||||
"@vue/eslint-config-standard": "8.0.1",
|
"@vue/eslint-config-standard": "8.0.1",
|
||||||
"@vue/eslint-config-typescript": "11.0.2",
|
"@vue/eslint-config-typescript": "11.0.2",
|
||||||
"@vue/test-utils": "2.2.7",
|
"@vue/test-utils": "2.2.7",
|
||||||
|
|
|
@ -11,8 +11,8 @@ export interface SoundSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Sound {
|
export interface Sound {
|
||||||
preload(): void | Promise<void>
|
preload(): Promise<void>
|
||||||
dispose(): void
|
dispose(): Promise<void>
|
||||||
|
|
||||||
readonly audioNode: IAudioNode<IAudioContext>
|
readonly audioNode: IAudioNode<IAudioContext>
|
||||||
readonly isErrored: Ref<boolean>
|
readonly isErrored: Ref<boolean>
|
||||||
|
@ -23,11 +23,11 @@ export interface Sound {
|
||||||
readonly buffered: number
|
readonly buffered: number
|
||||||
looping: boolean
|
looping: boolean
|
||||||
|
|
||||||
pause(): void | Promise<void>
|
pause(): Promise<void>
|
||||||
play(): void | Promise<void>
|
play(): Promise<void>
|
||||||
|
|
||||||
seekTo(seconds: number): void | Promise<void>
|
seekTo(seconds: number): Promise<void>
|
||||||
seekBy(seconds: number): void | Promise<void>
|
seekBy(seconds: number): Promise<void>
|
||||||
|
|
||||||
onSoundLoop: EventHookOn<Sound>
|
onSoundLoop: EventHookOn<Sound>
|
||||||
onSoundEnd: EventHookOn<Sound>
|
onSoundEnd: EventHookOn<Sound>
|
||||||
|
@ -95,19 +95,22 @@ export class HTMLSound implements Sound {
|
||||||
this.isLoaded.value = this.#audio.readyState >= 2
|
this.isLoaded.value = this.#audio.readyState >= 2
|
||||||
})
|
})
|
||||||
|
|
||||||
useEventListener(this.#audio, 'error', () => {
|
useEventListener(this.#audio, 'error', (err) => {
|
||||||
|
console.error('>> AUDIO ERRORED', err, this.__track?.title)
|
||||||
this.isErrored.value = true
|
this.isErrored.value = true
|
||||||
this.isLoaded.value = true
|
this.isLoaded.value = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
preload () {
|
async preload () {
|
||||||
|
this.isErrored.value = false
|
||||||
console.log('CALLING PRELOAD ON', this.__track?.title)
|
console.log('CALLING PRELOAD ON', this.__track?.title)
|
||||||
this.#audio.load()
|
this.#audio.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose () {
|
async dispose () {
|
||||||
this.audioNode.disconnect()
|
this.audioNode.disconnect()
|
||||||
|
this.#audio.pause()
|
||||||
|
|
||||||
// Cancel any request downloading the source
|
// Cancel any request downloading the source
|
||||||
this.#audio.src = ''
|
this.#audio.src = ''
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import type { QueueItemSource } from '~/types'
|
import type { QueueItemSource } from '~/types'
|
||||||
|
|
||||||
import { whenever, watchDebounced, useCurrentElement, useScrollLock, useFullscreen, useIdle, refAutoReset, useStorage } from '@vueuse/core'
|
import { whenever, watchDebounced, useCurrentElement, useScrollLock, useFullscreen, useIdle, refAutoReset, useStorage } from '@vueuse/core'
|
||||||
import { nextTick, ref, computed, watchEffect, onMounted, defineAsyncComponent } from 'vue'
|
import { nextTick, ref, computed, watchEffect, watch, defineAsyncComponent } from 'vue'
|
||||||
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
@ -42,7 +42,7 @@ const {
|
||||||
dequeue,
|
dequeue,
|
||||||
playTrack,
|
playTrack,
|
||||||
reorder,
|
reorder,
|
||||||
endsIn: timeLeft,
|
endsIn,
|
||||||
clear
|
clear
|
||||||
} = useQueue()
|
} = useQueue()
|
||||||
|
|
||||||
|
@ -93,16 +93,6 @@ const scrollToCurrent = (behavior: ScrollBehavior = 'smooth') => {
|
||||||
|
|
||||||
watchDebounced(currentTrack, () => scrollToCurrent(), { debounce: 100 })
|
watchDebounced(currentTrack, () => scrollToCurrent(), { debounce: 100 })
|
||||||
|
|
||||||
const scrollLoop = () => {
|
|
||||||
const visible = [...(list.value?.scroller.$_views.values() ?? [])].map(item => item.nr.index)
|
|
||||||
if (!visible.includes(currentIndex.value)) {
|
|
||||||
list.value?.scrollToIndex(currentIndex.value)
|
|
||||||
requestAnimationFrame(scrollLoop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(scrollLoop)
|
|
||||||
|
|
||||||
whenever(
|
whenever(
|
||||||
() => queue.value.length === 0,
|
() => queue.value.length === 0,
|
||||||
() => store.commit('ui/queueFocused', null),
|
() => store.commit('ui/queueFocused', null),
|
||||||
|
@ -118,6 +108,13 @@ const touchProgress = (event: MouseEvent) => {
|
||||||
seekTo(time)
|
seekTo(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const animated = ref(false)
|
||||||
|
watch(currentTrack, async track => {
|
||||||
|
animated.value = false
|
||||||
|
await nextTick()
|
||||||
|
animated.value = true
|
||||||
|
})
|
||||||
|
|
||||||
const play = async (index: number) => {
|
const play = async (index: number) => {
|
||||||
isPlaying.value = true
|
isPlaying.value = true
|
||||||
return playTrack(index)
|
return playTrack(index)
|
||||||
|
@ -358,8 +355,13 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
|
||||||
:style="{ 'transform': `translateX(${bufferProgress - 100}%)` }"
|
:style="{ 'transform': `translateX(${bufferProgress - 100}%)` }"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="position bar"
|
:class="['position bar', { animated }]"
|
||||||
:style="{ 'transform': `translateX(calc(${progress}% - 100%)` }"
|
:style="{
|
||||||
|
animationDuration: duration + 's',
|
||||||
|
animationPlayState: isPlaying
|
||||||
|
? 'running'
|
||||||
|
: 'paused'
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -416,12 +418,11 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
|
||||||
<div class="sub header">
|
<div class="sub header">
|
||||||
<div>
|
<div>
|
||||||
{{ $t('components.Queue.meta.queuePosition', {index: currentIndex +1, length: queue.length}) }}
|
{{ $t('components.Queue.meta.queuePosition', {index: currentIndex +1, length: queue.length}) }}
|
||||||
<template v-if="!$store.state.radios.running">
|
<span class="middle pipe symbol" />
|
||||||
<span class="middle ellipses symbol" />
|
{{ $t('components.Queue.meta.end') }}
|
||||||
<span :title="labels.duration">
|
<span :title="labels.duration">
|
||||||
{{ timeLeft }}
|
{{ endsIn }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -434,8 +435,7 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
|
||||||
:component="QueueItem"
|
:component="QueueItem"
|
||||||
:size="50"
|
:size="50"
|
||||||
@reorder="reorderTracks"
|
@reorder="reorderTracks"
|
||||||
@visible="scrollToCurrent('auto')"
|
@visible="list.scrollToIndex(currentIndex, 'center')"
|
||||||
@hidden="scrollLoop"
|
|
||||||
>
|
>
|
||||||
<template #default="{ index, item, classlist }">
|
<template #default="{ index, item, classlist }">
|
||||||
<queue-item
|
<queue-item
|
||||||
|
|
|
@ -150,7 +150,6 @@ const hideArtist = () => {
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="position bar"
|
class="position bar"
|
||||||
:style="{ 'transform': `translateX(${progress - 100}%)` }"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="seek bar"
|
class="seek bar"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||||
|
|
||||||
import { computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import usePlayOptions from '~/composables/audio/usePlayOptions'
|
import usePlayOptions from '~/composables/audio/usePlayOptions'
|
||||||
|
|
||||||
|
@ -23,8 +23,6 @@ interface Props extends PlayOptionsProps {
|
||||||
showPosition?: boolean
|
showPosition?: boolean
|
||||||
displayActions?: boolean
|
displayActions?: boolean
|
||||||
|
|
||||||
hover: boolean
|
|
||||||
|
|
||||||
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
|
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
|
||||||
tracks: Track[]
|
tracks: Track[]
|
||||||
isPlayable?: boolean
|
isPlayable?: boolean
|
||||||
|
@ -57,12 +55,15 @@ const { isPlaying, loading } = usePlayer()
|
||||||
const { currentTrack } = useQueue()
|
const { currentTrack } = useQueue()
|
||||||
|
|
||||||
const active = computed(() => props.track.id === currentTrack.value?.id && props.track.position === currentTrack.value?.position)
|
const active = computed(() => props.track.id === currentTrack.value?.id && props.track.position === currentTrack.value?.position)
|
||||||
|
const hover = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="[{ active }, 'track-row row']"
|
:class="[{ active }, 'track-row row']"
|
||||||
@dblclick="activateTrack(track, index)"
|
@dblclick="activateTrack(track, index)"
|
||||||
|
@mousemove="hover = true"
|
||||||
|
@mouseout="hover = false"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="actions one wide left floated column"
|
class="actions one wide left floated column"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Track } from '~/types'
|
import type { Track } from '~/types'
|
||||||
|
|
||||||
import { useElementByPoint, useMouse } from '@vueuse/core'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { clone, uniqBy } from 'lodash-es'
|
import { clone, uniqBy } from 'lodash-es'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
@ -71,15 +70,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
unique: true
|
unique: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const { x, y } = useMouse({ type: 'client' })
|
|
||||||
const { element } = useElementByPoint({ x, y })
|
|
||||||
const hover = computed(() => {
|
|
||||||
const row = element.value?.closest('.track-row') ?? null
|
|
||||||
return row && allTracks.value.find(track => {
|
|
||||||
return `${track.id}` === row.getAttribute('data-track-id') && `${track.position}` === row.getAttribute('data-track-position')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentPage = ref(props.page)
|
const currentPage = ref(props.page)
|
||||||
const totalTracks = ref(props.total)
|
const totalTracks = ref(props.total)
|
||||||
const fetchDataUrl = ref(props.nextUrl)
|
const fetchDataUrl = ref(props.nextUrl)
|
||||||
|
@ -235,8 +225,6 @@ const updatePage = (page: number) => {
|
||||||
<track-row
|
<track-row
|
||||||
v-for="(track, index) in allTracks"
|
v-for="(track, index) in allTracks"
|
||||||
:key="`${track.id} ${track.position}`"
|
:key="`${track.id} ${track.position}`"
|
||||||
:data-track-id="track.id"
|
|
||||||
:data-track-position="track.position"
|
|
||||||
:track="track"
|
:track="track"
|
||||||
:index="index"
|
:index="index"
|
||||||
:tracks="allTracks"
|
:tracks="allTracks"
|
||||||
|
@ -247,7 +235,6 @@ const updatePage = (page: number) => {
|
||||||
:display-actions="displayActions"
|
:display-actions="displayActions"
|
||||||
:show-duration="showDuration"
|
:show-duration="showDuration"
|
||||||
:is-podcast="isPodcast"
|
:is-podcast="isPodcast"
|
||||||
:hover="hover === track"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMouse, useCurrentElement, useRafFn, useElementByPoint } from '@vueuse/core'
|
import { useMouse, useCurrentElement, useRafFn, useElementByPoint } from '@vueuse/core'
|
||||||
import { ref, watchEffect, reactive } from 'vue'
|
import { ref, watchEffect, reactive, watch } from 'vue'
|
||||||
|
|
||||||
// @ts-expect-error no typings
|
// @ts-expect-error no typings
|
||||||
import { RecycleScroller } from 'vue-virtual-scroller'
|
import { RecycleScroller } from 'vue-virtual-scroller'
|
||||||
|
@ -9,7 +9,6 @@ import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'reorder', from: number, to: number): void
|
(e: 'reorder', from: number, to: number): void
|
||||||
(e: 'visible'): void
|
(e: 'visible'): void
|
||||||
(e: 'hidden'): void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -97,7 +96,19 @@ const cleanup = () => {
|
||||||
const scrollDirection = ref()
|
const scrollDirection = ref()
|
||||||
const containerSize = reactive({ bottom: 0, top: 0 })
|
const containerSize = reactive({ bottom: 0, top: 0 })
|
||||||
const { x, y: screenY } = useMouse({ type: 'client' })
|
const { x, y: screenY } = useMouse({ type: 'client' })
|
||||||
const { element: hoveredElement } = useElementByPoint({ x, y: screenY })
|
|
||||||
|
const { element: hoveredElement, pause: dragTrackPause, resume: dragTrackStart } = useElementByPoint({ x, y: screenY })
|
||||||
|
dragTrackPause()
|
||||||
|
|
||||||
|
// Disable element lookup
|
||||||
|
watch(draggedItem, (dragging) => {
|
||||||
|
if (dragging) {
|
||||||
|
dragTrackStart()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dragTrackPause()
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
// Find current index and position on both desktop and mobile devices
|
// Find current index and position on both desktop and mobile devices
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
@ -138,23 +149,35 @@ const resize = () => {
|
||||||
containerSize.bottom = element.offsetHeight + containerSize.top
|
containerSize.bottom = element.offsetHeight + containerSize.top
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scrolling when item held near top/bottom border
|
||||||
let lastDate = +new Date()
|
let lastDate = +new Date()
|
||||||
const { resume, pause } = useRafFn(() => {
|
const { resume, pause } = useRafFn(() => {
|
||||||
const now = +new Date()
|
const now = +new Date()
|
||||||
const delta = now - lastDate
|
|
||||||
const direction = scrollDirection.value
|
const direction = scrollDirection.value
|
||||||
|
|
||||||
if (direction && el.value?.children[0] && !isTouch.value) {
|
if (direction && el.value?.children[0] && !isTouch.value) {
|
||||||
el.value.children[0].scrollTop += 200 / delta * (direction === 'up' ? -1 : 1)
|
el.value.children[0].scrollTop += 200 / (now - lastDate) * (direction === 'up' ? -1 : 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
lastDate = now
|
lastDate = now
|
||||||
}, { immediate: false })
|
}, { immediate: false })
|
||||||
|
|
||||||
const virtualList = ref()
|
const virtualList = ref()
|
||||||
|
const scrollToIndex = (index: number, block: 'center' | 'start' | 'end' = 'start') => {
|
||||||
|
if (!virtualList.value) return
|
||||||
|
|
||||||
|
const position = block === 'start'
|
||||||
|
? index * props.size
|
||||||
|
: block === 'end'
|
||||||
|
? (index + 1) * props.size - virtualList.value.$el.offsetHeight
|
||||||
|
: (index + 0.5) * props.size - virtualList.value.$el.offsetHeight / 2
|
||||||
|
|
||||||
|
virtualList.value.scrollToPosition(position)
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
scrollToIndex: (index: number) => virtualList.value?.scrollToItem(index),
|
|
||||||
scroller: virtualList,
|
scroller: virtualList,
|
||||||
|
scrollToIndex,
|
||||||
cleanup
|
cleanup
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -173,7 +196,6 @@ defineExpose({
|
||||||
@touchmove="onTouchmove"
|
@touchmove="onTouchmove"
|
||||||
@resize="resize"
|
@resize="resize"
|
||||||
@visible="emit('visible')"
|
@visible="emit('visible')"
|
||||||
@hidden="emit('hidden')"
|
|
||||||
>
|
>
|
||||||
<template #before>
|
<template #before>
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
|
|
|
@ -168,15 +168,6 @@ export const usePlayer = createGlobalState(() => {
|
||||||
|
|
||||||
// Progress
|
// Progress
|
||||||
const progress = ref(0)
|
const progress = ref(0)
|
||||||
useRafFn(() => {
|
|
||||||
const sound = currentSound.value
|
|
||||||
if (!sound) {
|
|
||||||
progress.value = 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.value = sound.currentTime / sound.duration * 100
|
|
||||||
})
|
|
||||||
|
|
||||||
// Loading
|
// Loading
|
||||||
const loading = computed(() => {
|
const loading = computed(() => {
|
||||||
|
|
|
@ -296,7 +296,6 @@ export const useQueue = createGlobalState(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ends in
|
// Ends in
|
||||||
const now = useNow()
|
|
||||||
const endsIn = useTimeAgo(computed(() => {
|
const endsIn = useTimeAgo(computed(() => {
|
||||||
const seconds = sum(
|
const seconds = sum(
|
||||||
queue.value
|
queue.value
|
||||||
|
@ -304,10 +303,12 @@ export const useQueue = createGlobalState(() => {
|
||||||
.map((track) => track.sources[0]?.duration ?? 0)
|
.map((track) => track.sources[0]?.duration ?? 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
const date = new Date(now.value)
|
const date = new Date()
|
||||||
date.setSeconds(date.getSeconds() + seconds)
|
date.setSeconds(date.getSeconds() + seconds)
|
||||||
return date
|
return date
|
||||||
}))
|
}), {
|
||||||
|
updateInterval: 0
|
||||||
|
})
|
||||||
|
|
||||||
// Clear
|
// Clear
|
||||||
const clearRadio = ref(false)
|
const clearRadio = ref(false)
|
||||||
|
|
|
@ -20,7 +20,7 @@ const AUDIO_ELEMENT = document.createElement('audio')
|
||||||
|
|
||||||
const soundPromises = new Map<number, Promise<Sound>>()
|
const soundPromises = new Map<number, Promise<Sound>>()
|
||||||
const soundCache = useLRUCache<number, Sound>({
|
const soundCache = useLRUCache<number, Sound>({
|
||||||
max: 10,
|
max: 3,
|
||||||
dispose: (sound) => sound.dispose()
|
dispose: (sound) => sound.dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -192,7 +192,8 @@
|
||||||
"queuePosition": "Track {index} of {length}",
|
"queuePosition": "Track {index} of {length}",
|
||||||
"startTime": "00:00",
|
"startTime": "00:00",
|
||||||
"unknownArtist": "Unknown Artist",
|
"unknownArtist": "Unknown Artist",
|
||||||
"unknownAlbum": "Unknown Album"
|
"unknownAlbum": "Unknown Album",
|
||||||
|
"end": "End"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"RemoteSearchForm": {
|
"RemoteSearchForm": {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import type { InitModule } from '~/types'
|
||||||
|
|
||||||
import store, { key } from '~/store'
|
import store, { key } from '~/store'
|
||||||
import router from '~/router'
|
import router from '~/router'
|
||||||
|
import devtools from '@vue/devtools'
|
||||||
|
|
||||||
import { createApp, defineAsyncComponent, h } from 'vue'
|
import { createApp, defineAsyncComponent, h } from 'vue'
|
||||||
|
|
||||||
|
@ -15,6 +16,10 @@ import '~/api'
|
||||||
// NOTE: Set the theme as fast as possible
|
// NOTE: Set the theme as fast as possible
|
||||||
useTheme()
|
useTheme()
|
||||||
|
|
||||||
|
if (import.meta.env.MODE === 'development') {
|
||||||
|
devtools.connect(/* host, port */)
|
||||||
|
}
|
||||||
|
|
||||||
const logger = useLogger()
|
const logger = useLogger()
|
||||||
logger.info('Loading environment:', import.meta.env.MODE)
|
logger.info('Loading environment:', import.meta.env.MODE)
|
||||||
logger.debug('Environment variables:', import.meta.env)
|
logger.debug('Environment variables:', import.meta.env)
|
||||||
|
@ -34,6 +39,8 @@ const app = createApp({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.config.performance = false
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(store, key)
|
app.use(store, key)
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,18 @@
|
||||||
.ui.progress:not(.indeterminate)
|
.ui.progress:not(.indeterminate)
|
||||||
.bar.position:not(.buffer) {
|
.bar.position:not(.buffer) {
|
||||||
background: var(--vibrant-color);
|
background: var(--vibrant-color);
|
||||||
|
will-change: transform;
|
||||||
|
|
||||||
|
|
||||||
|
&.animated {
|
||||||
|
@keyframes progress {
|
||||||
|
0% { transform: translate3d(-100%, 0, 0) }
|
||||||
|
100% { transform: translate3d(0, 0, 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
animation-name: progress;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.indicating.progress .bar {
|
.indicating.progress .bar {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { defineConfig, type PluginOption } from 'vite'
|
import { defineConfig, type PluginOption } from 'vite'
|
||||||
import Vue from '@vitejs/plugin-vue'
|
import Vue from '@vitejs/plugin-vue'
|
||||||
import Inspector from 'vite-plugin-vue-inspector'
|
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
|
||||||
import VueI18n from '@intlify/vite-plugin-vue-i18n'
|
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
|
|
1192
front/yarn.lock
1192
front/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue