Started migration to Vue3 + Vuetify3
Started migration to Vue3 + Vuetify3
This commit is contained in:
parent
75990ea5c3
commit
4f27a90e30
@ -1,2 +0,0 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
17
.eslintrc.js
17
.eslintrc.js
@ -1,17 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
'extends': [
|
||||
'plugin:vue/essential',
|
||||
'eslint:recommended'
|
||||
],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
}
|
||||
}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,7 +1,7 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
/public/lib/*
|
||||
/public/lib
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
@ -11,6 +11,7 @@ node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
|
||||
15
README.md
15
README.md
@ -1,4 +1,4 @@
|
||||
# audiobookslibrary
|
||||
# audiolib.vue3
|
||||
|
||||
## Project setup
|
||||
```
|
||||
@ -7,22 +7,17 @@ yarn install
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn run serve
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn run build
|
||||
```
|
||||
|
||||
### Run your tests
|
||||
```
|
||||
yarn run test
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn run lint
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
@ -54,12 +49,14 @@ file format:
|
||||
```
|
||||
[
|
||||
{
|
||||
"cycle": "Books cycle",
|
||||
"title": "Book 1 title",
|
||||
"image": "lib/Author name/Book 1 title/cover.jpg",
|
||||
"book": "lib/Author name/Book 1 title/book.json"
|
||||
},
|
||||
...
|
||||
{
|
||||
"cycle": "Books cycle",
|
||||
"title": "Book N title",
|
||||
"image": "lib/Author name/Book N title/cover.jpg",
|
||||
"book": "lib/Author name/Book N title/book.json"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
|
||||
19
jsconfig.json
Normal file
19
jsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
||||
72
package.json
72
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookslibrary",
|
||||
"version": "0.2.0",
|
||||
"name": "audiolib.vue3",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
@ -8,30 +8,50 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^3.6.95",
|
||||
"axios": "^0.19.0",
|
||||
"core-js": "^2.6.5",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"register-service-worker": "^1.6.2",
|
||||
"sass": "^1.49.11",
|
||||
"sass-loader": "^10",
|
||||
"vue": "^2.6.10",
|
||||
"vue-axios": "^2.1.4",
|
||||
"vue-i18n": "^8.11.2",
|
||||
"vuetify": "^1.5.5"
|
||||
"@mdi/font": "5.9.55",
|
||||
"axios": "^0.26.1",
|
||||
"core-js": "^3.8.3",
|
||||
"crypto-js": "^4.1.1",
|
||||
"roboto-fontface": "*",
|
||||
"vue": "^3.2.13",
|
||||
"vue-axios": "^3.4.1",
|
||||
"vue-i18n": "^9.2.0-beta.34",
|
||||
"vue-router": "^4.0.14",
|
||||
"vuetify": "npm:@vuetify/nightly@next",
|
||||
"vuex": "^4.0.2",
|
||||
"webfontloader": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.8.0",
|
||||
"@vue/cli-plugin-eslint": "^3.8.0",
|
||||
"@vue/cli-plugin-pwa": "^3.8.0",
|
||||
"@vue/cli-service": "^3.8.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.1",
|
||||
"vue-cli-plugin-vuetify": "^0.5.0",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vuetify-loader": "^1.0.5"
|
||||
}
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.12.16",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"sass": "^1.50.0",
|
||||
"sass-loader": "^10.0.0",
|
||||
"vue-cli-plugin-vuetify": "~2.4.8",
|
||||
"vuetify-loader": "^2.0.0-alpha.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"AppNet": "CAINet",
|
||||
"AppName": "Аудиотека"
|
||||
}
|
||||
3
public/data/config.json
Normal file
3
public/data/config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"appNet": "CAINet"
|
||||
}
|
||||
BIN
public/img/library1080.webp
Normal file
BIN
public/img/library1080.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 697 KiB |
@ -1,18 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>img/favicon.ico">
|
||||
<title>CAINet: Аудиотека</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>Извините, данное приложение не работает без JavaScript.</strong><br>
|
||||
<strong>You need JavaScript to run this application.</strong>
|
||||
<hr>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
787
src/App.vue
787
src/App.vue
@ -1,166 +1,126 @@
|
||||
<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-app :theme="theme">
|
||||
<v-img
|
||||
v-bind:src="backgroundImage"
|
||||
width="100vw"
|
||||
height="100vh"
|
||||
cover
|
||||
class="bg-image"
|
||||
></v-img>
|
||||
<v-app-bar app>
|
||||
<v-app-bar-title class="headline text-uppercase" v-if="smAndUp">
|
||||
<span v-if="appData.net !== '' && mdAndUp">{{ appData.net }}:</span>
|
||||
<span class="font-weight-light">{{ appData.name }}</span>
|
||||
</v-app-bar-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>{{ $t("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>{{ $t("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>{{ $t("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>{{ $t("autoBookmarks") }}</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
<v-list-tile>
|
||||
<v-list-tile-action>
|
||||
<v-switch v-model="useWikipedia" color="blue"></v-switch>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-title>{{ $t("useWikipedia") }}</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>{{ $t("about") }}</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-toolbar>
|
||||
|
||||
<v-content>
|
||||
<v-tabs
|
||||
v-model="activeTab"
|
||||
centered
|
||||
fixed-tabs
|
||||
>
|
||||
<v-tab ripple>{{ $t("authors") }}</v-tab>
|
||||
<v-tab-item>
|
||||
<Authors ref="Authors" v-on:author_selected="openBooks" />
|
||||
</v-tab-item>
|
||||
<v-tab ripple>{{ $t("books") }}</v-tab>
|
||||
<v-tab-item>
|
||||
<Books ref="Books" v-on:book_selected="openBook" />
|
||||
</v-tab-item>
|
||||
<v-tab ripple>{{ $t("book") }}</v-tab>
|
||||
<v-tab-item>
|
||||
<Book ref="Book" v-on:progress_updated="updateProgress" />
|
||||
</v-tab-item>
|
||||
<v-tab ripple to="/authors">{{ $t("authors") }}</v-tab>
|
||||
<v-tab ripple to="/books">{{ $t("books") }}</v-tab>
|
||||
<v-tab ripple to="/book">{{ $t("book") }}</v-tab>
|
||||
</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-spacer></v-spacer>
|
||||
|
||||
<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-btn
|
||||
:icon="theme == 'light' ? 'mdi-weather-night' : 'mdi-weather-sunny'"
|
||||
@click="toggleTheme"
|
||||
></v-btn>
|
||||
</v-app-bar>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-progress-linear
|
||||
indeterminate
|
||||
color="blue"
|
||||
v-if="showProgress"
|
||||
></v-progress-linear>
|
||||
|
||||
<v-list-tile-action @click="prevChapter">
|
||||
<v-btn icon>
|
||||
<v-icon>mdi-skip-backward</v-icon>
|
||||
</v-btn>
|
||||
</v-list-tile-action>
|
||||
<v-main>
|
||||
<v-container fluid class="ma-0 pa-0">
|
||||
<transition name="fade">
|
||||
<router-view
|
||||
ref="view"
|
||||
@authorSelected="loadBooks"
|
||||
@bookSelected="loadBook"
|
||||
/>
|
||||
</transition>
|
||||
</v-container>
|
||||
</v-main>
|
||||
|
||||
<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 sm4 md3>
|
||||
<v-footer app height="80" class="pa-0 ma-0">
|
||||
<v-card flat class="player-interface w-100" :class="{ 'solid-bg': smAndDown }">
|
||||
<v-list style="background: rgba(0,0,0,0)" color="rgba(0, 0, 0, 0)">
|
||||
<v-list-item height="32" min-height="32" class="pb-0 pt-8">
|
||||
<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-model="progress"
|
||||
min="0"
|
||||
max="100"
|
||||
dense
|
||||
hide-details
|
||||
:disabled="isOff"
|
||||
:inverse-label="true"
|
||||
:label="`${currentTime ? currentTime : '00:00:00'}/${duration ? duration : '00:00:00'}`"
|
||||
class="time-slider w-100 px-2 py-0 m-0"
|
||||
></v-slider>
|
||||
</v-flex>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-list-item>
|
||||
<v-list-item class="py-0">
|
||||
<v-list-item-content v-if="smAndUp">
|
||||
<v-list-item-title>{{ authorName }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ bookTitle }}<span v-if="currentChapter !== ''"> - {{ currentChapter }}</span></v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-list-item-action class="mx-2">
|
||||
<v-btn
|
||||
flat
|
||||
icon="mdi-skip-backward"
|
||||
@click="prevChapter"
|
||||
>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
|
||||
<v-list-item-action>
|
||||
<v-btn
|
||||
flat
|
||||
icon
|
||||
@click="toggleStatus"
|
||||
>
|
||||
<v-icon>mdi-{{ isPlaying ? "pause" : "play" }}</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
|
||||
<v-list-item-action class="mx-2">
|
||||
<v-btn
|
||||
flat
|
||||
icon="mdi-skip-forward"
|
||||
@click="nextChapter"
|
||||
></v-btn>
|
||||
</v-list-item-action>
|
||||
|
||||
<v-card
|
||||
flat
|
||||
width="200"
|
||||
height="35"
|
||||
class="pr-6"
|
||||
>
|
||||
<v-slider
|
||||
class="w-100"
|
||||
:class="{ 'mr-3': mdAndUp, 'ml-3': mdAndDown }"
|
||||
:min="0"
|
||||
:max="10"
|
||||
hide-details
|
||||
v-model="volume"
|
||||
prepend-icon="mdi-volume-low"
|
||||
append-icon="mdi-volume-high"
|
||||
></v-slider>
|
||||
</v-card>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-footer>
|
||||
<!--
|
||||
<v-fab-transition>
|
||||
<v-btn
|
||||
class="to-top-button"
|
||||
@ -176,154 +136,122 @@
|
||||
<v-icon>keyboard_arrow_up</v-icon>
|
||||
</v-btn>
|
||||
</v-fab-transition>
|
||||
<MessageDialog ref="MessageDialog" />
|
||||
-->
|
||||
<message-box ref="MessageBox"/>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Authors from "./components/Authors"
|
||||
import Books from "./components/Books"
|
||||
import Book from "./components/Book"
|
||||
import MessageDialog from "./components/MessageDialog"
|
||||
//import { ref, onMounted } from "vue";
|
||||
import { ref } from "vue";
|
||||
import { useDisplay } from "vuetify";
|
||||
import MessageBox from "./components/MessageBox";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
|
||||
components: {
|
||||
Authors,
|
||||
Books,
|
||||
Book,
|
||||
MessageDialog
|
||||
MessageBox,
|
||||
},
|
||||
|
||||
setup () {
|
||||
const theme = ref("light");
|
||||
const { smAndDown, smAndUp, mdAndUp, mdAndDown } = useDisplay();
|
||||
|
||||
/*
|
||||
onMounted (() => {
|
||||
console.log("on mounted");
|
||||
})
|
||||
*/
|
||||
|
||||
return {
|
||||
theme,
|
||||
smAndDown,
|
||||
smAndUp,
|
||||
mdAndUp,
|
||||
mdAndDown,
|
||||
setTheme: (newTheme) => theme.value = newTheme === "light" || newTheme === "dark" ? newTheme : "light",
|
||||
toggleTheme: () => theme.value = theme.value === "light" ? "dark" : "light",
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
appNet: "CAINet",
|
||||
appName: "Аудиотека",
|
||||
activeTab: 0,
|
||||
appData: { name: "", net: "" },
|
||||
backgroundImage: this.defaultBackground,
|
||||
|
||||
activeTab: null,
|
||||
autoBookmark: false,
|
||||
darkTheme: false,
|
||||
isLoading: false,
|
||||
isMounted: false,
|
||||
locale: { flag: "ru", language: "ru", title: "Русский" },
|
||||
locales: [
|
||||
{ flag: "us", language: "en", title: "English" },
|
||||
{ flag: "ru", language: "ru", title: "Русский" }
|
||||
],
|
||||
scrolled: false,
|
||||
useWikipedia: false,
|
||||
volume: 5//,
|
||||
//locale: []
|
||||
}
|
||||
},
|
||||
authors: [],
|
||||
|
||||
created () {
|
||||
window.addEventListener("scroll", this.onScroll);
|
||||
},
|
||||
audioElement: null,
|
||||
status: 0,
|
||||
currentChapter: null,
|
||||
volume: 5,
|
||||
|
||||
mounted () {
|
||||
this.isMounted = true;
|
||||
this.darkTheme = localStorage.darkTheme
|
||||
? JSON.parse(localStorage.darkTheme)
|
||||
: false;
|
||||
this.autoBookmark = localStorage.autoBookmark
|
||||
? JSON.parse(localStorage.autoBookmark)
|
||||
: false;
|
||||
this.useWikipedia = localStorage.useWikipedia
|
||||
? JSON.parse(localStorage.useWikipedia)
|
||||
: false;
|
||||
this.volume = localStorage.volume
|
||||
? JSON.parse(localStorage.volume)
|
||||
: 5;
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
this.axios.get(`config.json?r=${Math.random()}`)
|
||||
.then(response => {
|
||||
this.appNet = response.data.AppNet;
|
||||
this.appName = response.data.AppName;
|
||||
})
|
||||
.catch(error => {
|
||||
this.showMessage(this.$t("error"), error);
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
this.axios.get(`lib/authors.json?r=${Math.random()}`)
|
||||
.then(response => {
|
||||
this.$refs.Authors.items = response.data;
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.showMessage(this.$t("error"), error);
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
|
||||
destroyed () {
|
||||
window.removeEventListener("scroll", this.onScroll);
|
||||
},
|
||||
|
||||
computed: {
|
||||
authorName () {
|
||||
return this.isMounted ? this.$refs.Book.authorName : "";
|
||||
},
|
||||
|
||||
bookOpened () {
|
||||
return this.isMounted ? this.$refs.Book.items.length > 0 : false;
|
||||
},
|
||||
|
||||
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 ? this.$refs.Book.currentTime : "00:00";
|
||||
},
|
||||
|
||||
duration () {
|
||||
return this.isMounted ? this.$refs.Book.duration : "00:00";
|
||||
},
|
||||
|
||||
isOff () {
|
||||
return this.isMounted ? this.$refs.Book.isOff : true;
|
||||
},
|
||||
|
||||
isPlaying () {
|
||||
return this.isMounted ? this.$refs.Book.isPlaying : false;
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
scrolled: false,
|
||||
useWikipedia: true,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
autoBookmark (val) { localStorage.autoBookmark = val; },
|
||||
darkTheme (val) { localStorage.darkTheme = val; },
|
||||
useWikipedia (val) { localStorage.useWikipedia = val; },
|
||||
volume (val) {
|
||||
localStorage.volume = val;
|
||||
this.$refs.Book.volume = val;
|
||||
"$route" (to) {
|
||||
if (to.path == "/authors") {
|
||||
this.activeTab = this.tabs.authors;
|
||||
} else if (to.path == "/books") {
|
||||
this.activeTab = this.tabs.books;
|
||||
} else if (to.path == "/book") {
|
||||
this.activeTab = this.tabs.book;
|
||||
}
|
||||
},
|
||||
|
||||
volume () { this.updateVolume(); }
|
||||
},
|
||||
|
||||
computed: {
|
||||
isOff () {
|
||||
return this.audioElement == null;
|
||||
},
|
||||
|
||||
isPaused () {
|
||||
return this.status === this.statuses.paused;
|
||||
},
|
||||
|
||||
isPlaying () {
|
||||
return this.status === this.statuses.playing;
|
||||
},
|
||||
|
||||
isChapterLoaded () {
|
||||
return (this.currentChapter !== null) && this.audioElement;
|
||||
},
|
||||
|
||||
showProgress () {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.appData.name = this.$t("appName");
|
||||
this.appData.net = this.appConfig.appNet;
|
||||
|
||||
if (this.$router.currentRoute._value.path == "/authors") {
|
||||
this.activeTab = this.tabs.authors;
|
||||
} else if (this.$router.currentRoute._value.path == "/books") {
|
||||
this.activeTab = this.tabs.books;
|
||||
} else if (this.$router.currentRoute._value.path == "/book") {
|
||||
this.activeTab = this.tabs.book;
|
||||
}
|
||||
|
||||
//window.addEventListener("scroll", this.onScroll);
|
||||
|
||||
this.loadAuthors();
|
||||
},
|
||||
|
||||
unmounted () {
|
||||
//window.removeEventListener("scroll", this.onScroll);
|
||||
},
|
||||
|
||||
methods: {
|
||||
fillWikiInfo (searchText, fillAuthorInfo = true) {
|
||||
this.axios.get(`https://${this.locale.language}.wikipedia.org/w/api.php`, {
|
||||
this.axios.get(`https://${this.$i18n.locale}.wikipedia.org/w/api.php`, {
|
||||
params: {
|
||||
format: "json",
|
||||
action: "query",
|
||||
@ -341,201 +269,208 @@
|
||||
].extract;
|
||||
|
||||
if (fillAuthorInfo) {
|
||||
this.$refs.Books.wikiText = result;
|
||||
let authorInfo = this.$store.getters.author;
|
||||
authorInfo.wiki = result;
|
||||
this.$store.commit("setAuthor", authorInfo);
|
||||
} else {
|
||||
this.$refs.Book.wikiText = result;
|
||||
let bookInfo = this.$store.getters.book;
|
||||
bookInfo.wiki = result;
|
||||
this.$store.commit("setBook", bookInfo);
|
||||
}
|
||||
} else {
|
||||
if (fillAuthorInfo) {
|
||||
this.$refs.Books.wikiText = "";
|
||||
let authorInfo = this.$store.getters.author;
|
||||
authorInfo.wiki = "";
|
||||
this.$store.commit("setAuthor", authorInfo);
|
||||
} else {
|
||||
this.$refs.Book.wikiText = "";
|
||||
let bookInfo = this.$store.getters.book;
|
||||
bookInfo.wiki = "";
|
||||
this.$store.commit("setBook", bookInfo);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.isLoading = false;
|
||||
|
||||
if (fillAuthorInfo) {
|
||||
this.$refs.Books.wikiText = "";
|
||||
let authorInfo = this.$store.getters.author;
|
||||
authorInfo.wiki = "";
|
||||
this.$store.commit("setAuthor", authorInfo);
|
||||
} else {
|
||||
this.$refs.Book.wikiText = "";
|
||||
let bookInfo = this.$store.getters.book;
|
||||
bookInfo.wiki = "";
|
||||
this.$store.commit("setBook", bookInfo);
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
|
||||
getHash (val) {
|
||||
let CryptoJS = require("crypto-js");
|
||||
let hash = CryptoJS.MD5(val);
|
||||
let hash = CryptoJS.MD5(val);
|
||||
|
||||
return hash.toString(CryptoJS.enc.Hex);
|
||||
},
|
||||
|
||||
nextChapter () {
|
||||
this.$refs.Book.nextChapter();
|
||||
loadAuthors () {
|
||||
this.axios.get(`lib/authors.json?r=${Math.random()}`)
|
||||
.then(response => {
|
||||
this.$store.commit("setAuthors", response.data);
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.showMessage(this.$t("error"), error);
|
||||
console.error(error);
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Load book data
|
||||
*
|
||||
* @param {object} bookData Book data
|
||||
*/
|
||||
loadBook (bookData) {
|
||||
let author = this.$store.getters.author;
|
||||
|
||||
this.$store.commit("setBook", {
|
||||
author: author.name,
|
||||
photo: author.image,
|
||||
title: bookData.title,
|
||||
cover: bookData.image,
|
||||
wiki: ""
|
||||
});
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
this.axios.get(`${bookData.book}?r=${Math.random()}`)
|
||||
.then(response => {
|
||||
this.$store.commit("setChapters", response.data);
|
||||
|
||||
let bmKey = this.getHash(JSON.stringify(this.$store.getters.chapters));
|
||||
let bookmark = localStorage.getItem(bmKey) ? JSON.parse(localStorage.getItem(bmKey)) : null;
|
||||
|
||||
console.log(bmKey, bookmark);
|
||||
|
||||
if (this.useWikipedia) {
|
||||
this.fillWikiInfo(bookData.title, false);
|
||||
}
|
||||
|
||||
this.backgroundImage = bookData.image;
|
||||
this.$router.push({ path: "/book" });
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.isLoading = false;
|
||||
this.showMessage(this.$t("error"), error);
|
||||
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Load books data
|
||||
*
|
||||
* @param {object} authorData Author data
|
||||
*/
|
||||
loadBooks (authorData) {
|
||||
this.isLoading = true;
|
||||
|
||||
this.axios.get(`${authorData.books}?r=${Math.random()}`)
|
||||
.then(response => {
|
||||
let books = {};
|
||||
|
||||
response.data.forEach((book) => {
|
||||
let cycle = book.cycle !== undefined ? book.cycle : "no_cycle";
|
||||
|
||||
if (books[cycle] == undefined) {
|
||||
books[cycle] = [];
|
||||
}
|
||||
|
||||
books[cycle].push(book);
|
||||
});
|
||||
|
||||
this.$store.commit("setAuthor", {
|
||||
name: authorData.author || "",
|
||||
image: authorData.image || "",
|
||||
wiki: ""
|
||||
});
|
||||
|
||||
this.$store.commit("setBooks", books); // response.data);
|
||||
|
||||
if (this.useWikipedia) { this.fillWikiInfo(authorData.author); }
|
||||
|
||||
this.$router.push({ path: "/books" });
|
||||
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.isLoading = false;
|
||||
this.showMessage(this.$t("error"), error);
|
||||
|
||||
console.error(error);
|
||||
})
|
||||
},
|
||||
|
||||
onScroll () {
|
||||
this.scrolled = window.scrollY > 0;
|
||||
},
|
||||
|
||||
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}?r=${Math.random()}`)
|
||||
.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();
|
||||
}
|
||||
|
||||
if (this.useWikipedia) {
|
||||
this.fillWikiInfo(val.title, false);
|
||||
}
|
||||
this.activeTab = 2;
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.isLoading = false;
|
||||
this.showMessage(this.$t("error"), error);
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
|
||||
openBooks (val) {
|
||||
this.isLoading = true;
|
||||
|
||||
this.axios.get(`${val.books}?r=${Math.random()}`)
|
||||
.then(response => {
|
||||
this.$refs.Books.authorName = val.author;
|
||||
this.$refs.Books.authorImg = val.image;
|
||||
this.$refs.Books.items = response.data;
|
||||
|
||||
if (this.useWikipedia) {
|
||||
this.fillWikiInfo(val.author);
|
||||
}
|
||||
|
||||
this.activeTab = 1;
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.isLoading = false;
|
||||
this.showMessage(this.$t("error"), error);
|
||||
console.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.$t("information"), this.$t("bookmarkRemoved")); }
|
||||
},
|
||||
|
||||
scrollToTop () {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: "smooth"
|
||||
});
|
||||
},
|
||||
|
||||
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.$t("information"), this.$t("bookmarkSaved")); }
|
||||
},
|
||||
|
||||
setLocale (locale) {
|
||||
this.locales.forEach(element => {
|
||||
if (element.language == locale) {
|
||||
this.locale = element;
|
||||
}
|
||||
});
|
||||
this.$i18n.locale = locale;
|
||||
},
|
||||
|
||||
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>© Александр Чебыкин, 2019-2022</center></p>" +
|
||||
"<p><center>Опубликовано под лицензией <a href=\"https://opensource.org/licenses/MIT\" target=\"_blank\">MIT license</a></center></p>" +
|
||||
"<p><center>Git: <a href=\"https://www.cainet.info/git/cai/AudioLib\" target=\"_blank\">https://home.cainet.info/git/cai/AudioLib</a></center></p>";
|
||||
} else {
|
||||
aboutText = "<p><center>Audiobooks library</center></p>" +
|
||||
"<p><center>© Alexander I Chebykin, 2019-2022</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://www.cainet.info/git/cai/AudioLib\" target=\"_blank\">https://www.cainet.info/git/cai/AudioLib</a></center></p>";
|
||||
}
|
||||
this.showMessage(this.$t("about"), aboutText);
|
||||
},
|
||||
|
||||
showMessage (title, text) {
|
||||
this.$refs.MessageDialog.title = title;
|
||||
this.$refs.MessageDialog.text = text;
|
||||
this.$refs.MessageDialog.dialog = true;
|
||||
this.$refs.MessageBox.title = title;
|
||||
this.$refs.MessageBox.text = text;
|
||||
this.$refs.MessageBox.dialog = true;
|
||||
},
|
||||
|
||||
toggleStatus () {
|
||||
this.$refs.Book.toggleStatus();
|
||||
if (this.autoBookmark && this.$refs.Book.isPaused) { this.setBookmark(); }
|
||||
},
|
||||
|
||||
updateProgress (val) { this.progress = val; }
|
||||
updateVolume () {
|
||||
this.audioElement ? this.audioElement.volume = (this.volume / 10) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
.bg-image
|
||||
position: absolute
|
||||
filter: blur(10px) opacity(30%) !important
|
||||
img
|
||||
opacity: 100% !important
|
||||
|
||||
.loading-progress
|
||||
margin: auto 15px
|
||||
|
||||
.player-interface
|
||||
position: fixed
|
||||
position: fixed !important
|
||||
bottom: 0
|
||||
width: 100%
|
||||
|
||||
.time-slider
|
||||
width: 100%
|
||||
margin: 0 0 -25px 0
|
||||
margin-top: -25px
|
||||
|
||||
.to-top-button
|
||||
margin-bottom: 80px
|
||||
|
||||
footer
|
||||
background: transparent !important
|
||||
|
||||
.v-application.v-theme--
|
||||
&light
|
||||
header, footer > .v-card.player-interface
|
||||
background: rgba(255, 255, 255, .5)
|
||||
footer > .v-card.player-interface.solid-bg
|
||||
background: #fff
|
||||
&dark
|
||||
header, footer > .v-card.player-interface
|
||||
background: rgba(15, 15,15, .5)
|
||||
footer > .v-card.player-interface.solid-bg
|
||||
background: #000
|
||||
|
||||
.content-container
|
||||
height: calc(100vh - 160px)
|
||||
overflow-y: auto
|
||||
overflow-x: hidden
|
||||
|
||||
/* vuetify bugfix */
|
||||
.v-toolbar__content
|
||||
.v-tabs
|
||||
.v-btn
|
||||
height: inherit
|
||||
</style>
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-card flat>
|
||||
<v-container
|
||||
fluid
|
||||
grid-list-lg
|
||||
pb-5
|
||||
class="authors-container"
|
||||
>
|
||||
<v-layout row wrap pb-5>
|
||||
<v-flex
|
||||
v-for="(item, index) in items"
|
||||
v-bind:key="index"
|
||||
xs12 sm5 md3 lg2
|
||||
my-1
|
||||
>
|
||||
<v-card
|
||||
hover
|
||||
ripple
|
||||
@click="authorClick(item)"
|
||||
>
|
||||
<v-layout>
|
||||
<v-flex xs5>
|
||||
<v-img
|
||||
:src="item.image"
|
||||
height="95px"
|
||||
width="95px"
|
||||
contain
|
||||
class="author-image ml-2"
|
||||
>
|
||||
</v-img>
|
||||
</v-flex>
|
||||
<v-flex xs7>
|
||||
<v-card-title>
|
||||
<div>
|
||||
<div>{{ item.author }}</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
items: []
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
authorClick (val) { this.$emit("author_selected", val); }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.author-image
|
||||
border-radius: 125px
|
||||
border: solid #fff 2px
|
||||
box-shadow: 0px 3px 1px -2px rgba(0,0,0,0.2), 0px 2px 2px 0px rgba(0,0,0,0.14), 0px 1px 5px 0px rgba(0,0,0,0.12)
|
||||
|
||||
.authors-container
|
||||
min-height: calc(100vh - 190px)
|
||||
</style>
|
||||
90
src/components/AuthorsLib.vue
Normal file
90
src/components/AuthorsLib.vue
Normal file
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<v-card
|
||||
flat
|
||||
tile
|
||||
color="rgba(0, 0, 0, 0)"
|
||||
class="d-flex align-content-start flex-wrap content-container mx-0 pt-2 px-2"
|
||||
>
|
||||
<v-row no-gutters>
|
||||
<v-col
|
||||
v-for="(item, index) in $store.getters.authors"
|
||||
:key="index"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
lg="3"
|
||||
xl="2"
|
||||
>
|
||||
<v-card
|
||||
hover
|
||||
ripple
|
||||
outlined
|
||||
tile
|
||||
height="127"
|
||||
class="pa-2 ma-2"
|
||||
@click="authorClick(item)"
|
||||
>
|
||||
<v-row
|
||||
no-gutters
|
||||
style="flex-wrap: nowrap;"
|
||||
>
|
||||
<v-col
|
||||
cols="5"
|
||||
style="min-width: 127px; max-width: 127px;"
|
||||
class="flex-grow-0 flex-shrink-0"
|
||||
>
|
||||
<v-img
|
||||
:src="item.image"
|
||||
height="95px"
|
||||
width="95px"
|
||||
contain
|
||||
class="author-image ma-2"
|
||||
>
|
||||
</v-img>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="1"
|
||||
class="flex-grow-1 flex-shrink-0"
|
||||
style="min-width: 80px; max-width: 100%"
|
||||
>
|
||||
<div class="author-name">
|
||||
<span class=" text-center font-weight-medium">{{ item.author }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AuthorsLib",
|
||||
|
||||
data: () => ({
|
||||
}),
|
||||
|
||||
methods: {
|
||||
authorClick (author) {
|
||||
this.$emit("authorSelected", author);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.author
|
||||
&-image
|
||||
border-radius: 125px
|
||||
border: solid #fff 2px
|
||||
box-shadow: 0px 3px 1px -2px rgba(0,0,0,0.2), 0px 2px 2px 0px rgba(0,0,0,0.14), 0px 1px 5px 0px rgba(0,0,0,0.12)
|
||||
&-name
|
||||
height: 100%
|
||||
line-height: 100px
|
||||
text-align: center
|
||||
> span
|
||||
display: inline-block
|
||||
vertical-align: middle
|
||||
line-height: normal
|
||||
</style>
|
||||
@ -1,228 +0,0 @@
|
||||
<template>
|
||||
<v-card flat>
|
||||
<v-container bg fill-height grid-list-lg pb-5>
|
||||
<v-layout row wrap pb-5 class="book-layout">
|
||||
<v-flex xs12 sm6 lg4>
|
||||
<v-card flat class="text-xs-center">
|
||||
<v-layout d-inline-block mb-2>
|
||||
<div class="d-inline-block f-left">
|
||||
<v-img
|
||||
v-if="authorImg !== ''"
|
||||
:src="authorImg"
|
||||
height="55px"
|
||||
width="55px"
|
||||
contain
|
||||
class="author-image ml-2"
|
||||
>
|
||||
</v-img>
|
||||
</div>
|
||||
<div class="d-inline-block">
|
||||
<v-card-title>
|
||||
<div>{{ authorName }}</div>
|
||||
</v-card-title>
|
||||
</div>
|
||||
</v-layout>
|
||||
</v-card>
|
||||
<v-flex xs12 text-xs-center>
|
||||
<v-img
|
||||
:src="bookImg"
|
||||
height="200px"
|
||||
width="200px"
|
||||
contain
|
||||
class="d-inline-block ml-2"
|
||||
>
|
||||
</v-img>
|
||||
</v-flex>
|
||||
<v-flex xs12 text-xs-center>{{ bookTitle }}</v-flex>
|
||||
<v-flex xs12 v-if="wikiText !== ''">
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-content>
|
||||
<template v-slot:header>
|
||||
<div>Wikipedia</div>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-text>{{ wikiText }}</v-card-text>
|
||||
</v-card>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
|
||||
</v-flex>
|
||||
</v-flex>
|
||||
<v-flex xs12 sm6>
|
||||
<v-list>
|
||||
<v-list-tile
|
||||
v-for="(item, index) in items"
|
||||
v-bind:key="index"
|
||||
@click="loadChapter(index, 0, true)"
|
||||
>
|
||||
<v-list-tile-action>
|
||||
<v-icon
|
||||
v-if="index == currentChapter"
|
||||
>mdi-play</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title v-text="item.title"></v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
audioElement: null,
|
||||
authorName: "",
|
||||
authorImg: "",
|
||||
bookImg: "",
|
||||
bookTitle: "",
|
||||
currentChapter: 0,
|
||||
currentTime: "00:00",
|
||||
duration: "00:00",
|
||||
items: [],
|
||||
progress: 0,
|
||||
status: 0, //this.statuses.stopped,
|
||||
volume: 5,
|
||||
wikiText: ""
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isOff () {
|
||||
return this.audioElement == null;
|
||||
},
|
||||
|
||||
isPaused () {
|
||||
return this.status === this.statuses.paused;
|
||||
},
|
||||
|
||||
isPlaying () {
|
||||
return this.status === this.statuses.playing;
|
||||
},
|
||||
|
||||
isChapterLoaded () {
|
||||
return (this.currentChapter !== null) && this.audioElement;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
//volume (val) { this.updateVolume(); }
|
||||
volume () { this.updateVolume(); }
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadChapter (index = 0, progress = 0, autoplay = false) {
|
||||
if (this.audioElement) { this.audioElement.pause(); }
|
||||
if (index >= this.items.length) { return false; } // show message?
|
||||
|
||||
this.currentChapter = index;
|
||||
this.audioElement = new Audio(encodeURI(this.items[index].url));
|
||||
this.updateVolume();
|
||||
this.status = this.statuses.stopped;
|
||||
this.audioElement.addEventListener("ended", this.loadNextChapter);
|
||||
|
||||
this.audioElement.ontimeupdate = this.updateProgress;
|
||||
|
||||
let bookInstance = this;
|
||||
|
||||
this.audioElement.addEventListener("durationchange", function () {
|
||||
let min = parseInt(bookInstance.audioElement.duration / 60) < 10
|
||||
? "0" + parseInt(bookInstance.audioElement.duration / 60)
|
||||
: parseInt(bookInstance.audioElement.duration / 60);
|
||||
let sec = parseInt(bookInstance.audioElement.duration % 60) < 10
|
||||
? "0" + parseInt(bookInstance.audioElement.duration % 60)
|
||||
: parseInt(bookInstance.audioElement.duration % 60);
|
||||
bookInstance.duration = min + ":" + sec;
|
||||
});
|
||||
|
||||
this.audioElement.addEventListener("canplay", function _listener () {
|
||||
bookInstance.audioElement.currentTime = bookInstance.audioElement.duration * progress / 100;
|
||||
bookInstance.audioElement.removeEventListener("canplay", _listener, true);
|
||||
}, true);
|
||||
|
||||
if (autoplay) this.play();
|
||||
},
|
||||
|
||||
loadNextChapter (autoplay = true) {
|
||||
this.currentChapter++;
|
||||
if (this.currentChapter >= this.items.length) {
|
||||
this.currentChapter--;
|
||||
return false;
|
||||
}
|
||||
this.loadChapter(this.currentChapter, 0, autoplay);
|
||||
},
|
||||
|
||||
nextChapter () {
|
||||
if (this.currentChapter < this.items.length - 1) {
|
||||
this.currentChapter++;
|
||||
this.loadChapter(this.currentChapter, 0, true);
|
||||
}
|
||||
},
|
||||
|
||||
pause () {
|
||||
this.status = this.statuses.paused;
|
||||
this.audioElement.pause();
|
||||
},
|
||||
|
||||
play () {
|
||||
this.status = this.statuses.playing;
|
||||
this.audioElement.play();
|
||||
},
|
||||
|
||||
prevChapter () {
|
||||
if (this.currentChapter > 0) {
|
||||
this.currentChapter--;
|
||||
this.loadChapter(this.currentChapter, 0, true);
|
||||
}
|
||||
},
|
||||
|
||||
toggleStatus () {
|
||||
if (!this.isChapterLoaded) {
|
||||
this.loadChapter(this.currentChapter || 0);
|
||||
}
|
||||
|
||||
if (!this.isPlaying) {
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
}
|
||||
},
|
||||
|
||||
updateProgress () {
|
||||
if (!this.audioElement || !this.audioElement.currentTime) {
|
||||
return this.progress = 0;
|
||||
}
|
||||
|
||||
this.progress = (this.audioElement.currentTime / this.audioElement.duration) * 100;
|
||||
|
||||
let min = parseInt(this.audioElement.currentTime / 60) < 10
|
||||
? "0" + parseInt(this.audioElement.currentTime / 60)
|
||||
: parseInt(this.audioElement.currentTime / 60);
|
||||
let sec = parseInt(this.audioElement.currentTime % 60) < 10
|
||||
? "0" + parseInt(this.audioElement.currentTime % 60)
|
||||
: parseInt(this.audioElement.currentTime % 60);
|
||||
this.currentTime = min + ":" + sec;
|
||||
},
|
||||
|
||||
updateVolume () {
|
||||
this.audioElement ? this.audioElement.volume = (this.volume / 10) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.author-image
|
||||
display: inline-block
|
||||
border-radius: 55px
|
||||
|
||||
.book-layout
|
||||
min-height: calc(100vh - 190px)
|
||||
|
||||
.f-left
|
||||
float: left
|
||||
</style>
|
||||
150
src/components/BookReader.vue
Normal file
150
src/components/BookReader.vue
Normal file
@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<v-card
|
||||
flat
|
||||
tile
|
||||
color="rgba(0, 0, 0, 0)"
|
||||
class="d-flex align-content-start flex-wrap content-container mx-0 pa-2"
|
||||
>
|
||||
<v-row
|
||||
no-gutters
|
||||
class="justify-center"
|
||||
>
|
||||
<v-col
|
||||
style="min-width: 316px; max-width: 316px;"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="5"
|
||||
lg="4"
|
||||
>
|
||||
<v-card
|
||||
flat
|
||||
tile
|
||||
width="298"
|
||||
height="127"
|
||||
color="rgba(0, 0, 0, 0)"
|
||||
class="pa-2 ma-2 d-flex"
|
||||
v-if="$store.getters.book.author.trim() !== ''"
|
||||
>
|
||||
<v-row
|
||||
no-gutters
|
||||
style="flex-wrap: nowrap;"
|
||||
>
|
||||
<v-col cols="5">
|
||||
<v-img
|
||||
:src="$store.getters.book.photo"
|
||||
height="95px"
|
||||
width="95px"
|
||||
contain
|
||||
class="author-image ma-2"
|
||||
>
|
||||
</v-img>
|
||||
</v-col>
|
||||
<v-col cols="7" class="px-2">
|
||||
<div class="author-name">
|
||||
<span class="font-weight-medium">{{ $store.getters.book.author }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-divider class="mt-4"></v-divider>
|
||||
</v-row>
|
||||
</v-card>
|
||||
|
||||
<v-row no-gutters>
|
||||
<v-col cols="12" class="d-flex justify-center" v-if="$store.getters.book.cover.trim() !== ''">
|
||||
<v-img
|
||||
:src="$store.getters.book.cover"
|
||||
height="200px"
|
||||
width="200px"
|
||||
contain
|
||||
class="d-inline-block ml-2"
|
||||
>
|
||||
</v-img>
|
||||
</v-col>
|
||||
<v-col cols="12" class="d-flex justify-center mt-2" v-if="$store.getters.book.title.trim() !== ''">{{ $store.getters.book.title }}</v-col>
|
||||
<v-col cols="12" class="d-flex justify-center mt-4" v-if="$store.getters.book.wiki.trim() !== ''">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-title>
|
||||
Wikipedia
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
{{ $store.getters.book.wiki }}
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="8"
|
||||
md="7"
|
||||
lg="6"
|
||||
class="flex-grow-1 flex-shrink-0 pa-2"
|
||||
v-if="illustrated"
|
||||
style="background-color: yellow"
|
||||
>
|
||||
2
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="7"
|
||||
:md="illustrated ? 3 : 6"
|
||||
:lg="illustrated ? 2 : 5"
|
||||
class="flex-grow-1 flex-shrink-0 pa-2"
|
||||
>
|
||||
<v-list class="transparent-bg">
|
||||
<v-list-item
|
||||
v-for="(item, index) in $store.getters.chapters"
|
||||
:key="index"
|
||||
:prepend-icon="index == currentChapter ? 'mdi-play' : ''"
|
||||
:title='item.title'
|
||||
:class="index !== currentChapter ? 'no-selection-pl' : ''"
|
||||
@click="loadChapter(index, 0, true)"
|
||||
></v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useDisplay } from "vuetify";
|
||||
|
||||
export default {
|
||||
name: "BookReader",
|
||||
|
||||
setup () {
|
||||
const { smAndDown, smAndUp, xs, md, lg, xl } = useDisplay();
|
||||
|
||||
return {
|
||||
smAndDown, smAndUp, xs, md, lg, xl
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
illustrated: false,
|
||||
currentChapter: 0
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.author
|
||||
&-image
|
||||
border-radius: 125px
|
||||
border: solid #fff 2px
|
||||
box-shadow: 0px 3px 1px -2px rgba(0,0,0,0.2), 0px 2px 2px 0px rgba(0,0,0,0.14), 0px 1px 5px 0px rgba(0,0,0,0.12)
|
||||
&-name
|
||||
height: 100%
|
||||
line-height: 100px
|
||||
> span
|
||||
display: inline-block
|
||||
vertical-align: middle
|
||||
line-height: normal
|
||||
|
||||
.transparent-bg
|
||||
background: transparent
|
||||
|
||||
.v-list-item.no-selection-pl
|
||||
padding-left: 74px
|
||||
</style>
|
||||
@ -1,111 +0,0 @@
|
||||
<template>
|
||||
<v-card flat>
|
||||
<v-container bg grid-list-md>
|
||||
<v-layout row wrap align-top>
|
||||
<v-flex>
|
||||
<v-img
|
||||
v-if="authorImg !== ''"
|
||||
:src="authorImg"
|
||||
height="55px"
|
||||
width="55px"
|
||||
contain
|
||||
class="author-image ml-2"
|
||||
>
|
||||
</v-img>
|
||||
<div
|
||||
class="author-name font-weight-black"
|
||||
>
|
||||
{{ authorName }}
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs12 sm6 v-if="wikiText !== ''">
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-content>
|
||||
<template v-slot:header>
|
||||
<div>Wikipedia</div>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-text>{{ wikiText }}</v-card-text>
|
||||
</v-card>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
||||
<v-container
|
||||
fluid
|
||||
fill-height
|
||||
grid-list-lg
|
||||
pb-5
|
||||
>
|
||||
<v-layout row wrap pb-5 class="books-layout">
|
||||
<v-flex
|
||||
v-for="(item, index) in items"
|
||||
v-bind:key="index"
|
||||
xs12 sm6 md4 lg3
|
||||
my-1
|
||||
>
|
||||
<v-card
|
||||
hover
|
||||
ripple
|
||||
@click="bookClick(item)"
|
||||
>
|
||||
<v-layout>
|
||||
<v-flex xs5>
|
||||
<v-img
|
||||
:src="item.image"
|
||||
height="95px"
|
||||
width="95px"
|
||||
contain
|
||||
class="ml-2"
|
||||
>
|
||||
</v-img>
|
||||
</v-flex>
|
||||
<v-flex xs7>
|
||||
<v-card-title>
|
||||
<div>
|
||||
<div>{{ item.title }}</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
authorName: "",
|
||||
authorImg: "",
|
||||
items: [],
|
||||
wikiText: ""
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
bookClick (val) {
|
||||
this.$emit("book_selected", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.author-image
|
||||
display: inline-block
|
||||
border-radius: 55px
|
||||
|
||||
.author-name
|
||||
display: inline-block
|
||||
position: absolute
|
||||
margin: 15px
|
||||
|
||||
.books-layout
|
||||
min-height: calc(100vh - 250px)
|
||||
</style>
|
||||
190
src/components/BooksLib.vue
Normal file
190
src/components/BooksLib.vue
Normal file
@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<v-card
|
||||
flat
|
||||
color="rgba(0, 0, 0, 0)"
|
||||
class="content-container mx-0 pt-2 px-2"
|
||||
>
|
||||
<v-row
|
||||
no-gutters
|
||||
v-if="$store.getters.author.name.trim() !== ''"
|
||||
>
|
||||
<v-col
|
||||
style="min-width: 316px; max-width: 316px;"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
lg="3"
|
||||
xl="2"
|
||||
>
|
||||
<v-card
|
||||
flat
|
||||
tile
|
||||
width="298"
|
||||
height="127"
|
||||
color="rgba(0, 0, 0, 0)"
|
||||
class="pa-2 ma-2 d-flex"
|
||||
>
|
||||
<v-row
|
||||
no-gutters
|
||||
style="flex-wrap: nowrap;"
|
||||
>
|
||||
<v-col cols="5">
|
||||
<v-img
|
||||
:src="$store.getters.author.image"
|
||||
height="95px"
|
||||
width="95px"
|
||||
contain
|
||||
class="author-image ma-2"
|
||||
>
|
||||
</v-img>
|
||||
</v-col>
|
||||
<v-col cols="7" class="px-2">
|
||||
<div class="author-name">
|
||||
<span class="font-weight-medium">{{ $store.getters.author.name }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="1"
|
||||
style="min-width: 100px; max-width: 100%;"
|
||||
class="flex-grow-1 flex-shrink-0 pa-2"
|
||||
v-if="$store.getters.author.wiki.trim() !== ''"
|
||||
>
|
||||
<v-card class="pa-0 ma-0">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-title>
|
||||
Wikipedia
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
{{ $store.getters.author.wiki }}
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider class="my-4"/>
|
||||
|
||||
<v-card
|
||||
v-for="(cycle, cycleName) in $store.getters.books"
|
||||
:key="cycleName"
|
||||
flat
|
||||
tile
|
||||
color="rgba(0, 0, 0, 0)"
|
||||
class="d-flex align-content-start flex-wrap"
|
||||
>
|
||||
<v-card-title class="w-100" v-if="cycleName !== 'no_cycle'">{{ $t("cycle") }} «{{ cycleName }}»</v-card-title>
|
||||
<v-card-text class="d-flex align-content-start flex-wrap pa-0 ma-0">
|
||||
<v-row no-gutters>
|
||||
<v-col
|
||||
v-for="(item, index) in cycle"
|
||||
:key="index"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
lg="3"
|
||||
xl="2"
|
||||
>
|
||||
<v-card
|
||||
hover
|
||||
ripple
|
||||
outlined
|
||||
tile
|
||||
height="127"
|
||||
class="pa-2 ma-2"
|
||||
@click="bookClick(item)"
|
||||
>
|
||||
<v-row
|
||||
no-gutters
|
||||
style="flex-wrap: nowrap;"
|
||||
>
|
||||
<v-col
|
||||
cols="5"
|
||||
style="min-width: 127px; max-width: 127px;"
|
||||
class="flex-grow-0 flex-shrink-0"
|
||||
>
|
||||
<v-img
|
||||
:src="item.image"
|
||||
height="95px"
|
||||
width="95px"
|
||||
contain
|
||||
class="book-image ma-2"
|
||||
>
|
||||
</v-img>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="1"
|
||||
class="flex-grow-1 flex-shrink-0"
|
||||
style="min-width: 80px; max-width: 100%"
|
||||
>
|
||||
<div class="book-title">
|
||||
<span class=" text-center font-weight-medium">{{ item.title }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider class="my-4"/>
|
||||
</v-card>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useDisplay } from "vuetify";
|
||||
|
||||
export default {
|
||||
name: "BooksLib",
|
||||
|
||||
setup () {
|
||||
const { smAndDown, smAndUp, xs, md, lg, xl } = useDisplay();
|
||||
|
||||
return {
|
||||
smAndDown, smAndUp, xs, md, lg, xl
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
}),
|
||||
|
||||
methods: {
|
||||
bookClick (book) {
|
||||
this.$emit("bookSelected", book);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.book
|
||||
&-image
|
||||
border-radius: 5px
|
||||
border: none
|
||||
&-title
|
||||
height: 100%
|
||||
line-height: 100px
|
||||
text-align: center
|
||||
> span
|
||||
display: inline-block
|
||||
vertical-align: middle
|
||||
line-height: normal
|
||||
|
||||
.author
|
||||
&-image
|
||||
border-radius: 125px
|
||||
border: solid #fff 2px
|
||||
box-shadow: 0px 3px 1px -2px rgba(0,0,0,0.2), 0px 2px 2px 0px rgba(0,0,0,0.14), 0px 1px 5px 0px rgba(0,0,0,0.12)
|
||||
&-name
|
||||
height: 100%
|
||||
line-height: 100px
|
||||
> span
|
||||
display: inline-block
|
||||
vertical-align: middle
|
||||
line-height: normal
|
||||
</style>
|
||||
@ -22,8 +22,8 @@
|
||||
data () {
|
||||
return {
|
||||
dialog: false,
|
||||
title: "",
|
||||
text: ""
|
||||
title: "",
|
||||
text: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/main.js
69
src/main.js
@ -1,35 +1,52 @@
|
||||
/**
|
||||
* Audiobooks library
|
||||
*
|
||||
* @author Alexander I. Chebykin <alex.chebykin@gmail.com>
|
||||
* @copyright 2018-2019 Alexander I. Chebykin
|
||||
* @license https://opensource.org/licenses/MIT MIT
|
||||
* @version 0.9
|
||||
*/
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import axios from "axios";
|
||||
import vueAxios from "vue-axios";
|
||||
import {createRouter, createWebHashHistory} from "vue-router";
|
||||
import routes from "./routes";
|
||||
import store from "./store";
|
||||
import vuetify from "./plugins/vuetify";
|
||||
import { loadFonts } from "./plugins/webfontloader";
|
||||
import i18n from "./plugins/i18n";
|
||||
|
||||
import "@mdi/font/css/materialdesignicons.css"
|
||||
import Vue from "vue"
|
||||
import Axios from "axios"
|
||||
import VueAxios from "vue-axios"
|
||||
import "./plugins/vuetify"
|
||||
import i18n from "@/plugins/i18n";
|
||||
import App from "./App.vue"
|
||||
import "./registerServiceWorker"
|
||||
loadFonts();
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.use(VueAxios, Axios)
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(), //createWebHistory(),
|
||||
routes
|
||||
});
|
||||
const app = createApp(App);
|
||||
|
||||
/**
|
||||
* Audioplayer statuses
|
||||
* Audio player statuses
|
||||
*/
|
||||
Vue.prototype.statuses = {
|
||||
app.config.globalProperties.statuses = {
|
||||
"stopped": 0,
|
||||
"paused": 1,
|
||||
"paused": 1,
|
||||
"playing": 2
|
||||
};
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount("#app")
|
||||
/**
|
||||
* App tabs
|
||||
*/
|
||||
app.config.globalProperties.tabs = {
|
||||
"authors": 0,
|
||||
"books": 1,
|
||||
"book": 2
|
||||
};
|
||||
|
||||
app.config.globalProperties.defaultBackground = "img/library1080.webp";
|
||||
|
||||
fetch(process.env.BASE_URL + "data/config.json")
|
||||
.then((response) => response.json())
|
||||
.then((config) => {
|
||||
app.config.globalProperties.appConfig = config
|
||||
app
|
||||
.use(vueAxios, axios)
|
||||
.use(vuetify)
|
||||
.use(i18n)
|
||||
.use(router)
|
||||
.use(routes)
|
||||
.use(store)
|
||||
.mount("#app")
|
||||
});
|
||||
|
||||
@ -1,45 +1,16 @@
|
||||
import Vue from "vue"
|
||||
import VueI18n from "vue-i18n"
|
||||
import { createI18n } from "vue-i18n";
|
||||
|
||||
Vue.use(VueI18n)
|
||||
import en from "./locales/en.js";
|
||||
import ru from "./locales/ru.js";
|
||||
|
||||
const messages = {
|
||||
"en": {
|
||||
about: "About",
|
||||
authors: "Authors",
|
||||
autoBookmarks: "Autobookmarks",
|
||||
book: "Book",
|
||||
bookmarkSaved: "Bookmark saved",
|
||||
bookmarkRemoved: "Bookmark removed",
|
||||
books: "Books",
|
||||
darkTheme: "Dark theme",
|
||||
error: "Error",
|
||||
information: "Information",
|
||||
removeBookmark: "Remove bookmark",
|
||||
setBookmark: "Set bookmark",
|
||||
useWikipedia: "Use Wikipedia"
|
||||
},
|
||||
"ru": {
|
||||
about: "О приложении",
|
||||
authors: "Авторы",
|
||||
autoBookmarks: "Автозакладки",
|
||||
book: "Книга",
|
||||
bookmarkSaved: "Закладка сохранена",
|
||||
bookmarkRemoved: "Закладка удалена",
|
||||
books: "Книги",
|
||||
darkTheme: "Тёмная тема",
|
||||
error: "Ошибка",
|
||||
information: "Информация",
|
||||
removeBookmark: "Удалить закладку",
|
||||
setBookmark: "Установить закладку",
|
||||
useWikipedia: "Использовать Wikipedia"
|
||||
}
|
||||
en: en,
|
||||
ru: ru
|
||||
}
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: "ru", // set locale
|
||||
fallbackLocale: "en", // set fallback locale
|
||||
messages, // set locale messages
|
||||
})
|
||||
|
||||
export default i18n
|
||||
export default new createI18n({
|
||||
legacy: false, // Vuetify does not support the legacy mode of vue-i18n
|
||||
locale: "ru",
|
||||
fallbackLocale: "en",
|
||||
messages,
|
||||
});
|
||||
|
||||
9
src/plugins/locales/en.js
Normal file
9
src/plugins/locales/en.js
Normal file
@ -0,0 +1,9 @@
|
||||
const messages = {
|
||||
appName: "AudioLib",
|
||||
authors: "Authors",
|
||||
book: "Books",
|
||||
books: "Book",
|
||||
cycle: "Cycle"
|
||||
}
|
||||
|
||||
export default messages;
|
||||
9
src/plugins/locales/ru.js
Normal file
9
src/plugins/locales/ru.js
Normal file
@ -0,0 +1,9 @@
|
||||
const messages = {
|
||||
appName: "Аудиотека",
|
||||
authors: "Авторы",
|
||||
book: "Книга",
|
||||
books: "Книги",
|
||||
cycle: "Цикл"
|
||||
}
|
||||
|
||||
export default messages;
|
||||
@ -1,7 +1,17 @@
|
||||
import Vue from "vue";
|
||||
import Vuetify from "vuetify/lib";
|
||||
import "vuetify/src/stylus/app.styl";
|
||||
// Styles
|
||||
import "@mdi/font/css/materialdesignicons.css";
|
||||
import "vuetify/styles";
|
||||
import { createVueI18nAdapter } from "vuetify/locale/adapters/vue-i18n";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import i18n from "./i18n.js";
|
||||
|
||||
Vue.use(Vuetify, {
|
||||
iconfont: "md",
|
||||
// Vuetify
|
||||
import { createVuetify } from "vuetify";
|
||||
|
||||
export default createVuetify({
|
||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||
locale: createVueI18nAdapter({
|
||||
i18n,
|
||||
useI18n,
|
||||
})
|
||||
});
|
||||
|
||||
15
src/plugins/webfontloader.js
Normal file
15
src/plugins/webfontloader.js
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* plugins/webfontloader.js
|
||||
*
|
||||
* webfontloader documentation: https://github.com/typekit/webfontloader
|
||||
*/
|
||||
|
||||
export async function loadFonts () {
|
||||
const webFontLoader = await import(/* webpackChunkName: "webfontloader" */"webfontloader");
|
||||
|
||||
webFontLoader.load({
|
||||
google: {
|
||||
families: ["Roboto:100,300,400,500,700,900&display=swap"],
|
||||
},
|
||||
});
|
||||
}
|
||||
12
src/routes.js
Normal file
12
src/routes.js
Normal file
@ -0,0 +1,12 @@
|
||||
import AuthorsLib from "./components/AuthorsLib";
|
||||
import BookReader from "./components/BookReader";
|
||||
import BooksLib from "./components/BooksLib";
|
||||
|
||||
const routes = [
|
||||
{ path: '', redirect: '/authors' },
|
||||
{ path: '/authors', component: AuthorsLib },
|
||||
{ path: '/books', component: BooksLib },
|
||||
{ path: '/book', component: BookReader },
|
||||
];
|
||||
|
||||
export default routes;
|
||||
42
src/store.js
Normal file
42
src/store.js
Normal file
@ -0,0 +1,42 @@
|
||||
//import { createApp } from "vue";
|
||||
import { createStore } from "vuex";
|
||||
|
||||
const store = createStore({
|
||||
state () {
|
||||
return {
|
||||
author: {
|
||||
name: "",
|
||||
image: "",
|
||||
wiki: ""
|
||||
},
|
||||
authors: [],
|
||||
books: [],
|
||||
book: {
|
||||
author: "",
|
||||
photo: "",
|
||||
title: "",
|
||||
cover: "",
|
||||
wiki: ""
|
||||
},
|
||||
chapters: []
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
author: state => { return state.author; },
|
||||
authors: state => { return state.authors; },
|
||||
books: state => { return state.books; },
|
||||
book: state => { return state.book; },
|
||||
chapters: state => { return state.chapters }
|
||||
},
|
||||
|
||||
mutations: {
|
||||
setAuthors (state, payload) { state.authors = payload; },
|
||||
setAuthor (state, payload) { state.author = payload; },
|
||||
setBooks (state, payload) { state.books = payload; },
|
||||
setBook (state, payload) { state.book = payload; },
|
||||
setChapters (state, payload) { state.chapters = payload; }
|
||||
}
|
||||
});
|
||||
|
||||
export default store;
|
||||
@ -1,5 +1,14 @@
|
||||
module.exports = {
|
||||
publicPath: process.env.NODE_ENV === 'production'
|
||||
? '/audiobooks/'
|
||||
: '/'
|
||||
}
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
|
||||
publicPath: process.env.NODE_ENV === "production"
|
||||
? "/audiobooks.next/"
|
||||
: "/",
|
||||
|
||||
pluginOptions: {
|
||||
vuetify: {
|
||||
// https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vuetify-loader
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user