add darkmode and progress bar
This commit is contained in:
parent
27ab4c870d
commit
4f7b922428
16
src/App.vue
16
src/App.vue
@ -1,9 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import DarkModeToggle from "@/components/DarkmodeToggle.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="$route.meta.allowEmbed && $route.query.embed === 'true'" class="p-2">
|
||||
<router-view />
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="bg-secondary shadow">
|
||||
<div class="bg-darkmode-dark bg-light shadow sticky-top">
|
||||
<nav class="navbar px-2 container">
|
||||
<router-link class="d-flex btn" to="/">
|
||||
<div class="d-flex header-title flex-column justify-content-center">
|
||||
@ -11,14 +15,18 @@
|
||||
<div>music connects</div>
|
||||
</div>
|
||||
</router-link>
|
||||
<div>
|
||||
<div class="d-flex align-items-center">
|
||||
<router-link
|
||||
v-if="!$api.isAuthorized()"
|
||||
to="/auth"
|
||||
class="btn btn-outline-dark"
|
||||
class="btn btn-outline"
|
||||
>
|
||||
login
|
||||
</router-link>
|
||||
<DarkModeToggle class="mx-2" v-slot="{ state }">
|
||||
<i v-if="state" class="bi-moon"></i>
|
||||
<i v-else class="bi-sun"></i>
|
||||
</DarkModeToggle>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
@ -31,8 +39,6 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "main.scss";
|
||||
|
||||
nav {
|
||||
a {
|
||||
font-weight: bold;
|
||||
|
@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
import TrackProgressBar from "@/components/TrackProgressBar.vue";
|
||||
|
||||
defineProps({
|
||||
currentlyPlaying: Object,
|
||||
currentlyPlaying: Object
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -11,46 +12,48 @@ defineProps({
|
||||
<div class="card-header">
|
||||
<b>{{ currentlyPlaying?.item.name }}</b>
|
||||
<div>
|
||||
{{ currentlyPlaying?.item.artists.map(artist => artist.name).join(', ') }}
|
||||
{{ currentlyPlaying?.item.artists.map(artist => artist.name).join(", ") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col coverGroup">
|
||||
<img :src="currentlyPlaying.item.album.images[0].url" alt="album cover" class="card-img">
|
||||
<p v-if="currentlyPlaying.context">
|
||||
listening from {{ currentlyPlaying?.context.type }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<p class="my-2">
|
||||
<a
|
||||
:href="currentlyPlaying?.item.externalURL.spotify"
|
||||
target="_blank"
|
||||
rel="noopener norefferrer"
|
||||
class="btn btn-outline-dark m-1"
|
||||
>
|
||||
view track on Spotify
|
||||
</a>
|
||||
<a
|
||||
v-if="currentlyPlaying?.context"
|
||||
:href="currentlyPlaying?.context.externalURL.spotify"
|
||||
target="_blank"
|
||||
rel="noopener norefferrer"
|
||||
class="btn btn-outline-dark m-1"
|
||||
>
|
||||
view {{ currentlyPlaying?.context.type }} on Spotify
|
||||
</a>
|
||||
</p>
|
||||
<TrackProgressBar
|
||||
:duration="currentlyPlaying?.item.duration"
|
||||
:progress="currentlyPlaying.progress"
|
||||
:isPlaying="currentlyPlaying.isPlaying"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="">
|
||||
<a
|
||||
:href="currentlyPlaying?.item.externalURL.spotify"
|
||||
target="_blank"
|
||||
rel="noopener norefferrer"
|
||||
class="btn btn-outline-dark m-1"
|
||||
>
|
||||
view track on Spotify
|
||||
</a>
|
||||
<a
|
||||
v-if="currentlyPlaying?.context"
|
||||
:href="currentlyPlaying?.context.externalURL.spotify"
|
||||
target="_blank"
|
||||
rel="noopener norefferrer"
|
||||
class="btn btn-outline-dark m-1"
|
||||
>
|
||||
view {{ currentlyPlaying?.context.type }} on Spotify
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.currentlyPlaying {
|
||||
.card-img {
|
||||
.coverGroup {
|
||||
max-width: 20rem;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
import TrackProgressBar from "@/components/TrackProgressBar.vue";
|
||||
|
||||
defineProps({
|
||||
currentlyPlaying: Object,
|
||||
@ -18,6 +19,11 @@ defineProps({
|
||||
<div>
|
||||
{{ currentlyPlaying?.item.artists.map(artist => artist.name).join(', ') }}
|
||||
</div>
|
||||
<TrackProgressBar
|
||||
:duration="currentlyPlaying?.item.duration"
|
||||
:progress="currentlyPlaying.progress"
|
||||
:isPlaying="currentlyPlaying.isPlaying"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
23
src/components/DarkmodeToggle.vue
Normal file
23
src/components/DarkmodeToggle.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="form-check form-switch">
|
||||
<label class="custom-control-label" for="darkSwitch">
|
||||
<slot :state="sliderState" />
|
||||
</label>
|
||||
<input v-model="sliderState" type="checkbox" class="form-check-input" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { themeConfig } from '@/main';
|
||||
|
||||
const sliderState = ref(themeConfig.getTheme() === 'dark');
|
||||
|
||||
watch(sliderState, (state) => {
|
||||
themeConfig.setTheme(state ? 'dark' : 'light');
|
||||
});
|
||||
|
||||
themeConfig.themeChangeHandlers.push((newTheme) => {
|
||||
sliderState.value = newTheme === 'dark';
|
||||
});
|
||||
</script>
|
@ -52,14 +52,13 @@ const update = async (promise: Promise | unknown) => {
|
||||
loading.value = true;
|
||||
if (props.throbber) setTimeout(() => {
|
||||
if (loading.value) showThrobber.value = true;
|
||||
}, 250);
|
||||
}, 500);
|
||||
try {
|
||||
data.value = await (promise.isPromiseList
|
||||
? Promise.all(promise.promises)
|
||||
: promise);
|
||||
} catch (e) {
|
||||
error.value = e;
|
||||
console.error('PR', e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
showThrobber.value = false;
|
||||
|
30
src/components/TimeFormatter.vue
Normal file
30
src/components/TimeFormatter.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, watch, ref } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
seconds: Number,
|
||||
});
|
||||
|
||||
const getHmsFromSeconds = (d: number) => ({
|
||||
hours: Math.floor(d / 3600),
|
||||
minutes: Math.floor(d % 3600 / 60),
|
||||
seconds: Math.floor(d % 3600 % 60),
|
||||
});
|
||||
|
||||
const getLeadingZero = (a: number, digits: number) =>
|
||||
('0'.repeat(digits) + a.toString())
|
||||
.substr(- digits);
|
||||
|
||||
const hms = ref(getHmsFromSeconds(props.seconds as number));
|
||||
|
||||
watch(() => props.seconds, (to) => {
|
||||
hms.value = getHmsFromSeconds(to as number);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="hms.hours" class="hours">{{ getLeadingZero(hms.hours, 2) }}:</span>
|
||||
<span class="minutes">{{ getLeadingZero(hms.minutes, 2) }}:</span>
|
||||
<span class="seconds">{{ getLeadingZero(hms.seconds, 2) }}</span>
|
||||
</template>
|
||||
|
52
src/components/TrackProgressBar.vue
Normal file
52
src/components/TrackProgressBar.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, ref, watch, onBeforeUnmount } from "vue";
|
||||
import TimeFormatter from "@/components/TimeFormatter.vue";
|
||||
|
||||
const props = defineProps({
|
||||
duration: Number,
|
||||
progress: Number,
|
||||
isPlaying: Boolean,
|
||||
});
|
||||
|
||||
const estimatedProgress = ref(props.progress as number);
|
||||
|
||||
const updateInterval = setInterval(() => {
|
||||
if (props.isPlaying) estimatedProgress.value += 1000;
|
||||
}, 1000);
|
||||
|
||||
watch(() => props.progress, (to) => {
|
||||
estimatedProgress.value = to as number;
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(updateInterval);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="progress my-2">
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
:style="{
|
||||
width: `${ estimatedProgress / duration * 100 }%`
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col">
|
||||
<TimeFormatter :seconds="estimatedProgress / 1000"/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<TimeFormatter :seconds="duration / 1000"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.progress {
|
||||
height: 0.5rem;
|
||||
}
|
||||
</style>
|
@ -1,8 +1,7 @@
|
||||
@import "bootstrap/scss/bootstrap.scss";
|
||||
//@import "bootstrap-darkmode/scss/darktheme.scss";
|
||||
@import "../node_modules/bootstrap/scss/bootstrap.scss";
|
||||
@import "../node_modules/bootstrap-darkmode/scss/darktheme.scss";
|
||||
|
||||
html, body {
|
||||
//background-color: $dark-body-bg;
|
||||
line-break: loose;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
@ -3,6 +3,12 @@ import App from "./App.vue";
|
||||
import "./registerServiceWorker";
|
||||
import router from "./router";
|
||||
import { createApi } from "@/Api";
|
||||
import 'bootstrap-icons/font/bootstrap-icons.scss';
|
||||
import './main.scss';
|
||||
import { ThemeConfig } from 'bootstrap-darkmode';
|
||||
|
||||
export const themeConfig = new ThemeConfig();
|
||||
themeConfig.initTheme();
|
||||
|
||||
createApp(App)
|
||||
.use(router)
|
||||
|
@ -13,7 +13,7 @@ const userInfo = ref(api?.getUserInfo(route.params.id as string));
|
||||
|
||||
let refreshUserInfo = setInterval(() => {
|
||||
userInfo.value = api?.getUserInfo(route.params.id as string);
|
||||
}, 20000);
|
||||
}, 10000);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(refreshUserInfo);
|
||||
@ -43,10 +43,7 @@ onBeforeUnmount(() => {
|
||||
v-slot="{ data: { user, currentlyPlaying } }"
|
||||
class="row"
|
||||
>
|
||||
<div v-if="$route.query.embed === 'true'">
|
||||
|
||||
</div>
|
||||
<div v-else class="col-md-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{ user.displayName }}
|
||||
@ -80,6 +77,9 @@ onBeforeUnmount(() => {
|
||||
<div class="col">
|
||||
<h2>Currently listening to:</h2>
|
||||
<CurrentlyPlaying v-if="currentlyPlaying?.item" :currently-playing="currentlyPlaying" />
|
||||
<p v-else class="alert alert-info">
|
||||
{{ user.displayName }} is not listening to music.
|
||||
</p>
|
||||
</div>
|
||||
</PromiseResolver>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user