Skip to content

Commit

Permalink
fix: web styles and player bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
Reknij committed May 15, 2024
1 parent 55ed99e commit 8ade493
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 61 deletions.
3 changes: 2 additions & 1 deletion web/api/customFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ type useFetchType = typeof useFetch;

export const useApiFetch: useFetchType = (path, opts = {}) => {
const auth = useCookie(AUTH_COOKIE_NAME);
const config = useRuntimeConfig();
const headers: any = {}
if (auth.value) {
headers["X-Authorization"] = auth.value.toString();
}
opts.baseURL = `/api`;
opts.baseURL = `${import.meta.dev ? config.public.devBaseUrl : config.public.baseUrl}/api`;
opts.headers = headers;
opts.key = `${path}${JSON.stringify(opts.query)}`;
// opts.credentials = 'include'; // cloudflare workers will broken.
Expand Down
3 changes: 2 additions & 1 deletion web/api/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import type { PubMediaInfo, GetMediasQuery, ListSlice } from "./model";

export function getMediaFileAddress(id: number): string {
const auth = useCookie(AUTH_COOKIE_NAME).value;
const origin = import.meta.dev? 'http://127.0.0.1:3177' : location.origin;
const config = useRuntimeConfig();
const origin = import.meta.dev? config.public.devBaseUrl : config.public.baseUrl;
return `${origin}/api/media_file/${id}?auth=${auth}`;
}

Expand Down
33 changes: 14 additions & 19 deletions web/components/MediaPlayer.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
<script setup lang="ts">
import type { PubMediaInfo } from '~/api/model';
import { type MediaPlayerState, PlayMode, useMediaPlayer } from '~/composables/player';
const DEFAULT_COVER = '/default_cover.jpg';
const props = defineProps<{
state: MediaPlayerState,
}>();
Expand All @@ -28,17 +24,6 @@ function changePlaying() {
}
}
function toHHMMSS(sec_num: number) {
const hours = Math.floor(sec_num / 3600);
const minutes = Math.floor((sec_num - (hours * 3600)) / 60);
const seconds = sec_num - (hours * 3600) - (minutes * 60);
return [hours, minutes, seconds]
.map(v => v < 10 ? "0" + v : v)
.filter((v, i) => v !== "00" || i > 0)
.join(":");
}
function closePlayer() {
player.stop();
player.hide();
Expand All @@ -49,8 +34,18 @@ function closePlayer() {
<div v-if="state.visible">
<div v-if="state.current"
class="bg-white border-t border-gray-200 dark:border-gray-700 border-gray-200 dark:bg-gray-900 flex flex-col md:flex-row items-center gap-2 p-2 w-full">
<div class="flex flex-row items-center justify-center gap-2 flex-1">
<img class="w-20 aspect-square rounded" :src="setAuthQuery(state.current.cover_url) ?? DEFAULT_COVER" />
<div class="flex flex-row items-center justify-center gap-2 w-full">
<div class="flex items-center justify-center size-16 shrink-0">
<img loading="lazy" class="size-full rounded object-cover" v-if="state.current.cover_url"
:src="setAuthQuery(state.current.cover_url)" :onerror="() => {
if (state.current) state.current.cover_url = undefined;
}">
<svg v-else class="text-gray-300 size-full rounded object-cover" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path fill="currentColor"
d="M16 9h-3v5.5a2.5 2.5 0 0 1-2.5 2.5A2.5 2.5 0 0 1 8 14.5a2.5 2.5 0 0 1 2.5-2.5c.57 0 1.08.19 1.5.5V7h4zm-4-7a10 10 0 0 1 10 10a10 10 0 0 1-10 10A10 10 0 0 1 2 12A10 10 0 0 1 12 2m0 2a8 8 0 0 0-8 8a8 8 0 0 0 8 8a8 8 0 0 0 8-8a8 8 0 0 0-8-8" />
</svg>
</div>
<div class="flex flex-col gap-1 w-full">
<div class="flex flex-col gap-1">
<span class="font-bold text-sm line-clamp-1">{{ state.current.title }}</span>
Expand Down Expand Up @@ -125,13 +120,13 @@ function closePlayer() {

<div class="flex flex-col justify-center gap-2" v-for="media in state.playlist">
<div class="flex flex-row items-center justify-between gap-2">
<div :class="media === state.current ? `text-primary` : ``" @click="player.skipTo(media)">
<div :class="media === state.current ? `text-primary` : ``" @click="player.skipTo(media)">
<div
class="flex flex-col justify-center gap-2 flex-1 w-full hover:text-primary-400 hover:cursor-pointer">
<div class="flex flex-col gap-1">
<span class="font-bold text-sm line-clamp-1">{{
media.title
}}</span>
}}</span>
<div class="flex flex-row flex-wrap gap-2 items-center ">
<div class="flex flex-row items-center gap-1">
<UIcon name="i-mdi-account-music-outline" />
Expand Down
24 changes: 15 additions & 9 deletions web/components/Medias.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { getMedias } from '~/api/media';
import type { GetMediasQuery, PubMediaInfo, Source } from '~/api/model';
import { PlayMode, useMediaPlayer, useMediaPlayerState } from '~/composables/player';
const DEFAULT_COVER = '/default_cover.jpg';
const props = defineProps<{
source: Source,
filter?: string,
Expand All @@ -20,6 +19,7 @@ const query = reactive<GetMediasQuery>({
const { data: medias } = await getMedias(query);
async function searchClicked() {
query.index = 0;
query.to_search = searchValue.value;
}
const player = useMediaPlayer();
Expand Down Expand Up @@ -70,6 +70,14 @@ function getMoreActions(media: PubMediaInfo) {
player.push(media);
player.show();
}
},
{
icon: 'i-heroicons-photo',
label: 'Open cover',
disabled: !media.cover_url,
click() {
window.open(media.cover_url, '_blank');
}
}
]
]
Expand Down Expand Up @@ -97,14 +105,12 @@ function getMoreActions(media: PubMediaInfo) {
class="grid grid-flow-row auto-rows-fr grid-cols-2 min-[410px]:grid-cols-3 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-9 min-[1920px]:grid-cols-12 gap-2 justify-items-center">
<div class="relative wild-card wild-card-btn hover:!cursor-default flex flex-col items-center justify-center w-full !h-full"
v-for="media in medias?.items">
<div class="flex items-center w-full h-full rounded">
<img class="aspect-square w-full rounded" v-if="media.cover_url" :src="setAuthQuery(media.cover_url) ?? DEFAULT_COVER" :onerror="() => {
// @ts-ignore
this.onerror = null;
// @ts-ignore
this.src = DEFAULT_COVER;
}">
<svg v-else class="text-gray-300 aspect-square w-full rounded" xmlns="http://www.w3.org/2000/svg"
<div class="flex items-center justify-center size-full">
<img loading="lazy" class="size-full rounded object-cover" v-if="media.cover_url"
:src="setAuthQuery(media.cover_url)" :onerror="() => {
media.cover_url = undefined;
}">
<svg v-else class="text-gray-300 size-full rounded object-cover" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path fill="currentColor"
d="M16 9h-3v5.5a2.5 2.5 0 0 1-2.5 2.5A2.5 2.5 0 0 1 8 14.5a2.5 2.5 0 0 1 2.5-2.5c.57 0 1.08.19 1.5.5V7h4zm-4-7a10 10 0 0 1 10 10a10 10 0 0 1-10 10A10 10 0 0 1 2 12A10 10 0 0 1 12 2m0 2a8 8 0 0 0-8 8a8 8 0 0 0 8 8a8 8 0 0 0 8-8a8 8 0 0 0-8-8" />
Expand Down
65 changes: 47 additions & 18 deletions web/composables/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ export enum PlayMode {

export interface MediaPlayerState {
visible: boolean,
current?: PubMediaInfo,
current: PubMediaInfo | undefined,
currentIndex: number,
currentElapsedSeconds: number,
_elapsedSecondsHandler: number,
_setSeekHandler: number,
playlist: PubMediaInfo[],
playing: boolean,
mode: PlayMode
Expand All @@ -23,8 +25,11 @@ export const useMediaPlayerState = () => useState<MediaPlayerState>('mediaPlayer
const state: MediaPlayerState = {
visible: false,
playing: false,
current: undefined,
currentIndex: -1,
currentElapsedSeconds: 0,
_elapsedSecondsHandler: -1,
_setSeekHandler: -1,
playlist: [],
mode: PlayMode.Order,
};
Expand All @@ -41,32 +46,47 @@ function unloadHowl() {
useSound().value = undefined;
}
}

function setElapsedTime(clearOnly: boolean = false) {
const state = useMediaPlayerState().value;
if (state._elapsedSecondsHandler != -1) {
window.clearInterval(state._elapsedSecondsHandler);
state._elapsedSecondsHandler = -1;
}
if (!clearOnly) {
state._elapsedSecondsHandler = window.setInterval(() => {
state.currentElapsedSeconds = useSound().value?.seek() ?? 0;
}, 10);
}
}

function loadHowl() {
const state = useMediaPlayerState().value;
if (state.current) {
unloadHowl();
const sound = new Howl({
src: getMediaFileAddress(state.current.id),
format: state.current.file_type,
onplay(soundId) {
onplay() {
state.playing = true;
const setSeek = () => setTimeout(() => {
state.currentElapsedSeconds = Math.floor(sound.seek(soundId));
if (sound.playing(soundId)) {
setSeek();
}
}, 150);
setSeek();
setElapsedTime();
},
onstop() {
state.playing = false;
setElapsedTime(true);
},
onpause() {
state.playing = false;
},
onend(soundId) {
state.playing = false;
const player = useMediaPlayer();
const playNext = (loop = false) => {
if (player.canForward()) {
player.forward();
} else if (loop) {
player.playByIndex(0);
} else {
state.playing = false;
}
}
switch (state.mode) {
Expand All @@ -84,7 +104,7 @@ function loadHowl() {
const random = Math.floor(Math.random() * state.playlist.length);
player.playByIndex(random);
} else {
state.playing = false;
state.currentIndex = -1;
}
break;
default:
Expand All @@ -101,6 +121,12 @@ function loadHowl() {
export const useMediaPlayer = () => {
const state = useMediaPlayerState();
return {
getCurrent(): PubMediaInfo | undefined {
if (state.value.currentIndex > -1 && state.value.currentIndex < this.total()) {
return state.value.playlist[state.value.currentIndex];
}
return undefined;
},
show() {
state.value.visible = true;
},
Expand All @@ -115,8 +141,8 @@ export const useMediaPlayer = () => {
},
playByIndex(index: number) {
if (this.total() > 0 && this.total() > index && index >= 0) {
state.value.current = state.value.playlist[index];
state.value.currentIndex = index;
state.value.current = state.value.playlist[index];
loadHowl();
}
},
Expand All @@ -138,19 +164,15 @@ export const useMediaPlayer = () => {
return state.value.playlist.length;
},
stop() {
state.value.current = undefined;
state.value.currentIndex = -1;
state.value.currentElapsedSeconds = 0;
state.value.current = undefined;
state.value.mode = PlayMode.Order;
unloadHowl();
state.value.playing = false;
},
resume() {
state.value.playing = true;
useSound().value?.play();
},
pause() {
state.value.playing = false;
useSound().value?.pause();
},
canForward() {
Expand Down Expand Up @@ -181,9 +203,16 @@ export const useMediaPlayer = () => {
},
seekTo(seconds: number) {
const sound = useSound().value;
const state = useMediaPlayerState().value;
if (sound) {
this.pause();
sound.seek(seconds);
sound.play()
if (state._setSeekHandler != -1) {
window.clearTimeout(state._setSeekHandler);
}
state._setSeekHandler = window.setTimeout(() => {
this.resume();
}, 100);
}
},
push(media: PubMediaInfo, target = -1) {
Expand Down
11 changes: 6 additions & 5 deletions web/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ export default defineNuxtConfig({
}
},
ssr: false,
runtimeConfig: {
public: {
baseUrl: '',
devBaseUrl: 'http://127.0.0.1:3177'
}
},
nitro: {
preset: 'static',
devProxy: {
"/api": {
target: "http://0.0.0.0:3177/api",
},
},
},
})
18 changes: 13 additions & 5 deletions web/pages/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,46 @@ const state = reactive<LoginQuery>({
username: '',
password: '',
})
const isGuestEnter = ref(false);
async function loginBtnClicked() {
await loginNow(state);
router.replace('/');
}
async function guestEnter() {
isGuestEnter.value = true;
state.username = 'guest';
state.password = '';
if (serverInfo.value?.guest_password_required === false) {
await loginBtnClicked();
}
}
function userEnter() {
isGuestEnter.value = false;
state.username = '';
state.password = '';
}
</script>

<template>
<div class="flex items-center justify-center p-2 h-full">
<UCard>
<UForm :schema="schema" :state="state" class="space-y-2" @keyup.enter="loginBtnClicked">
<UFormGroup label="Username" v-if="!serverInfo?.guest_enable && state.username !== 'guest'">
<UFormGroup label="Username" v-if="!isGuestEnter">
<UInput v-model="state.username" />
</UFormGroup>
<UFormGroup label="Password" v-if="state.username !== 'guest' || serverInfo?.guest_password_required">
<UFormGroup label="Password" v-if="!isGuestEnter || serverInfo?.guest_password_required">
<UInput v-model="state.password" />
</UFormGroup>
<UFormGroup v-if="state.username !== 'guest'">
<UFormGroup v-if="!isGuestEnter">
<div class="flex gap-2">
<UButton @click="loginBtnClicked" label="Login" />
<UButton @click="guestEnter" label="Guest enter" />
<UButton v-if="serverInfo?.guest_enable" @click="guestEnter" label="Guest enter" />
</div>
</UFormGroup>
<UFormGroup v-else>
<UButton @click="state.username = ''" label="User enter" />
<UButton @click="userEnter" label="User enter" />
</UFormGroup>
</UForm>
</UCard>
Expand Down
6 changes: 3 additions & 3 deletions web/pages/workplace.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ async function reloadPluginsHandler() {
}
async function reloadAllHandler() {
await reloadMediasHandler();
await reloadPluginsHandler();
await reloadMediasHandler();
}
</script>

Expand All @@ -52,9 +52,9 @@ async function reloadAllHandler() {
<UButton label="Reload plugins" variant="outline" @click="reloadPluginsHandler" />
<UButton label="Reload all" @click="reloadAllHandler" />
<UButton label="Setup again" to="/setup" />
<UDivider orientation="vertical" class="w-full text-white" :ui="{container: {base: 'text-black'}}" />
<UDivider orientation="vertical" class="w-full text-white" :ui="{ container: { base: 'text-black' } }" />
<span>Total media: {{ medias?.total ?? 0 }}</span>
<span>Server time running: {{ serverInfo?.time_running ? (serverInfo.time_running / 60).toFixed(0) + ' minutes' : 'Just started' }}</span>
<span>Server time running: {{ serverInfo?.time_running ? (serverInfo.time_running / 60 / 60).toFixed(1) + ' hour(s)' : 'Just started' }}</span>
<span>Author: {{ serverInfo?.author }}</span>
<span>Version: {{ serverInfo?.version }}</span>
</div>
Expand Down
Binary file removed web/public/default_cover.jpg
Binary file not shown.
Loading

0 comments on commit 8ade493

Please sign in to comment.