add darkmode and progress bar

master
adb-sh 2 years ago
parent 27ab4c870d
commit 4f7b922428

@ -1,9 +1,13 @@
<script setup lang="ts">
import DarkModeToggle from "@/components/DarkmodeToggle.vue";
</script>
<template> <template>
<div v-if="$route.meta.allowEmbed && $route.query.embed === 'true'" class="p-2"> <div v-if="$route.meta.allowEmbed && $route.query.embed === 'true'" class="p-2">
<router-view /> <router-view />
</div> </div>
<div v-else> <div v-else>
<div class="bg-secondary shadow"> <div class="bg-darkmode-dark bg-light shadow sticky-top">
<nav class="navbar px-2 container"> <nav class="navbar px-2 container">
<router-link class="d-flex btn" to="/"> <router-link class="d-flex btn" to="/">
<div class="d-flex header-title flex-column justify-content-center"> <div class="d-flex header-title flex-column justify-content-center">
@ -11,14 +15,18 @@
<div>music connects</div> <div>music connects</div>
</div> </div>
</router-link> </router-link>
<div> <div class="d-flex align-items-center">
<router-link <router-link
v-if="!$api.isAuthorized()" v-if="!$api.isAuthorized()"
to="/auth" to="/auth"
class="btn btn-outline-dark" class="btn btn-outline"
> >
login login
</router-link> </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> </div>
</nav> </nav>
</div> </div>
@ -31,8 +39,6 @@
</template> </template>
<style lang="scss"> <style lang="scss">
@import "main.scss";
nav { nav {
a { a {
font-weight: bold; font-weight: bold;

@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineProps } from "vue"; import { defineProps } from "vue";
import TrackProgressBar from "@/components/TrackProgressBar.vue";
defineProps({ defineProps({
currentlyPlaying: Object, currentlyPlaying: Object
}); });
</script> </script>
@ -11,46 +12,48 @@ defineProps({
<div class="card-header"> <div class="card-header">
<b>{{ currentlyPlaying?.item.name }}</b> <b>{{ currentlyPlaying?.item.name }}</b>
<div> <div>
{{ currentlyPlaying?.item.artists.map(artist => artist.name).join(', ') }} {{ currentlyPlaying?.item.artists.map(artist => artist.name).join(", ") }}
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row justify-content-center">
<div class="col-md"> <div class="col coverGroup">
<img :src="currentlyPlaying.item.album.images[0].url" alt="album cover" class="card-img"> <img :src="currentlyPlaying.item.album.images[0].url" alt="album cover" class="card-img">
<p v-if="currentlyPlaying.context"> <TrackProgressBar
listening from {{ currentlyPlaying?.context.type }} :duration="currentlyPlaying?.item.duration"
</p> :progress="currentlyPlaying.progress"
</div> :isPlaying="currentlyPlaying.isPlaying"
<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>
</div> </div>
</div> </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> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.currentlyPlaying { .currentlyPlaying {
.card-img { .coverGroup {
max-width: 20rem; max-width: 20rem;
} }
} }

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineProps } from "vue"; import { defineProps } from "vue";
import TrackProgressBar from "@/components/TrackProgressBar.vue";
defineProps({ defineProps({
currentlyPlaying: Object, currentlyPlaying: Object,
@ -18,6 +19,11 @@ defineProps({
<div> <div>
{{ currentlyPlaying?.item.artists.map(artist => artist.name).join(', ') }} {{ currentlyPlaying?.item.artists.map(artist => artist.name).join(', ') }}
</div> </div>
<TrackProgressBar
:duration="currentlyPlaying?.item.duration"
:progress="currentlyPlaying.progress"
:isPlaying="currentlyPlaying.isPlaying"
/>
</div> </div>
</div> </div>
</div> </div>

@ -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; loading.value = true;
if (props.throbber) setTimeout(() => { if (props.throbber) setTimeout(() => {
if (loading.value) showThrobber.value = true; if (loading.value) showThrobber.value = true;
}, 250); }, 500);
try { try {
data.value = await (promise.isPromiseList data.value = await (promise.isPromiseList
? Promise.all(promise.promises) ? Promise.all(promise.promises)
: promise); : promise);
} catch (e) { } catch (e) {
error.value = e; error.value = e;
console.error('PR', e);
} finally { } finally {
loading.value = false; loading.value = false;
showThrobber.value = false; showThrobber.value = false;

@ -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>

@ -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 "../node_modules/bootstrap/scss/bootstrap.scss";
//@import "bootstrap-darkmode/scss/darktheme.scss"; @import "../node_modules/bootstrap-darkmode/scss/darktheme.scss";
html, body { html, body {
//background-color: $dark-body-bg;
line-break: loose; line-break: loose;
word-break: break-word; word-break: break-word;
} }

@ -3,6 +3,12 @@ import App from "./App.vue";
import "./registerServiceWorker"; import "./registerServiceWorker";
import router from "./router"; import router from "./router";
import { createApi } from "@/Api"; 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) createApp(App)
.use(router) .use(router)

@ -13,7 +13,7 @@ const userInfo = ref(api?.getUserInfo(route.params.id as string));
let refreshUserInfo = setInterval(() => { let refreshUserInfo = setInterval(() => {
userInfo.value = api?.getUserInfo(route.params.id as string); userInfo.value = api?.getUserInfo(route.params.id as string);
}, 20000); }, 10000);
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearInterval(refreshUserInfo); clearInterval(refreshUserInfo);
@ -43,10 +43,7 @@ onBeforeUnmount(() => {
v-slot="{ data: { user, currentlyPlaying } }" v-slot="{ data: { user, currentlyPlaying } }"
class="row" class="row"
> >
<div v-if="$route.query.embed === 'true'"> <div class="col-md-4">
</div>
<div v-else class="col-md-4">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
{{ user.displayName }} {{ user.displayName }}
@ -80,6 +77,9 @@ onBeforeUnmount(() => {
<div class="col"> <div class="col">
<h2>Currently listening to:</h2> <h2>Currently listening to:</h2>
<CurrentlyPlaying v-if="currentlyPlaying?.item" :currently-playing="currentlyPlaying" /> <CurrentlyPlaying v-if="currentlyPlaying?.item" :currently-playing="currentlyPlaying" />
<p v-else class="alert alert-info">
{{ user.displayName }} is not listening to music.
</p>
</div> </div>
</PromiseResolver> </PromiseResolver>
</div> </div>

Loading…
Cancel
Save