Skip to content

Commit

Permalink
feat: add Delete button to context menu
Browse files Browse the repository at this point in the history
* Split from: #1951
* Also add CanDelete to API requests
  • Loading branch information
noaione authored and ferferga committed Apr 27, 2023
1 parent 6fdc59f commit 95b1fe0
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 9 deletions.
6 changes: 5 additions & 1 deletion frontend/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
"customRating": "Custom rating",
"darkModeToggle": "Toggle dark mode",
"dateAdded": "Date added",
"delete": "Delete",
"deleteItem": "Delete media",
"deleteItemDescription": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"details": "Details",
"dialog": {
"upNext": {
Expand Down Expand Up @@ -79,6 +82,7 @@
},
"failedRetrievingDisplayPreferences": "Unable to get display preferences. Using last known settings.",
"failedSettingDisplayPreferences": "Unable to update display preferences.",
"failedToDeleteItem": "Failed to delete item",
"failedToRefreshItems": "Failed to refresh items",
"favorite": "Favorite",
"features": "Features",
Expand Down Expand Up @@ -561,4 +565,4 @@
"year": "Year",
"years": "Years",
"youMayAlsoLike": "You may also like"
}
}
86 changes: 86 additions & 0 deletions frontend/src/components/Dialogs/ConfirmDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<template>
<v-dialog
class="confirm-dialog"
content-class="confirm-dialog"
:model-value="dialog"
:fullscreen="$vuetify.display.mobile"
@update:model-value="close">
<v-card height="100%" class="d-flex width-90">
<v-card-title class="text-center font-weight-light mt-4">
{{ title }}
</v-card-title>
<v-card-subtitle v-if="subtitle" class="pb-3 text-center">
{{ subtitle }}
</v-card-subtitle>

<v-divider />

<v-card-text class="text-center font-weight-normal px-4">
{{ text }}
</v-card-text>

<v-divider />

<v-card-actions class="d-flex flex-row align-center justify-center mb-4">
<v-btn variant="flat" width="8em" color="secondary" @click="close">
{{ t('cancel') }}
</v-btn>

<v-btn
variant="flat"
width="8em"
:color="confirmColor ?? 'error'"
@click="closeAndConfirm">
{{ confirmText }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script setup lang="ts">
import { useI18n } from 'vue-i18n';
defineProps<{
dialog: boolean;
title: string;
text: string;
confirmText: string;
subtitle?: string;
confirmColor?: string;
}>();
const emit = defineEmits<{
(e: 'update:dialog', isOpen: boolean): void;
(e: 'onConfirm'): void;
}>();
const { t } = useI18n();
/**
* Close the dialog
*/
function close(): void {
emit('update:dialog', false);
}
/**
* Close the dialog and send confirmation event
*/
function closeAndConfirm(): void {
close();
emit('onConfirm');
}
</script>

<style lang="scss" scoped>
.confirm-dialog {
max-width: 100vw;
}
@media screen and (min-width: 600px) {
.confirm-dialog {
max-width: 70vw;
}
}
</style>
43 changes: 42 additions & 1 deletion frontend/src/components/Item/ItemMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
v-if="item.Id"
v-model:dialog="metadataDialog"
:item-id="item.Id" />
<confirm-dialog
v-if="item.Id"
v-model:dialog="deleteDialog"
:title="t('deleteItem')"
:text="t('deleteItemDescription')"
:confirm-text="t('delete')"
@on-confirm="onDeleteConfirmed" />
</template>

<script setup lang="ts">
Expand All @@ -47,14 +54,16 @@ import { getItemRefreshApi } from '@jellyfin/sdk/lib/utils/api/item-refresh-api'
import IMdiPlaySpeed from 'virtual:icons/mdi/play-speed';
import IMdiArrowExpandUp from 'virtual:icons/mdi/arrow-expand-up';
import IMdiArrowExpandDown from 'virtual:icons/mdi/arrow-expand-down';
import IMdiDelete from 'virtual:icons/mdi/delete';
import IMdiDisc from 'virtual:icons/mdi/disc';
import IMdiPlaylistMinus from 'virtual:icons/mdi/playlist-minus';
import IMdiPlaylistPlus from 'virtual:icons/mdi/playlist-plus';
import IMdiPencilOutline from 'virtual:icons/mdi/pencil-outline';
import IMdiShuffle from 'virtual:icons/mdi/shuffle';
import IMdiReplay from 'virtual:icons/mdi/replay';
import IMdiRefresh from 'virtual:icons/mdi/refresh';
import { useRemote, useSnackbar } from '@/composables';
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
import { useRemote, useRouter, useSnackbar } from '@/composables';
import { canInstantMix, canResume } from '@/utils/items';
import { TaskType } from '@/store/taskManager';
import { playbackManagerStore, taskManagerStore } from '@/store';
Expand All @@ -68,6 +77,7 @@ type MenuOption = {
const { t } = useI18n();
const remote = useRemote();
const router = useRouter();
const menuProps = withDefaults(
defineProps<{
Expand All @@ -90,6 +100,7 @@ const show = ref(false);
const positionX = ref<number | undefined>(undefined);
const positionY = ref<number | undefined>(undefined);
const metadataDialog = ref(false);
const deleteDialog = ref(false);
const playbackManager = playbackManagerStore();
const taskManager = taskManagerStore();
const isItemRefreshing = computed(
Expand Down Expand Up @@ -276,6 +287,16 @@ function getLibraryOptions(): MenuOption[] {
});
}
if (menuProps.item.CanDelete) {
libraryOptions.push({
title: t('deleteItem'),
icon: IMdiDelete,
action: (): void => {
deleteDialog.value = true;
}
});
}
return libraryOptions;
}
Expand All @@ -302,6 +323,26 @@ function onActivatorClick(): void {
positionY.value = undefined;
}
/**
* Handle deletion of the item
*/
async function onDeleteConfirmed(): Promise<void> {
if (!menuProps.item.Id) {
useSnackbar(t('failedToDeleteItem'), 'error');
return;
}
try {
await remote.sdk.newUserApi(getLibraryApi).deleteItem({
itemId: menuProps.item.Id
});
router.replace('/');
} catch {
useSnackbar(t('failedToDeleteItem'), 'error');
}
}
onMounted(() => {
const parentHtml = parent?.subTree.el;
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/components/Playback/TrackList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { groupBy } from 'lodash-es';
import { BaseItemDto, SortOrder } from '@jellyfin/sdk/lib/generated-client';
import {
BaseItemDto,
ItemFields,
SortOrder
} from '@jellyfin/sdk/lib/generated-client';
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
import { getItemDetailsLink } from '@/utils/items';
import { formatTicks } from '@/utils/time';
Expand All @@ -108,7 +112,8 @@ async function fetch(): Promise<void> {
userId: remote.auth.currentUserId || '',
parentId: props.item.Id,
sortBy: ['SortName'],
sortOrder: [SortOrder.Ascending]
sortOrder: [SortOrder.Ascending],
fields: [ItemFields.CanDelete]
})
).data.Items;
}
Expand Down
14 changes: 9 additions & 5 deletions frontend/src/store/userLibraries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ class UserLibrariesStore {
await remote.sdk.newUserApi(getItemsApi).getResumeItems({
userId: remote.auth.currentUserId || '',
limit: 24,
fields: [ItemFields.PrimaryImageAspectRatio],
fields: [ItemFields.PrimaryImageAspectRatio, ItemFields.CanDelete],
imageTypeLimit: 1,
enableImageTypes: [
ImageType.Primary,
Expand All @@ -174,7 +174,7 @@ class UserLibrariesStore {
await remote.sdk.newUserApi(getItemsApi).getResumeItems({
userId: remote.auth.currentUserId || '',
limit: 24,
fields: [ItemFields.PrimaryImageAspectRatio],
fields: [ItemFields.PrimaryImageAspectRatio, ItemFields.CanDelete],
imageTypeLimit: 1,
enableImageTypes: [
ImageType.Primary,
Expand All @@ -200,7 +200,7 @@ class UserLibrariesStore {
await remote.sdk.newUserApi(getTvShowsApi).getNextUp({
userId: remote.auth.currentUserId,
limit: 24,
fields: [ItemFields.PrimaryImageAspectRatio],
fields: [ItemFields.PrimaryImageAspectRatio, ItemFields.CanDelete],
imageTypeLimit: 1,
enableImageTypes: [
ImageType.Primary,
Expand Down Expand Up @@ -228,7 +228,7 @@ class UserLibrariesStore {
await remote.sdk.newUserApi(getUserLibraryApi).getLatestMedia({
userId: remote.auth.currentUserId || '',
limit: 24,
fields: [ItemFields.PrimaryImageAspectRatio],
fields: [ItemFields.PrimaryImageAspectRatio, ItemFields.CanDelete],
imageTypeLimit: 1,
enableImageTypes: [
ImageType.Primary,
Expand All @@ -253,7 +253,11 @@ class UserLibrariesStore {
await remote.sdk.newUserApi(getUserLibraryApi).getLatestMedia({
userId: remote.auth.currentUserId || '',
limit: 10,
fields: [ItemFields.Overview, ItemFields.PrimaryImageAspectRatio],
fields: [
ItemFields.Overview,
ItemFields.PrimaryImageAspectRatio,
ItemFields.CanDelete
],
enableImageTypes: [ImageType.Backdrop, ImageType.Logo],
imageTypeLimit: 1
})
Expand Down
1 change: 1 addition & 0 deletions frontend/types/global/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ declare module '@vue/runtime-core' {
CastButton: typeof import('./../../src/components/Layout/AppBar/Buttons/CastButton.vue')['default']
CollectionTabs: typeof import('./../../src/components/Item/CollectionTabs.vue')['default']
CommitLink: typeof import('./../../src/components/Layout/Navigation/CommitLink.vue')['default']
ConfirmDialog: typeof import('./../../src/components/Dialogs/ConfirmDialog.vue')['default']
DateInput: typeof import('./../../src/components/Item/Metadata/DateInput.vue')['default']
DraggableQueue: typeof import('./../../src/components/Playback/DraggableQueue.vue')['default']
FilterButton: typeof import('./../../src/components/Buttons/FilterButton.vue')['default']
Expand Down

0 comments on commit 95b1fe0

Please sign in to comment.