mirror of
https://github.com/VueTubeApp/VueTube
synced 2024-11-24 04:05:16 +00:00
feat: implemented local playlist (#611)
This commit is contained in:
parent
f7ce9a62ea
commit
b1254b7376
14 changed files with 433 additions and 42 deletions
|
@ -18,6 +18,7 @@ module.exports = {
|
||||||
"vue/multi-word-component-names": 0,
|
"vue/multi-word-component-names": 0,
|
||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
|
"prettier/prettier": ["error", {endOfLine: "auto"}]
|
||||||
// 'prettier/prettier': ['error', { semi: false }],
|
// 'prettier/prettier': ['error', { semi: false }],
|
||||||
// semi: [2, 'never'],
|
// semi: [2, 'never'],
|
||||||
},
|
},
|
||||||
|
|
|
@ -312,6 +312,7 @@
|
||||||
:controls="controls"
|
:controls="controls"
|
||||||
:sources="sources"
|
:sources="sources"
|
||||||
:seeking="seeking"
|
:seeking="seeking"
|
||||||
|
:disabled="disabled"
|
||||||
@seeking="seeking = !seeking"
|
@seeking="seeking = !seeking"
|
||||||
@scrub="
|
@scrub="
|
||||||
($refs.player.currentTime = $event), ($refs.audio.currentTime = $event)
|
($refs.player.currentTime = $event), ($refs.audio.currentTime = $event)
|
||||||
|
@ -384,6 +385,10 @@ export default {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
hide-details
|
hide-details
|
||||||
height="2"
|
height="2"
|
||||||
dense
|
dense
|
||||||
|
:disabled="disabled"
|
||||||
color="transparent"
|
color="transparent"
|
||||||
thumb-color="primary"
|
thumb-color="primary"
|
||||||
track-color="transparent"
|
track-color="transparent"
|
||||||
|
@ -85,6 +86,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ["scrub", "seeking"],
|
emits: ["scrub", "seeking"],
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
|
41
NUXT/components/Playlist/playlistAlert.vue
Normal file
41
NUXT/components/Playlist/playlistAlert.vue
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
<v-dialog v-if="dialog" width="500">
|
||||||
|
<v-card
|
||||||
|
class="rounded-lg"
|
||||||
|
:class="
|
||||||
|
$vuetify.theme.dark ? 'background lighten-1' : 'background darken-1'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<v-card-title class="text-h5">Save To Playlist</v-card-title>
|
||||||
|
<v-checkbox
|
||||||
|
v-for="(playlist, index) in playlist"
|
||||||
|
:key="index"
|
||||||
|
:v-model="
|
||||||
|
!(
|
||||||
|
playlist.videos.findIndex(
|
||||||
|
(playlistVideo) => playlistVideo.id !== currentVideo.id
|
||||||
|
) === -1
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:label="playlist.name"
|
||||||
|
/>
|
||||||
|
<v-divider />
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="primary" text @click="$emit('close')"> Done </v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card></v-dialog
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: { dialog: Boolean, currentVideo: { type: Object, required: true } },
|
||||||
|
computed: {
|
||||||
|
playlists() {
|
||||||
|
return this.$store.state.playlist.playlists;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,7 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card
|
<v-btn
|
||||||
v-ripple
|
v-ripple
|
||||||
class="background d-flex flex-row overflow-hidden mb-4 mx-4"
|
text
|
||||||
|
class="background d-flex flex-row overflow-hidden mb-4 mx-7 mainCard px-0"
|
||||||
|
to="/playlist"
|
||||||
style="height: 6rem !important"
|
style="height: 6rem !important"
|
||||||
:class="
|
:class="
|
||||||
$store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak > 0
|
$store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak > 0
|
||||||
|
@ -15,11 +17,11 @@
|
||||||
? `${$store.state.tweaks.roundTweak / 3}rem`
|
? `${$store.state.tweaks.roundTweak / 3}rem`
|
||||||
: '0',
|
: '0',
|
||||||
}"
|
}"
|
||||||
flat
|
@click.native="clickHandler"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img
|
||||||
contain
|
contain
|
||||||
src="/dev.svg"
|
:src="thumbnail"
|
||||||
class="background"
|
class="background"
|
||||||
style="position: relative; max-width: 8rem !important"
|
style="position: relative; max-width: 8rem !important"
|
||||||
:class="$vuetify.theme.dark ? 'lighten-3' : 'darken-3'"
|
:class="$vuetify.theme.dark ? 'lighten-3' : 'darken-3'"
|
||||||
|
@ -33,24 +35,24 @@
|
||||||
class="d-flex flex-column justify-center align-center background-opaque"
|
class="d-flex flex-column justify-center align-center background-opaque"
|
||||||
style="position: absolute; top: 0; right: 0; width: 50%; height: 100%"
|
style="position: absolute; top: 0; right: 0; width: 50%; height: 100%"
|
||||||
>
|
>
|
||||||
<div>420</div>
|
<div>{{ playlist.videos.length }}</div>
|
||||||
<v-icon>mdi-playlist-play</v-icon>
|
<v-icon>mdi-playlist-play</v-icon>
|
||||||
</div>
|
</div>
|
||||||
</v-img>
|
</v-img>
|
||||||
<div class="pa-4" v-emoji style="font-size: 0.75rem !important">
|
<div v-emoji class="pa-4 text-left" style="font-size: 0.75rem !important">
|
||||||
<b>Work in Progress</b>
|
<b>{{ playlist.name }}</b>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="background--text caption mt-2"
|
class="background--text caption mt-2"
|
||||||
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||||
>
|
>
|
||||||
Bottom Text <br />
|
Bottom Text <br />
|
||||||
420 videos
|
{{ playlist.videos.length }} videos
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<v-btn
|
<!-- <v-btn
|
||||||
text
|
text
|
||||||
tile
|
tile
|
||||||
elevation="0"
|
elevation="0"
|
||||||
|
@ -67,7 +69,39 @@
|
||||||
style="width: 2rem !important"
|
style="width: 2rem !important"
|
||||||
>
|
>
|
||||||
<v-icon>mdi-playlist-plus</v-icon>
|
<v-icon>mdi-playlist-plus</v-icon>
|
||||||
</v-btn>
|
</v-btn> -->
|
||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
playlist: { type: Object, required: true },
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
thumbnail() {
|
||||||
|
try {
|
||||||
|
const videoId =
|
||||||
|
this.playlist.videos.length === 0 ? "" : this.playlist.videos[0].id;
|
||||||
|
return `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
|
||||||
|
} catch (e) {
|
||||||
|
alert(e.message);
|
||||||
|
return `https://img.youtube.com/vi//maxresdefault.jpg`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clickHandler() {
|
||||||
|
this.$emit("click");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.mainCard {
|
||||||
|
text-transform: none !important;
|
||||||
|
letter-spacing: normal !important;
|
||||||
|
}
|
||||||
|
</style>
|
101
NUXT/components/Playlist/playlistVideoCard.vue
Normal file
101
NUXT/components/Playlist/playlistVideoCard.vue
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<template>
|
||||||
|
<v-card
|
||||||
|
v-ripple
|
||||||
|
class="background d-flex flex-row overflow-hidden mb-4 mx-4"
|
||||||
|
style="height: 4.5rem !important"
|
||||||
|
:class="
|
||||||
|
$store.state.tweaks.roundThumb && $store.state.tweaks.roundTweak > 0
|
||||||
|
? $vuetify.theme.dark
|
||||||
|
? 'lighten-1'
|
||||||
|
: 'darken-1'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
:style="{
|
||||||
|
borderRadius: $store.state.tweaks.roundThumb
|
||||||
|
? `${$store.state.tweaks.roundTweak / 3}rem`
|
||||||
|
: '0',
|
||||||
|
}"
|
||||||
|
flat
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
class="d-flex flex-row w-50 elevation-0"
|
||||||
|
:to="`/watch?v=${video.id}`"
|
||||||
|
>
|
||||||
|
<v-img
|
||||||
|
:src="thumbnail"
|
||||||
|
aspect-ratio="1.7778"
|
||||||
|
style="position: relative; width: 8rem"
|
||||||
|
:class="$vuetify.theme.dark ? 'lighten-3' : 'darken-3'"
|
||||||
|
:style="{
|
||||||
|
borderRadius: $store.state.tweaks.roundThumb
|
||||||
|
? `${$store.state.tweaks.roundTweak / 3}rem`
|
||||||
|
: '0',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
</v-img>
|
||||||
|
<div
|
||||||
|
v-emoji
|
||||||
|
style="font-size: 0.75rem !important"
|
||||||
|
class="d-flex flex-column ml-2"
|
||||||
|
>
|
||||||
|
<b
|
||||||
|
class="text-left"
|
||||||
|
style="
|
||||||
|
{
|
||||||
|
height: 1.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>{{ video.title }}
|
||||||
|
</b>
|
||||||
|
<div
|
||||||
|
class="background--text caption mt-2 text-left d-inline-block text-truncate"
|
||||||
|
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||||
|
style="
|
||||||
|
{
|
||||||
|
height: 0.75rem;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ video.channel }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<div class="d-flex w-50 flex-column">
|
||||||
|
<v-btn
|
||||||
|
text
|
||||||
|
tile
|
||||||
|
elevation="0"
|
||||||
|
class="flex-grow-1"
|
||||||
|
style="width: 2rem !important"
|
||||||
|
@click="deleted"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: { video: { type: Object, required: true } },
|
||||||
|
computed: {
|
||||||
|
thumbnail() {
|
||||||
|
return `https://img.youtube.com/vi/${this.video.id}/maxresdefault.jpg`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deleted() {
|
||||||
|
this.$emit("deleted");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -6,7 +6,11 @@
|
||||||
v-show="!search"
|
v-show="!search"
|
||||||
class="my-auto ml-4"
|
class="my-auto ml-4"
|
||||||
v-text="
|
v-text="
|
||||||
$route.path.includes('channel') ? $store.state.channel.title : page
|
$route.path.includes('channel')
|
||||||
|
? $store.state.channel.title
|
||||||
|
: $route.path.includes('playlist')
|
||||||
|
? $store.state.playlist.currentPlaylist.name
|
||||||
|
: page
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"@capacitor/status-bar": "^1.0.8",
|
"@capacitor/status-bar": "^1.0.8",
|
||||||
"core-js": "^3.25.0",
|
"core-js": "^3.25.0",
|
||||||
"nuxt": "^2.15.8",
|
"nuxt": "^2.15.8",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"vue": "^2.7.10",
|
"vue": "^2.7.10",
|
||||||
"vue-server-renderer": "^2.7.10",
|
"vue-server-renderer": "^2.7.10",
|
||||||
"vue-template-compiler": "^2.7.10",
|
"vue-template-compiler": "^2.7.10",
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div></div>
|
||||||
<playlist-card />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import playlistCard from "../../components/playlistCard.vue";
|
export default {};
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
playlistCard,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,12 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h4
|
<div class="d-flex justify-space-between mb-2 mx-7">
|
||||||
class="ml-7 mb-2 background--text"
|
<h4
|
||||||
:class="$vuetify.theme.dark ? 'text--lighten-3' : 'text--darken-3'"
|
class="background--text w-50"
|
||||||
>
|
:class="$vuetify.theme.dark ? 'text--lighten-3' : 'text--darken-3'"
|
||||||
Local Playlists
|
>
|
||||||
</h4>
|
Local Playlists
|
||||||
<playlist-card />
|
</h4>
|
||||||
|
<v-btn text tile elevation="0" class="w-5-0" @click="dialog = true">
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column-reverse">
|
||||||
|
<playlist-card
|
||||||
|
v-for="(playlist, index) in playlists"
|
||||||
|
:key="index"
|
||||||
|
:playlist="playlist"
|
||||||
|
@click="changeToPlaylist(index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
text
|
text
|
||||||
class="entry text-left setting-btn no-spacing"
|
class="entry text-left setting-btn no-spacing"
|
||||||
|
@ -27,13 +40,62 @@
|
||||||
>
|
>
|
||||||
History
|
History
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<!-- Create Playlist Dialog -->
|
||||||
|
<v-dialog v-model="dialog" width="500">
|
||||||
|
<v-card
|
||||||
|
class="rounded-lg"
|
||||||
|
:class="
|
||||||
|
$vuetify.theme.dark ? 'background lighten-1' : 'background darken-1'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<v-card-title class="text-h5">Create Playlist</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field v-model="playlistName" label="Playlist Name" solo />
|
||||||
|
</v-card-text>
|
||||||
|
<v-divider />
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="primary" text @click="dialog = false">
|
||||||
|
{{ lang.cancel }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="primary" text @click="createPlaylist()">
|
||||||
|
{{ lang.create }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card></v-dialog
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import playlistCard from "../components/playlistCard.vue";
|
import playlistCard from "~/components/Playlist/playlistCard.vue";
|
||||||
export default {
|
export default {
|
||||||
components: { playlistCard },
|
components: { playlistCard },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dialog: false,
|
||||||
|
lang: {},
|
||||||
|
playlistName: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
playlists() {
|
||||||
|
return this.$store.state.playlist.playlists;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const lang = this.$lang();
|
||||||
|
this.lang = lang.mods.developer;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createPlaylist: function () {
|
||||||
|
this.$store.commit("playlist/createPlaylist", this.playlistName);
|
||||||
|
this.dialog = false;
|
||||||
|
},
|
||||||
|
changeToPlaylist: function (videoIndex) {
|
||||||
|
this.$store.commit("playlist/changeToPlaylist", videoIndex);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
30
NUXT/pages/playlist.vue
Normal file
30
NUXT/pages/playlist.vue
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<playlist-video-card
|
||||||
|
v-for="(video, index) in playlist.videos"
|
||||||
|
:key="index"
|
||||||
|
:video="video"
|
||||||
|
@deleted="deletePlaylistVideo(video)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import playlistVideoCard from "~/components/playlist/playlistVideoCard.vue";
|
||||||
|
export default {
|
||||||
|
components: { playlistVideoCard },
|
||||||
|
computed: {
|
||||||
|
playlist() {
|
||||||
|
return this.$store.state.playlist.currentPlaylist;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deletePlaylistVideo(target) {
|
||||||
|
this.$store.commit("playlist/removeFromPlaylist", {
|
||||||
|
playlistIndex: this.playlist.index,
|
||||||
|
video: target,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -9,6 +9,7 @@
|
||||||
:video="video"
|
:video="video"
|
||||||
:sources="sources"
|
:sources="sources"
|
||||||
:recommends="recommends"
|
:recommends="recommends"
|
||||||
|
:disabled="saveDialog"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -273,6 +274,30 @@
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<v-dialog v-model="saveDialog" width="500">
|
||||||
|
<v-card
|
||||||
|
class="rounded-lg"
|
||||||
|
:class="
|
||||||
|
$vuetify.theme.dark ? 'background lighten-1' : 'background darken-1'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<v-card-title class="text-h5">Save To Playlist</v-card-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-checkbox
|
||||||
|
v-for="(playlist, index) in playlists"
|
||||||
|
:key="index"
|
||||||
|
v-model="playlistsCheckbox[index]"
|
||||||
|
class="mx-5"
|
||||||
|
:label="playlist.name"
|
||||||
|
@change="updatePlaylist($event, index)"
|
||||||
|
/>
|
||||||
|
<v-divider />
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="primary" text @click="saveDialog = false"> Done </v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card></v-dialog
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -304,6 +329,12 @@ export default {
|
||||||
data: function () {
|
data: function () {
|
||||||
return this.initializeState();
|
return this.initializeState();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
playlists() {
|
||||||
|
return this.$store.state.playlist.playlists;
|
||||||
|
},
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
// Watch for change in the route query string (in this case, ?v=xxxxxxxx to ?v=yyyyyyyy)
|
// Watch for change in the route query string (in this case, ?v=xxxxxxxx to ?v=yyyyyyyy)
|
||||||
$route: {
|
$route: {
|
||||||
|
@ -356,6 +387,14 @@ export default {
|
||||||
title: this.video.title,
|
title: this.video.title,
|
||||||
channel: this.video.channelName,
|
channel: this.video.channelName,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.playlistsCheckbox = this.playlists.map(
|
||||||
|
(playlist) =>
|
||||||
|
playlist.videos.findIndex(
|
||||||
|
(playlistVideo) => playlistVideo.id === this.video.id
|
||||||
|
) !== -1
|
||||||
|
);
|
||||||
|
|
||||||
//--- API WatchTime call ---//
|
//--- API WatchTime call ---//
|
||||||
if (this.$store.state.watchTelemetry) {
|
if (this.$store.state.watchTelemetry) {
|
||||||
this.playbackTracking = result.playbackTracking;
|
this.playbackTracking = result.playbackTracking;
|
||||||
|
@ -459,8 +498,9 @@ export default {
|
||||||
{
|
{
|
||||||
name: "Save",
|
name: "Save",
|
||||||
icon: "mdi-plus-box-multiple-outline",
|
icon: "mdi-plus-box-multiple-outline",
|
||||||
actionName: "enqueue",
|
// action: this.save()
|
||||||
disabled: true,
|
actionName: "save",
|
||||||
|
disabled: false,
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// name: "Quality",
|
// name: "Quality",
|
||||||
|
@ -484,13 +524,14 @@ export default {
|
||||||
interval: null,
|
interval: null,
|
||||||
video: null,
|
video: null,
|
||||||
backHierarchy: [],
|
backHierarchy: [],
|
||||||
|
saveDialog: false,
|
||||||
|
playlistsCheckbox: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
mountedInit() {
|
mountedInit() {
|
||||||
this.startTime = Math.floor(Date.now() / 1000);
|
this.startTime = Math.floor(Date.now() / 1000);
|
||||||
this.getVideo();
|
this.getVideo();
|
||||||
|
|
||||||
// Reset vertical scrolling
|
// Reset vertical scrolling
|
||||||
const scrollableList = document.querySelectorAll(".overflow-y-auto");
|
const scrollableList = document.querySelectorAll(".overflow-y-auto");
|
||||||
scrollableList.forEach((scrollable) => {
|
scrollableList.forEach((scrollable) => {
|
||||||
|
@ -514,6 +555,28 @@ export default {
|
||||||
this.$vuetube.addBackAction(dismissComment);
|
this.$vuetube.addBackAction(dismissComment);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
save() {
|
||||||
|
this.saveDialog = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePlaylist(event, index) {
|
||||||
|
if (event) {
|
||||||
|
this.$store.commit("playlist/addToPlaylist", {
|
||||||
|
video: {
|
||||||
|
id: this.video.id,
|
||||||
|
title: this.video.title,
|
||||||
|
channel: this.video.channelName,
|
||||||
|
},
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$store.commit("playlist/removeFromPlaylist", {
|
||||||
|
video: this.video,
|
||||||
|
playlistIndex: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
51
NUXT/store/playlist/index.js
Normal file
51
NUXT/store/playlist/index.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
export const state = () => ({
|
||||||
|
playlists: [],
|
||||||
|
currentPlaylist: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shape of playlists
|
||||||
|
// [playlist, playlist]
|
||||||
|
|
||||||
|
// Shape of playlist
|
||||||
|
// {name: string, videos: []}
|
||||||
|
|
||||||
|
// Shape of currentPlaylist
|
||||||
|
// {index: number, name: string, videos: []}
|
||||||
|
|
||||||
|
export const mutations = {
|
||||||
|
initPlaylists(state) {
|
||||||
|
if (process.client) {
|
||||||
|
// read local storage and parse the list of objects
|
||||||
|
state.playlists = JSON.parse(localStorage.getItem("playlists"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createPlaylist(state, name) {
|
||||||
|
state.playlists.push({ name, videos: [] });
|
||||||
|
localStorage.setItem("playlists", JSON.stringify(state.playlists));
|
||||||
|
},
|
||||||
|
removePlaylist(state, index) {
|
||||||
|
state.playlists.splice(index, 1);
|
||||||
|
localStorage.setItem("playlists", JSON.stringify(state.playlists));
|
||||||
|
},
|
||||||
|
addToPlaylist(state, { index, video }) {
|
||||||
|
state.playlists[index].videos.unshift(video);
|
||||||
|
localStorage.setItem("playlists", JSON.stringify(state.playlists));
|
||||||
|
},
|
||||||
|
removeFromPlaylist(state, { playlistIndex, video }) {
|
||||||
|
const videoIndex = state.playlists[playlistIndex].videos.findIndex(
|
||||||
|
(playlistVideo) => playlistVideo.id === video.id
|
||||||
|
);
|
||||||
|
if (videoIndex === -1) throw new Error("Unable To Find Video");
|
||||||
|
state.playlists[playlistIndex].videos.splice(videoIndex, 1);
|
||||||
|
localStorage.setItem("playlists", JSON.stringify(state.playlists));
|
||||||
|
},
|
||||||
|
changeToPlaylist(state, videoIndex) {
|
||||||
|
state.currentPlaylist = {
|
||||||
|
index: videoIndex,
|
||||||
|
...state.playlists[videoIndex],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
exitPlaylist(state) {
|
||||||
|
state.currentPlaylist = null;
|
||||||
|
},
|
||||||
|
};
|
|
@ -9,16 +9,16 @@ install! 'cocoapods', :disable_input_output_paths => true
|
||||||
def capacitor_pods
|
def capacitor_pods
|
||||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCommunityHttp', :path => '../../node_modules/@capacitor-community/http'
|
pod 'CapacitorCommunityHttp', :path => '..\..\node_modules\@capacitor-community\http'
|
||||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
pod 'CapacitorApp', :path => '..\..\node_modules\@capacitor\app'
|
||||||
pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'
|
pod 'CapacitorDevice', :path => '..\..\node_modules\@capacitor\device'
|
||||||
pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
|
pod 'CapacitorFilesystem', :path => '..\..\node_modules\@capacitor\filesystem'
|
||||||
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
|
pod 'CapacitorHaptics', :path => '..\..\node_modules\@capacitor\haptics'
|
||||||
pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
|
pod 'CapacitorShare', :path => '..\..\node_modules\@capacitor\share'
|
||||||
pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
|
pod 'CapacitorSplashScreen', :path => '..\..\node_modules\@capacitor\splash-screen'
|
||||||
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
|
pod 'CapacitorStatusBar', :path => '..\..\node_modules\@capacitor\status-bar'
|
||||||
pod 'CapacitorToast', :path => '../../node_modules/@capacitor/toast'
|
pod 'CapacitorToast', :path => '..\..\node_modules\@capacitor\toast'
|
||||||
pod 'HugotomaziCapacitorNavigationBar', :path => '../../node_modules/@hugotomazi/capacitor-navigation-bar'
|
pod 'HugotomaziCapacitorNavigationBar', :path => '..\..\node_modules\@hugotomazi\capacitor-navigation-bar'
|
||||||
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
|
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue