[Youtube UI] More playlist actions.

- `create_playlist`
- `rename_playlist`
- `delete_playlist`
This commit is contained in:
Fabio Manganiello 2024-06-27 01:26:00 +02:00
parent d4354e81f8
commit 98a6adb7ef
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
5 changed files with 229 additions and 4 deletions

View file

@ -1,5 +1,5 @@
<template> <template>
<Modal ref="modal" :title="title"> <Modal ref="modal" :title="title" @close="close">
<div class="dialog-content"> <div class="dialog-content">
<slot /> <slot />
</div> </div>
@ -19,7 +19,7 @@
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
export default { export default {
emits: ['input', 'click', 'touch'], emits: ['input', 'click', 'close', 'touch'],
components: {Modal}, components: {Modal},
props: { props: {
title: { title: {
@ -49,6 +49,7 @@ export default {
close() { close() {
this.$refs.modal.hide() this.$refs.modal.hide()
this.$emit('close')
}, },
show() { show() {

View file

@ -39,7 +39,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.floating-btn { .floating-btn {
position: fixed; position: absolute;
bottom: 0; bottom: 0;
right: 0; right: 0;
margin: auto 1em 1em auto; margin: auto 1em 1em auto;

View file

@ -59,7 +59,10 @@ export default {
methods: { methods: {
onConfirm() { onConfirm() {
if (this.value_?.trim()?.length) {
this.$emit('input', this.value_) this.$emit('input', this.value_)
}
this.close() this.close()
}, },
@ -120,6 +123,8 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.modal) { :deep(.modal) {
.dialog-content { .dialog-content {
display: flex;
flex-direction: column;
padding: 1em; padding: 1em;
} }

View file

@ -112,6 +112,15 @@ export default {
} }
}, },
async createPlaylist(name) {
this.loading = true
try {
await this.request('youtube.create_playlist', {name: name})
} finally {
this.loading = false
}
},
selectView(view) { selectView(view) {
this.selectedView = view this.selectedView = view
if (view === 'playlists') if (view === 'playlists')

View file

@ -13,6 +13,14 @@
@click="$emit('select', playlist)"> @click="$emit('select', playlist)">
<MediaImage :item="playlist" :has-play="false" /> <MediaImage :item="playlist" :has-play="false" />
<div class="title">{{ playlist.name }}</div> <div class="title">{{ playlist.name }}</div>
<div class="actions">
<button title="Remove" @click.stop="deletedPlaylist = playlist.id">
<i class="fa fa-trash" />
</button>
<button title="Edit" @click.stop="editedPlaylist = playlist.id">
<i class="fa fa-pencil" />
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -27,14 +35,74 @@
@play="$emit('play', $event)" @play="$emit('play', $event)"
/> />
</div> </div>
<TextPrompt
:visible="showCreatePlaylist"
@input="createPlaylist($event)"
@close="showCreatePlaylist = false"
>
Playlist name
</TextPrompt>
<ConfirmDialog
ref="removePlaylist"
title="Remove Playlist"
:visible="deletedPlaylist != null"
@close="deletedPlaylist = null"
@input="removePlaylist"
>
Are you sure you want to remove this playlist?
</ConfirmDialog>
<Modal
ref="editPlaylist"
title="Edit Playlist"
:visible="editedPlaylist != null"
@close="clearEditPlaylist"
@open="onEditPlaylistOpen"
>
<form class="edit-playlist-form" @submit.prevent="editPlaylist">
<div class="row">
<input ref="editPlaylistName" placeholder="Playlist name" v-model="editedPlaylistName" />
</div>
<div class="row">
<input placeholder="Playlist description" v-model="editedPlaylistDescription" />
</div>
<div class="row buttons">
<div class="btn-container col-6">
<button type="submit">
<i class="fa fa-check" />&nbsp;Save
</button>
</div>
<div class="btn-container col-6">
<button @click="clearEditPlaylist">
<i class="fa fa-times" />&nbsp;Cancel
</button>
</div>
</div>
</form>
</Modal>
<FloatingButton
icon-class="fa fa-plus"
title="Create Playlist"
@click="showCreatePlaylist = true"
/>
</div> </div>
</template> </template>
<script> <script>
import ConfirmDialog from "@/components/elements/ConfirmDialog";
import FloatingButton from "@/components/elements/FloatingButton";
import MediaImage from "@/components/panels/Media/MediaImage"; import MediaImage from "@/components/panels/Media/MediaImage";
import Modal from "@/components/Modal";
import NoItems from "@/components/elements/NoItems"; import NoItems from "@/components/elements/NoItems";
import Loading from "@/components/Loading"; import Loading from "@/components/Loading";
import Playlist from "./Playlist"; import Playlist from "./Playlist";
import TextPrompt from "@/components/elements/TextPrompt"
import Utils from "@/Utils"; import Utils from "@/Utils";
export default { export default {
@ -50,10 +118,14 @@ export default {
], ],
components: { components: {
ConfirmDialog,
FloatingButton,
Loading, Loading,
MediaImage, MediaImage,
Modal,
NoItems, NoItems,
Playlist, Playlist,
TextPrompt,
}, },
props: { props: {
@ -70,8 +142,13 @@ export default {
data() { data() {
return { return {
deletedPlaylist: null,
editedPlaylist: null,
editedPlaylistName: '',
editedPlaylistDescription: '',
playlists: [], playlists: [],
loading: false, loading: false,
showCreatePlaylist: false,
} }
}, },
@ -95,6 +172,64 @@ export default {
this.loading = false this.loading = false
} }
}, },
async createPlaylist(name) {
this.loading = true
try {
await this.request('youtube.create_playlist', {name: name})
this.showCreatePlaylist = false
this.loadPlaylists()
} finally {
this.loading = false
}
},
async removePlaylist() {
if (!this.deletedPlaylist)
return
this.loading = true
try {
await this.request('youtube.delete_playlist', {id: this.deletedPlaylist})
this.deletedPlaylist = null
this.loadPlaylists()
} finally {
this.loading = false
}
},
async editPlaylist() {
if (!this.editedPlaylist)
return
this.loading = true
try {
await this.request('youtube.rename_playlist', {
id: this.editedPlaylist,
name: this.editedPlaylistName,
description: this.editedPlaylistDescription,
})
this.clearEditPlaylist()
this.loadPlaylists()
} finally {
this.loading = false
}
},
clearEditPlaylist() {
this.editedPlaylist = null
this.editedPlaylistName = ''
this.editedPlaylistDescription = ''
this.$refs.editPlaylist.hide()
},
onEditPlaylistOpen() {
const playlist = this.playlistsById[this.editedPlaylist]
this.editedPlaylistName = playlist.name
this.editedPlaylistDescription = playlist.description
this.$nextTick(() => this.$refs.editPlaylistName.focus())
},
}, },
mounted() { mounted() {
@ -106,12 +241,14 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.media-youtube-playlists { .media-youtube-playlists {
height: 100%; height: 100%;
position: relative;
.playlist-body { .playlist-body {
height: 100%; height: 100%;
} }
:deep(.playlist.item) { :deep(.playlist.item) {
position: relative;
cursor: pointer; cursor: pointer;
.title { .title {
@ -119,6 +256,33 @@ export default {
margin-top: 0.5em; margin-top: 0.5em;
} }
.actions {
position: absolute;
top: 0;
right: 0;
display: flex;
padding: 0.5em;
background: rgba(0, 0, 0, 0.25);
opacity: 0.9;
border-radius: 0 0 0.5em 0.5em;
transition: opacity 0.2s;
z-index: 1;
button {
background: none;
border: none;
color: white;
cursor: pointer;
transition: transform 0.2s;
flex: 1;
&:hover {
color: $default-hover-fg;
transform: scale(1.2);
}
}
}
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
@ -127,5 +291,51 @@ export default {
} }
} }
} }
:deep(.modal) {
.edit-playlist-form {
min-width: 300px;
display: flex;
flex-direction: column;
.row {
margin: 0.5em 0;
input {
border: $default-border;
border-radius: 1em;
padding: 0.5em;
width: 100%;
&:focus {
border: 1px solid $selected-fg;
}
}
&.buttons {
display: flex;
justify-content: flex-end;
margin-top: 0.5em;
.btn-container {
display: flex;
justify-content: center;
}
}
button {
margin: 0 0.5em;
padding: 0.5em;
border-radius: 1em;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: $hover-bg;
}
}
}
}
}
} }
</style> </style>