Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 93 additions & 18 deletions src/layouts/default/AddToPlaylistDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,19 @@
<script setup lang="ts">
import MediaItemThumb from "@/components/MediaItemThumb.vue";
import ProviderIcon from "@/components/ProviderIcon.vue";
import { MediaType } from "@/plugins/api/interfaces";
import Toolbar from "@/components/Toolbar.vue";
import api from "@/plugins/api";
import type {
BrowseFolder,
MediaItemType,
MediaItemTypeOrItemMapping,
Playlist,
Track,
} from "@/plugins/api/interfaces";
import { onBeforeUnmount, onMounted, ref } from "vue";
import { ProviderFeature } from "@/plugins/api/interfaces";
import api from "@/plugins/api";
import { AlertType, store } from "@/plugins/store";
import { MediaType, ProviderFeature } from "@/plugins/api/interfaces";
import { eventbus, PlaylistDialogEvent } from "@/plugins/eventbus";
import Toolbar from "@/components/Toolbar.vue";
import { $t } from "@/plugins/i18n";
import { AlertType, store } from "@/plugins/store";
import { onBeforeUnmount, onMounted, ref } from "vue";

const show = ref<boolean>(false);
const playlists = ref<Playlist[]>([]);
Expand Down Expand Up @@ -192,18 +191,94 @@ const fetchPlaylists = async function () {
}
}
};

const expandFolderToTracks = async function (
folder: BrowseFolder,
visitedPaths: Set<string> = new Set(),
depth: number = 0,
): Promise<string[]> {
const trackUris: string[] = [];

if (depth > 10) {
console.warn("Maximum folder depth reached, stopping recursion");
return trackUris;
}

if (!folder.path || visitedPaths.has(folder.path)) {
console.warn("Skipping folder to prevent infinite recursion:", folder.path);
return trackUris;
}

visitedPaths.add(folder.path);

try {
const folderContents = await api.browse(folder.path);

for (const item of folderContents) {
if (item.media_type === MediaType.TRACK) {
trackUris.push(item.uri);
} else if (item.media_type === MediaType.FOLDER) {
if (item.name === "..") {
continue;
}
// Recursively get tracks from subfolders with depth limit
const subfolderTracks = await expandFolderToTracks(
item as BrowseFolder,
visitedPaths,
depth + 1,
);
trackUris.push(...subfolderTracks);
}
}
} catch (error) {
console.error("Error expanding folder:", folder.path, error);
}

return trackUris;
};

const addToPlaylist = async function (value: MediaItemType) {
/// add item(s) to playlist
api.addPlaylistTracks(
value.item_id,
selectedItems.value.map((x) => x.uri),
);
close();
store.activeAlert = {
type: AlertType.INFO,
message: $t("background_task_added"),
persistent: false,
};
let urisToAdd: string[] = [];

try {
for (const item of selectedItems.value) {
if (item.media_type === MediaType.FOLDER) {
const folderTracks = await expandFolderToTracks(item as BrowseFolder);

urisToAdd.push(...folderTracks);
} else {
urisToAdd.push(item.uri);
}
}

if (urisToAdd.length === 0) {
store.activeAlert = {
type: AlertType.ERROR,
message: "No tracks found in the selected folder(s)",
persistent: false,
};
close();
return;
}

await api.addPlaylistTracks(value.item_id, urisToAdd);

close();

store.activeAlert = {
type: AlertType.INFO,
message: $t("background_task_added"),
persistent: false,
};
} catch (error) {
console.error("Error adding folder to playlist:", error);
store.activeAlert = {
type: AlertType.ERROR,
message: "Failed to add folder to playlist",
persistent: false,
};
close();
}
};
const newPlaylist = async function (provId: string) {
const name = prompt($t("new_playlist_name"));
Expand Down
30 changes: 16 additions & 14 deletions src/layouts/default/ItemContextMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@
</template>

<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref } from "vue";
import api from "@/plugins/api";
import { store } from "@/plugins/store";
import { ContextMenuDialogEvent, eventbus } from "@/plugins/eventbus";
import { store } from "@/plugins/store";
import { nextTick, onBeforeUnmount, onMounted, ref } from "vue";

const show = ref<boolean>(false);
const items = ref<ContextMenuItem[]>([]);
Expand Down Expand Up @@ -201,22 +201,22 @@ const playMenuHeaderClicked = function (evt: MouseEvent | KeyboardEvent) {

import router from "@/plugins/router";

import { playerVisible } from "@/helpers/utils";
import { itemIsAvailable } from "@/plugins/api/helpers";
import {
ProviderFeature,
Album,
BrowseFolder,
MediaItem,
QueueOption,
MediaItemType,
MediaItemTypeOrItemMapping,
MediaType,
Playlist,
Album,
Track,
MediaItemType,
PodcastEpisode,
MediaItemTypeOrItemMapping,
BrowseFolder,
ProviderFeature,
QueueOption,
Track,
} from "@/plugins/api/interfaces";
import { $t } from "@/plugins/i18n";
import { itemIsAvailable } from "@/plugins/api/helpers";
import { playerVisible } from "@/helpers/utils";

export interface ContextMenuItem {
label: string;
Expand Down Expand Up @@ -259,7 +259,8 @@ export const showContextMenuForMediaItem = async function (
mediaItems[0].is_playable &&
itemIsAvailable(mediaItems[0])
) {
menuItems.push(...(await getPlayMenuItems(mediaItems, parentItem)));
const playMenuItems = await getPlayMenuItems(mediaItems, parentItem);
menuItems.push(...playMenuItems);
}

if (menuItems.length == 0) return;
Expand Down Expand Up @@ -789,10 +790,11 @@ export const getContextMenuItems = async function (
});
}
}
// add to playlist action (tracks only)
// add to playlist action (tracks, albums, and folders)
if (
items[0].media_type === MediaType.TRACK ||
items[0].media_type === MediaType.ALBUM
items[0].media_type === MediaType.ALBUM ||
items[0].media_type === MediaType.FOLDER
) {
contextMenuItems.push({
label: "add_playlist",
Expand Down
Loading