AudioLib/src/App.vue
2019-06-15 20:34:50 +03:00

458 lines
17 KiB
Vue

<template>
<v-app :dark="darkTheme">
<v-toolbar app>
<v-toolbar-title class="headline text-uppercase">
<span v-if="appNet !== ''">{{ appNet }}:</span>
<span class="font-weight-light">{{ appName }}</span>
</v-toolbar-title>
<v-progress-circular
indeterminate
v-if="isLoading"
class="loading-progress"
></v-progress-circular>
<v-spacer></v-spacer>
<v-menu bottom left>
<template v-slot:activator="{ on }">
<v-btn
flat
icon
v-on="on"
>
<v-icon>mdi-bookmark</v-icon>
</v-btn>
</template>
<v-list>
<v-list-tile
@click="setBookmark(false)"
>
<v-list-tile-action>
<v-icon>mdi-bookmark-plus</v-icon>
</v-list-tile-action>
<v-list-tile-title>{{ locale.SetBookmark }}</v-list-tile-title>
</v-list-tile>
<v-list-tile
@click="removeBookmark(false)"
>
<v-list-tile-action>
<v-icon>mdi-bookmark-minus</v-icon>
</v-list-tile-action>
<v-list-tile-title>{{ locale.RemoveBookmark }}</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
<v-menu bottom left>
<template v-slot:activator="{ on }">
<v-btn
flat
icon
v-on="on"
>
<v-icon>more_vert</v-icon>
</v-btn>
</template>
<v-list>
<v-list-tile>
<v-list-tile-action>
<v-switch v-model="darkTheme" color="blue"></v-switch>
</v-list-tile-action>
<v-list-tile-title>{{ locale.DarkTheme }}</v-list-tile-title>
</v-list-tile>
<v-list-tile>
<v-list-tile-action>
<v-switch v-model="autoBookmark" color="blue"></v-switch>
</v-list-tile-action>
<v-list-tile-title>{{ locale.AutoBookmarks }}</v-list-tile-title>
</v-list-tile>
<v-divider></v-divider>
<v-list-tile @click="showAboutDialog">
<v-list-tile-action>
<v-icon>mdi-information-variant</v-icon>
</v-list-tile-action>
<v-list-tile-title>{{ locale.About }}</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</v-toolbar>
<v-content>
<v-tabs
v-model="activeTab"
align-with-title
centered
fixed-tabs
>
<v-tab ripple>{{ locale.Authors }}</v-tab>
<v-tab-item>
<Authors ref="Authors" v-on:author_selected="openBooks" />
</v-tab-item>
<v-tab ripple>{{ locale.Books }}</v-tab>
<v-tab-item>
<Books ref="Books" v-on:book_selected="openBook" />
</v-tab-item>
<v-tab ripple>{{ locale.Book }}</v-tab>
<v-tab-item>
<Book ref="Book" v-on:progress_updated="updateProgress" />
</v-tab-item>
</v-tabs>
</v-content>
<v-card tile class="player-interface">
<v-flex xs12>
<v-slider
v-model="progress"
min="0"
max="100"
height="30px"
:disabled="isOff"
:inverse-label="true"
:label="`${currentTime}/${duration}`"
class="time-slider px-2"
></v-slider>
</v-flex>
<v-list>
<v-list-tile>
<v-list-tile-content v-if="$vuetify.breakpoint.smAndUp">
<v-list-tile-title>{{ authorName }}</v-list-tile-title>
<v-list-tile-sub-title>{{ bookTitle }}<span v-if="currentChapter !== ''"> - {{ currentChapter }}</span></v-list-tile-sub-title>
</v-list-tile-content>
<v-spacer></v-spacer>
<v-list-tile-action @click="prevChapter">
<v-btn icon>
<v-icon>mdi-skip-backward</v-icon>
</v-btn>
</v-list-tile-action>
<v-list-tile-action :class="{ 'mx-5': $vuetify.breakpoint.mdAndUp }">
<v-btn icon @click="toggleStatus">
<v-icon>mdi-{{ isPlaying ? "pause" : "play" }}</v-icon>
</v-btn>
</v-list-tile-action>
<v-list-tile-action
@click="nextChapter"
>
<v-btn icon>
<v-icon>mdi-skip-forward</v-icon>
</v-btn>
</v-list-tile-action>
<v-flex xs6 sm3 md2>
<v-slider
:class="{ 'mr-3 ml-5': $vuetify.breakpoint.mdAndUp, 'ml-3' : $vuetify.breakpoint.mdAndDown }"
:min="0"
:max="10"
v-model="volume"
append-icon="volume_up"
prepend-icon="volume_down"
></v-slider>
</v-flex>
</v-list-tile>
</v-list>
</v-card>
<MessageDialog ref="MessageDialog" />
</v-app>
</template>
<script>
import Authors from './components/Authors'
import Books from './components/Books'
import Book from './components/Book'
import MessageDialog from './components/MessageDialog'
export default {
name: 'App',
components: {
Authors,
Books,
Book,
MessageDialog
},
data () {
return {
appNet: "CAINet",
appName: "Аудиотека",
activeTab: null,
autoBookmark: false,
darkTheme: false,
isLoading: false,
isMounted: false,
volume: 5,
locale: []
}
},
mounted () {
this.isMounted = true;
this.darkTheme = localStorage.darkTheme ? JSON.parse(localStorage.darkTheme) : false;
this.autoBookmark = localStorage.autoBookmark ? JSON.parse(localStorage.autoBookmark) : false;
this.volume = localStorage.volume ? JSON.parse(localStorage.volume) : 5;
this.isLoading = true;
let userLocale = navigator.language || navigator.userLanguage;
this.axios.get("locales/locales.json")
.then(response => {
let localeLoaded = false;
response.data.forEach(element => {
if (element.code == userLocale.toLowerCase()) {
this.loadLocale(element.file);
localeLoaded = true;
}
});
if (!localeLoaded) { this.loadLocale("en"); }
})
.catch(error => {
console.log(error);
this.showMessage("Error", error);
});
this.axios.get("config.json")
.then(response => {
this.appNet = response.data.AppNet;
this.appName = response.data.AppName;
})
.catch(error => {
console.log(error);
this.showMessage(this.locale.Error, error);
});
this.axios.get("lib/authors.json")
.then(response => {
this.$refs.Authors.items = response.data;
this.isLoading = false;
})
.catch(error => {
console.log(error);
this.showMessage(this.locale.Error, error);
});
},
computed: {
authorName () {
return !this.isMounted ? "" : this.$refs.Book.authorName;
},
bookOpened () {
return !this.isMounted ? false : this.$refs.Book.items.length > 0;
},
bookTitle () {
return !this.isMounted ? "" : this.$refs.Book.bookTitle;
},
currentChapter () {
let curChapter = !this.isMounted
? ""
: this.$refs.Book.items[this.$refs.Book.currentChapter];
return curChapter ? curChapter.title : "";
},
currentTime () {
return !this.isMounted ? "00:00" : this.$refs.Book.currentTime;
},
duration () {
return !this.isMounted ? "00:00" : this.$refs.Book.duration;
},
isOff () {
return !this.isMounted ? true : this.$refs.Book.isOff;
},
isPlaying () {
return !this.isMounted ? false : this.$refs.Book.isPlaying;
},
progress: {
get () {
return !this.isMounted ? "" : this.$refs.Book.progress;
},
set (newVal) {
if (newVal !== Math.round(this.$refs.Book.progress)) {
this.$refs.Book.audioElement.currentTime = this.$refs.Book.audioElement.duration * newVal / 100;
}
}
}
},
watch: {
autoBookmark (val) { localStorage.autoBookmark = val; },
darkTheme (val) { localStorage.darkTheme = val; },
volume (val) {
localStorage.volume = val;
this.$refs.Book.volume = val;
}
},
methods: {
getHash (val) {
let CryptoJS = require("crypto-js");
let hash = CryptoJS.MD5(val);
return hash.toString(CryptoJS.enc.Hex);
},
loadLocale (langFile) {
this.axios.get("locales/" + langFile)
.then(response => {
this.locale = response.data;
})
.catch(error => {
console.log(error);
this.showMessage("Error", error);
});
},
nextChapter () { this.$refs.Book.nextChapter(); },
openBook (val) {
this.$refs.Book.authorName = this.$refs.Books.authorName;
this.$refs.Book.authorImg = this.$refs.Books.authorImg;
this.$refs.Book.bookImg = val.image;
this.$refs.Book.bookTitle = val.title;
this.isLoading = true;
this.axios.get(val.book)
.then(response => {
this.$refs.Book.items = response.data;
let bmKey = this.getHash(JSON.stringify(this.$refs.Book.items));
let bookmark = localStorage.getItem(bmKey) ? JSON.parse(localStorage.getItem(bmKey)) : null;
if (bookmark !== null) {
this.$refs.Book.loadChapter(bookmark.chapter, bookmark.progress);
} else {
this.$refs.Book.loadChapter();
}
this.activeTab = 2;
this.isLoading = false;
})
.catch(error => {
this.isLoading = false;
console.log(error);
this.showMessage(this.locale.Error, error);
});
this.activeTab = 2;
},
openBooks (val) {
this.isLoading = true;
this.axios.get(val.books)
.then(response => {
this.$refs.Books.authorName = val.author;
this.$refs.Books.authorImg = val.image;
this.$refs.Books.items = response.data;
this.activeTab = 1;
this.isLoading = false;
})
.catch(error => {
this.isLoading = false;
console.log(error);
this.showMessage(this.locale.Error, error);
});
},
prevChapter () { this.$refs.Book.prevChapter(); },
progressClick (event) {
if (!this.isMounted) { return; }
if (this.$refs.Book.audioElement !== null) {
let elLeft = this.$refs.playProgress.$el.getBoundingClientRect().left;
let elWidth = this.$refs.playProgress.$el.getBoundingClientRect().width;
this.$refs.Book.audioElement.currentTime = this.$refs.Book.audioElement.duration * (event.clientX - elLeft) / elWidth;
}
},
removeBookmark (silent = true) {
if (this.$refs.Book.items.length == 0) { return; }
localStorage.removeItem(this.getHash(this.$refs.Book.items));
if (!silent) { this.showMessage(this.locale.Information, this.locale.BookmarkRemoved); }
},
setBookmark (silent = true) {
if (this.$refs.Book.items.length == 0) { return; }
let data = {
chapter: this.$refs.Book.currentChapter,
progress: this.$refs.Book.progress
};
localStorage.setItem(
this.getHash(JSON.stringify(this.$refs.Book.items)),
JSON.stringify(data)
);
if (!silent) { this.showMessage(this.locale.Information, this.locale.BookmarkSaved); }
},
showAboutDialog () {
let userLocale = navigator.language || navigator.userLanguage;
userLocale = userLocale.indexOf("-") > 0
? userLocale.substring(0, userLocale.indexOf("-")).toLowerCase()
: userLocale.toLowerCase();
let aboutText = "";
if (userLocale == "ru") {
aboutText = "<p><center>Аудиотека</center></p>" +
"<p><center>&copy; Александр Чебыкин, 2019</center></p>" +
"<p><center>Опубликовано под лицензией <a href=\"https://opensource.org/licenses/MIT\" target=\"_blank\">MIT license</a></center></p>" +
"<p><center>Git: <a href=\"https://home.cainet.info:3000/cai/AudioLib\" target=\"_blank\">https://home.cainet.info:3000/cai/AudioLib</a></center></p>";
} else {
aboutText = "<p><center>Audiobooks library</center></p>" +
"<p><center>&copy; Alexander I Chebykin, 2019</center></p>" +
"<p><center>Published under <a href=\"https://opensource.org/licenses/MIT\" target=\"_blank\">MIT license</a></center></p>" +
"<p><center>Git: <a href=\"https://home.cainet.info:3000/cai/AudioLib\" target=\"_blank\">https://home.cainet.info:3000/cai/AudioLib</a></center></p>";
}
this.showMessage(this.locale.About, aboutText);
},
showMessage (title, text) {
this.$refs.MessageDialog.title = title;
this.$refs.MessageDialog.text = text;
this.$refs.MessageDialog.dialog = true;
},
toggleStatus () {
this.$refs.Book.toggleStatus();
if (this.autoBookmark && this.$refs.Book.isPaused) { this.setBookmark(); }
},
updateProgress (val) { this.progress = val; }
}
}
</script>
<style>
.loading-progress { margin: auto 15px; }
.player-interface {
position: fixed;
bottom: 0;
width: 100%;
}
.player-interface .time-slider {
width: 100%;
margin: 0 0 -25px 0;
}
</style>