Compare commits
4 commits
5f23aa8e78
...
d4354e81f8
Author | SHA1 | Date | |
---|---|---|---|
d4354e81f8 | |||
701623c99d | |||
8880b966fc | |||
5d4bfb3f90 |
14 changed files with 411 additions and 30 deletions
|
@ -43,13 +43,21 @@ export default {
|
||||||
this.close()
|
this.close()
|
||||||
},
|
},
|
||||||
|
|
||||||
show() {
|
open() {
|
||||||
this.$refs.modal.show()
|
this.$refs.modal.show()
|
||||||
},
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.$refs.modal.hide()
|
this.$refs.modal.hide()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
show() {
|
||||||
|
this.open()
|
||||||
|
},
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.close()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<form @submit.prevent="onConfirm">
|
<form @submit.prevent="onConfirm">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<slot />
|
<slot />
|
||||||
<input type="text" ref="input" />
|
<input type="text" ref="input" v-model="value_" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
@ -38,26 +38,79 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: "Cancel",
|
default: "Cancel",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value_: "",
|
||||||
|
visible_: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onConfirm() {
|
onConfirm() {
|
||||||
this.$emit('input', this.$refs.input.value)
|
this.$emit('input', this.value_)
|
||||||
this.close()
|
this.close()
|
||||||
},
|
},
|
||||||
|
|
||||||
show() {
|
open() {
|
||||||
|
if (this.visible_)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.value_ = this.value
|
||||||
this.$refs.modal.show()
|
this.$refs.modal.show()
|
||||||
|
this.visible_ = true
|
||||||
|
this.focus()
|
||||||
},
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
if (!this.visible_)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.value_ = ""
|
||||||
this.$refs.modal.hide()
|
this.$refs.modal.hide()
|
||||||
|
this.visible_ = false
|
||||||
|
},
|
||||||
|
|
||||||
|
show() {
|
||||||
|
this.open()
|
||||||
|
},
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.close()
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.input.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
visible(val) {
|
||||||
|
if (val) {
|
||||||
|
this.open()
|
||||||
|
} else {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.visible_ = this.visible
|
||||||
|
this.value_ = this.value || ""
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.input.value = ""
|
|
||||||
this.$refs.input.focus()
|
this.$refs.input.focus()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,9 +23,11 @@
|
||||||
<component
|
<component
|
||||||
:is="mediaProvider"
|
:is="mediaProvider"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
@back="mediaProvider = null"
|
@back="mediaProvider = null"
|
||||||
@path-change="$emit('path-change', $event)"
|
@path-change="$emit('path-change', $event)"
|
||||||
@play="$emit('play', $event)" />
|
@play="$emit('play', $event)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
|
@ -39,8 +41,17 @@ import Utils from "@/Utils";
|
||||||
import providersMetadata from "./Providers/meta.json";
|
import providersMetadata from "./Providers/meta.json";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['path-change', 'play'],
|
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
|
emits: [
|
||||||
|
'add-to-playlist',
|
||||||
|
'create-playlist',
|
||||||
|
'path-change',
|
||||||
|
'play',
|
||||||
|
'remove-from-playlist',
|
||||||
|
'remove-playlist',
|
||||||
|
'rename-playlist',
|
||||||
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Browser,
|
Browser,
|
||||||
Loading,
|
Loading,
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
:plugin-name="pluginName"
|
:plugin-name="pluginName"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:filter="browserFilter"
|
:filter="browserFilter"
|
||||||
|
@add-to-playlist="addToPlaylistItem = $event"
|
||||||
@select="onResultSelect($event)"
|
@select="onResultSelect($event)"
|
||||||
@play="play"
|
@play="play"
|
||||||
@view="view"
|
@view="view"
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
v-else-if="selectedView === 'torrents'" />
|
v-else-if="selectedView === 'torrents'" />
|
||||||
|
|
||||||
<Browser :filter="browserFilter"
|
<Browser :filter="browserFilter"
|
||||||
|
@add-to-playlist="addToPlaylistItem = $event"
|
||||||
@path-change="browserFilter = ''"
|
@path-change="browserFilter = ''"
|
||||||
@play="play($event)"
|
@play="play($event)"
|
||||||
v-else-if="selectedView === 'browser'" />
|
v-else-if="selectedView === 'browser'" />
|
||||||
|
@ -75,6 +77,16 @@
|
||||||
<UrlPlayer :value="urlPlay" @input="urlPlay = $event.target.value" @play="playUrl($event)" />
|
<UrlPlayer :value="urlPlay" @input="urlPlay = $event.target.value" @play="playUrl($event)" />
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="add-to-playlist-container" v-if="addToPlaylistItem">
|
||||||
|
<Modal title="Add to playlist" :visible="addToPlaylistItem != null" @close="addToPlaylistItem = null">
|
||||||
|
<PlaylistAdder
|
||||||
|
:item="addToPlaylistItem"
|
||||||
|
@done="addToPlaylistItem = null"
|
||||||
|
@close="addToPlaylistItem = null"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</template>
|
</template>
|
||||||
|
@ -88,6 +100,7 @@ import Header from "@/components/panels/Media/Header";
|
||||||
import MediaUtils from "@/components/Media/Utils";
|
import MediaUtils from "@/components/Media/Utils";
|
||||||
import MediaView from "@/components/Media/View";
|
import MediaView from "@/components/Media/View";
|
||||||
import Nav from "@/components/panels/Media/Nav";
|
import Nav from "@/components/panels/Media/Nav";
|
||||||
|
import PlaylistAdder from "@/components/panels/Media/PlaylistAdder";
|
||||||
import Results from "@/components/panels/Media/Results";
|
import Results from "@/components/panels/Media/Results";
|
||||||
import Subtitles from "@/components/panels/Media/Subtitles";
|
import Subtitles from "@/components/panels/Media/Subtitles";
|
||||||
import Transfers from "@/components/panels/Torrent/Transfers";
|
import Transfers from "@/components/panels/Torrent/Transfers";
|
||||||
|
@ -102,6 +115,7 @@ export default {
|
||||||
MediaView,
|
MediaView,
|
||||||
Modal,
|
Modal,
|
||||||
Nav,
|
Nav,
|
||||||
|
PlaylistAdder,
|
||||||
Results,
|
Results,
|
||||||
Subtitles,
|
Subtitles,
|
||||||
Transfers,
|
Transfers,
|
||||||
|
@ -139,6 +153,7 @@ export default {
|
||||||
awaitingPlayTorrent: null,
|
awaitingPlayTorrent: null,
|
||||||
urlPlay: null,
|
urlPlay: null,
|
||||||
browserFilter: null,
|
browserFilter: null,
|
||||||
|
addToPlaylistItem: null,
|
||||||
torrentPlugin: null,
|
torrentPlugin: null,
|
||||||
torrentPlugins: [
|
torrentPlugins: [
|
||||||
'torrent',
|
'torrent',
|
||||||
|
@ -487,4 +502,10 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.add-to-playlist-container) {
|
||||||
|
.body {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
:class="{selected: selected}"
|
:class="{selected: selected}"
|
||||||
@click.right.prevent="$refs.dropdown.toggle()"
|
@click.right.prevent="$refs.dropdown.toggle()"
|
||||||
v-if="!hidden">
|
v-if="!hidden">
|
||||||
|
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<MediaImage :item="item" @play="$emit('play')" />
|
<MediaImage :item="item" @play="$emit('play')" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,6 +20,10 @@
|
||||||
v-if="item.type === 'torrent'" />
|
v-if="item.type === 'torrent'" />
|
||||||
<DropdownItem icon-class="fa fa-window-maximize" text="View in browser" @click="$emit('view')"
|
<DropdownItem icon-class="fa fa-window-maximize" text="View in browser" @click="$emit('view')"
|
||||||
v-if="item.type === 'file'" />
|
v-if="item.type === 'file'" />
|
||||||
|
<DropdownItem icon-class="fa fa-list" text="Add to playlist" @click="$emit('add-to-playlist')"
|
||||||
|
v-if="item.type === 'youtube'" />
|
||||||
|
<DropdownItem icon-class="fa fa-trash" text="Remove from playlist" @click="$refs.confirmPlaylistRemove.open()"
|
||||||
|
v-if="playlist" />
|
||||||
<DropdownItem icon-class="fa fa-info-circle" text="Info" @click="$emit('select')" />
|
<DropdownItem icon-class="fa fa-info-circle" text="Info" @click="$emit('select')" />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,10 +40,15 @@
|
||||||
{{ formatDateTime(item.created_at, true) }}
|
{{ formatDateTime(item.created_at, true) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ConfirmDialog ref="confirmPlaylistRemove" @input="$emit('remove-from-playlist')">
|
||||||
|
Are you sure you want to remove this item from the playlist?
|
||||||
|
</ConfirmDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ConfirmDialog from "@/components/elements/ConfirmDialog";
|
||||||
import Dropdown from "@/components/elements/Dropdown";
|
import Dropdown from "@/components/elements/Dropdown";
|
||||||
import DropdownItem from "@/components/elements/DropdownItem";
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
import Icons from "./icons.json";
|
import Icons from "./icons.json";
|
||||||
|
@ -46,9 +56,23 @@ import MediaImage from "./MediaImage";
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {Dropdown, DropdownItem, MediaImage},
|
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
emits: ['play', 'select', 'view', 'download'],
|
components: {
|
||||||
|
ConfirmDialog,
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
MediaImage,
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: [
|
||||||
|
'add-to-playlist',
|
||||||
|
'download',
|
||||||
|
'play',
|
||||||
|
'remove-from-playlist',
|
||||||
|
'select',
|
||||||
|
'view',
|
||||||
|
],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -64,6 +88,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
playlist: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
@ -87,6 +115,10 @@ export default {
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-bottom: 1px solid transparent !important;
|
border-bottom: 1px solid transparent !important;
|
||||||
|
|
||||||
|
@include from($tablet) {
|
||||||
|
max-height: max(25em, 25%);
|
||||||
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
box-shadow: $border-shadow-bottom;
|
box-shadow: $border-shadow-bottom;
|
||||||
background: $selected-bg;
|
background: $selected-bg;
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
<template>
|
||||||
|
<div class="playlist-adder-container">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<TextPrompt ref="newPlaylistName" :visible="showNewPlaylist" @input="createPlaylist($event)">
|
||||||
|
Playlist name
|
||||||
|
</TextPrompt>
|
||||||
|
|
||||||
|
<div class="playlists">
|
||||||
|
<div class="playlist new-playlist">
|
||||||
|
<button @click="showNewPlaylist = true">
|
||||||
|
<i class="fa fa-plus" />
|
||||||
|
Create new playlist
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="playlist" v-for="playlist in playlists" :key="playlist.id">
|
||||||
|
<button @click="addToPlaylist(playlist.id)">
|
||||||
|
<i class="fa fa-list" />
|
||||||
|
{{ playlist.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
import TextPrompt from "@/components/elements/TextPrompt"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
emits: ['done'],
|
||||||
|
mixins: [Utils],
|
||||||
|
components: {Loading, TextPrompt},
|
||||||
|
props: {
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
playlists: [],
|
||||||
|
showNewPlaylist: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async createPlaylist(name) {
|
||||||
|
name = name?.trim()
|
||||||
|
if (!name?.length)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const playlist = await this.request('youtube.create_playlist', {
|
||||||
|
name: name,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.request('youtube.add_to_playlist', {
|
||||||
|
playlist_id: playlist.id,
|
||||||
|
video_id: this.item.id || this.item.url,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$emit('done')
|
||||||
|
this.notify({
|
||||||
|
text: 'Playlist created and video added',
|
||||||
|
image: {
|
||||||
|
icon: 'check',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
this.showNewPlaylist = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshPlaylists() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.playlists = await this.request('youtube.get_playlists')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async addToPlaylist(playlistId) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.request('youtube.add_to_playlist', {
|
||||||
|
playlist_id: playlistId,
|
||||||
|
video_id: this.item.id || this.item.url,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.notify({
|
||||||
|
text: 'Video added to playlist',
|
||||||
|
image: {
|
||||||
|
icon: 'check',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$emit('done')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.refreshPlaylists()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.playlist-adder-container {
|
||||||
|
min-width: 300px;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.playlists {
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist {
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-playlist {
|
||||||
|
button {
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: $default-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,8 +2,18 @@
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['back', 'path-change', 'play'],
|
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
|
emits: [
|
||||||
|
'add-to-playlist',
|
||||||
|
'back',
|
||||||
|
'create-playlist',
|
||||||
|
'path-change',
|
||||||
|
'play',
|
||||||
|
'remove-from-playlist',
|
||||||
|
'remove-playlist',
|
||||||
|
'rename-playlist',
|
||||||
|
],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
filter: {
|
filter: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
@ -8,17 +8,27 @@
|
||||||
|
|
||||||
<div class="body" v-else>
|
<div class="body" v-else>
|
||||||
<Feed :filter="filter"
|
<Feed :filter="filter"
|
||||||
@play="$emit('play', $event)" v-if="selectedView === 'feed'" />
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
|
@play="$emit('play', $event)"
|
||||||
|
v-if="selectedView === 'feed'"
|
||||||
|
/>
|
||||||
|
|
||||||
<Playlists :filter="filter"
|
<Playlists :filter="filter"
|
||||||
:selected-playlist="selectedPlaylist"
|
:selected-playlist="selectedPlaylist"
|
||||||
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@remove-from-playlist="removeFromPlaylist"
|
||||||
@select="onPlaylistSelected"
|
@select="onPlaylistSelected"
|
||||||
v-else-if="selectedView === 'playlists'" />
|
v-else-if="selectedView === 'playlists'"
|
||||||
|
/>
|
||||||
|
|
||||||
<Subscriptions :filter="filter"
|
<Subscriptions :filter="filter"
|
||||||
:selected-channel="selectedChannel"
|
:selected-channel="selectedChannel"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
@select="onChannelSelected"
|
@select="onChannelSelected"
|
||||||
v-else-if="selectedView === 'subscriptions'" />
|
v-else-if="selectedView === 'subscriptions'"
|
||||||
|
/>
|
||||||
|
|
||||||
<Index @select="selectView" v-else />
|
<Index @select="selectView" v-else />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,6 +97,21 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async removeFromPlaylist(event) {
|
||||||
|
const playlistId = event.playlist_id
|
||||||
|
const videoId = event.item.url
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.request('youtube.remove_from_playlist', {
|
||||||
|
playlist_id: playlistId,
|
||||||
|
video_id: videoId,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
selectView(view) {
|
selectView(view) {
|
||||||
this.selectedView = view
|
this.selectedView = view
|
||||||
if (view === 'playlists')
|
if (view === 'playlists')
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
:sources="{'youtube': true}"
|
:sources="{'youtube': true}"
|
||||||
:selected-result="selectedResult"
|
:selected-result="selectedResult"
|
||||||
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
@select="selectedResult = $event"
|
@select="selectedResult = $event"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
v-else />
|
v-else />
|
||||||
|
@ -22,8 +23,12 @@ import Results from "@/components/panels/Media/Results";
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['play'],
|
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
|
emits: [
|
||||||
|
'add-to-playlist',
|
||||||
|
'play',
|
||||||
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Loading,
|
Loading,
|
||||||
NoItems,
|
NoItems,
|
||||||
|
|
|
@ -8,9 +8,12 @@
|
||||||
<Results :results="items"
|
<Results :results="items"
|
||||||
:sources="{'youtube': true}"
|
:sources="{'youtube': true}"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
|
:playlist="id"
|
||||||
:selected-result="selectedResult"
|
:selected-result="selectedResult"
|
||||||
@select="selectedResult = $event"
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
|
@remove-from-playlist="$emit('remove-from-playlist', $event)"
|
||||||
|
@select="selectedResult = $event"
|
||||||
v-else />
|
v-else />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -22,8 +25,13 @@ import Results from "@/components/panels/Media/Results";
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['play'],
|
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
|
emits: [
|
||||||
|
'add-to-playlist',
|
||||||
|
'play',
|
||||||
|
'remove-from-playlist',
|
||||||
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Loading,
|
Loading,
|
||||||
NoItems,
|
NoItems,
|
||||||
|
|
|
@ -18,7 +18,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="playlist-body" v-else>
|
<div class="playlist-body" v-else>
|
||||||
<Playlist :id="selectedPlaylist" :filter="filter" @play="$emit('play', $event)" />
|
<Playlist
|
||||||
|
:id="selectedPlaylist"
|
||||||
|
:filter="filter"
|
||||||
|
:playlist="playlist"
|
||||||
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
|
@remove-from-playlist="$emit('remove-from-playlist', {item: $event, playlist_id: selectedPlaylist})"
|
||||||
|
@play="$emit('play', $event)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -31,8 +38,17 @@ import Playlist from "./Playlist";
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['play', 'select'],
|
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
|
emits: [
|
||||||
|
'add-to-playlist',
|
||||||
|
'create-playlist',
|
||||||
|
'play',
|
||||||
|
'remove-from-playlist',
|
||||||
|
'remove-playlist',
|
||||||
|
'rename-playlist',
|
||||||
|
'select',
|
||||||
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Loading,
|
Loading,
|
||||||
MediaImage,
|
MediaImage,
|
||||||
|
|
|
@ -4,9 +4,12 @@
|
||||||
<div class="grid" ref="grid" v-if="results?.length" @scroll="onScroll">
|
<div class="grid" ref="grid" v-if="results?.length" @scroll="onScroll">
|
||||||
<Item v-for="(item, i) in visibleResults"
|
<Item v-for="(item, i) in visibleResults"
|
||||||
:key="i"
|
:key="i"
|
||||||
:item="item"
|
|
||||||
:selected="selectedResult === i"
|
|
||||||
:hidden="!!Object.keys(sources || {}).length && !sources[item.type]"
|
:hidden="!!Object.keys(sources || {}).length && !sources[item.type]"
|
||||||
|
:item="item"
|
||||||
|
:playlist="playlist"
|
||||||
|
:selected="selectedResult === i"
|
||||||
|
@add-to-playlist="$emit('add-to-playlist', item)"
|
||||||
|
@remove-from-playlist="$emit('remove-from-playlist', item)"
|
||||||
@select="$emit('select', i)"
|
@select="$emit('select', i)"
|
||||||
@play="$emit('play', item)"
|
@play="$emit('play', item)"
|
||||||
@view="$emit('view', item)"
|
@view="$emit('view', item)"
|
||||||
|
@ -30,7 +33,16 @@ import Modal from "@/components/Modal";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {Info, Item, Loading, Modal},
|
components: {Info, Item, Loading, Modal},
|
||||||
emits: ['select', 'play', 'view', 'download', 'scroll-end'],
|
emits: [
|
||||||
|
'add-to-playlist',
|
||||||
|
'download',
|
||||||
|
'play',
|
||||||
|
'remove-from-playlist',
|
||||||
|
'scroll-end',
|
||||||
|
'select',
|
||||||
|
'view',
|
||||||
|
],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
loading: {
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -64,6 +76,10 @@ export default {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 25,
|
default: 25,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
playlist: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import base64
|
import base64
|
||||||
|
import re
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
|
@ -99,6 +100,14 @@ class YoutubePlugin(Plugin):
|
||||||
PipedChannelSchema().dump(self._request(f'channel/{id}')) or {} # type: ignore
|
PipedChannelSchema().dump(self._request(f'channel/{id}')) or {} # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_video_id(id_or_url: str) -> str:
|
||||||
|
m = re.search(r'/watch\?v=([^&]+)', id_or_url)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
|
||||||
|
return id_or_url
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def search(self, query: str, **_) -> List[dict]:
|
def search(self, query: str, **_) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
|
@ -215,10 +224,10 @@ class YoutubePlugin(Plugin):
|
||||||
:param playlist_id: Piped playlist ID.
|
:param playlist_id: Piped playlist ID.
|
||||||
"""
|
"""
|
||||||
self._request(
|
self._request(
|
||||||
'playlists/add',
|
'user/playlists/add',
|
||||||
method='post',
|
method='post',
|
||||||
json={
|
json={
|
||||||
'videoIds': [video_id],
|
'videoIds': [self._get_video_id(video_id)],
|
||||||
'playlistId': playlist_id,
|
'playlistId': playlist_id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -235,13 +244,15 @@ class YoutubePlugin(Plugin):
|
||||||
|
|
||||||
Note that either the video ID or the index must be provided.
|
Note that either the video ID or the index must be provided.
|
||||||
|
|
||||||
:param video_id: YouTube video ID.
|
:param video_id: YouTube video ID or URL.
|
||||||
:param index: (0-based) index of the video in the playlist.
|
:param index: (0-based) index of the video in the playlist.
|
||||||
:param playlist_id: Piped playlist ID.
|
:param playlist_id: Piped playlist ID.
|
||||||
"""
|
"""
|
||||||
assert video_id or index, 'Either the video ID or the index must be provided'
|
assert video_id or index, 'Either the video ID or the index must be provided'
|
||||||
|
|
||||||
if index is None:
|
if index is None:
|
||||||
|
assert video_id
|
||||||
|
video_id = self._get_video_id(video_id)
|
||||||
index = next(
|
index = next(
|
||||||
(
|
(
|
||||||
i
|
i
|
||||||
|
@ -250,7 +261,7 @@ class YoutubePlugin(Plugin):
|
||||||
'relatedStreams', []
|
'relatedStreams', []
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if v.get('id') == video_id
|
if self._get_video_id(v.get('url')) == video_id
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -262,7 +273,7 @@ class YoutubePlugin(Plugin):
|
||||||
return
|
return
|
||||||
|
|
||||||
self._request(
|
self._request(
|
||||||
'playlists/remove',
|
'user/playlists/remove',
|
||||||
method='post',
|
method='post',
|
||||||
json={
|
json={
|
||||||
'index': index,
|
'index': index,
|
||||||
|
@ -278,8 +289,11 @@ class YoutubePlugin(Plugin):
|
||||||
:param name: Playlist name.
|
:param name: Playlist name.
|
||||||
:return: Playlist information.
|
:return: Playlist information.
|
||||||
"""
|
"""
|
||||||
|
name = name.strip()
|
||||||
|
assert name, 'Playlist name cannot be empty'
|
||||||
|
|
||||||
playlist_id = self._request(
|
playlist_id = self._request(
|
||||||
'playlists/create',
|
'user/playlists/create',
|
||||||
method='post',
|
method='post',
|
||||||
json={'name': name},
|
json={'name': name},
|
||||||
).get('playlistId')
|
).get('playlistId')
|
||||||
|
@ -312,7 +326,7 @@ class YoutubePlugin(Plugin):
|
||||||
return
|
return
|
||||||
|
|
||||||
self._request(
|
self._request(
|
||||||
'playlists/rename',
|
'user/playlists/rename',
|
||||||
method='post',
|
method='post',
|
||||||
json={
|
json={
|
||||||
'playlistId': id,
|
'playlistId': id,
|
||||||
|
@ -328,7 +342,7 @@ class YoutubePlugin(Plugin):
|
||||||
:param id: Piped playlist ID.
|
:param id: Piped playlist ID.
|
||||||
"""
|
"""
|
||||||
self._request(
|
self._request(
|
||||||
'playlists/delete',
|
'user/playlists/delete',
|
||||||
method='post',
|
method='post',
|
||||||
json={'playlistId': id},
|
json={'playlistId': id},
|
||||||
)
|
)
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -66,7 +66,7 @@ backend = pkg_files('platypush/backend')
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="platypush",
|
name="platypush",
|
||||||
version="1.1.0",
|
version="1.1.1",
|
||||||
author="Fabio Manganiello",
|
author="Fabio Manganiello",
|
||||||
author_email="fabio@manganiello.tech",
|
author_email="fabio@manganiello.tech",
|
||||||
description="Platypush service",
|
description="Platypush service",
|
||||||
|
|
Loading…
Reference in a new issue