diff --git a/src/components/InfoHeader.vue b/src/components/InfoHeader.vue
index e78a204c9..7fd6c9c6a 100644
--- a/src/components/InfoHeader.vue
+++ b/src/components/InfoHeader.vue
@@ -377,6 +377,9 @@
outlined
class="cursor-pointer"
@click="handleMediaItemClick(genre, 0, 0)"
+ @contextmenu.prevent="
+ (e: MouseEvent) => showGenreChipContextMenu(e, genre)
+ "
>
{{
getGenreDisplayName(genre.name, genre.translation_key, t, te)
@@ -506,6 +509,38 @@ watch(
{ immediate: true },
);
+const showGenreChipContextMenu = (evt: MouseEvent, genre: Genre) => {
+ if (
+ !compProps.item ||
+ !isAdmin.value ||
+ compProps.item.provider !== "library"
+ )
+ return;
+ const mediaItem = compProps.item;
+ const menuItems: ContextMenuItem[] = [
+ {
+ label: "exclude_genre",
+ icon: "mdi-cancel",
+ action: async () => {
+ await api.excludeGenreFromItem(
+ genre.item_id,
+ mediaItem.media_type,
+ mediaItem.item_id,
+ );
+ mappedGenres.value = mappedGenres.value.filter(
+ (g) => g.item_id !== genre.item_id,
+ );
+ eventbus.emit("genreExcluded");
+ },
+ },
+ ];
+ eventbus.emit("contextmenu", {
+ items: menuItems,
+ posX: evt.clientX,
+ posY: evt.clientY,
+ });
+};
+
const albumClick = function (item: Album | ItemMapping) {
// album entry clicked
router.push({
diff --git a/src/components/ProviderDetails.vue b/src/components/ProviderDetails.vue
index e6a8b5eff..9f119d7d4 100644
--- a/src/components/ProviderDetails.vue
+++ b/src/components/ProviderDetails.vue
@@ -125,10 +125,19 @@
+
diff --git a/src/plugins/api/index.ts b/src/plugins/api/index.ts
index ee45791bb..206c92858 100644
--- a/src/plugins/api/index.ts
+++ b/src/plugins/api/index.ts
@@ -1033,6 +1033,40 @@ export class MusicAssistantApi {
});
}
+ public excludeGenreFromItem(
+ genre_id: string,
+ media_type: string,
+ media_id: string,
+ ): Promise {
+ return this.sendCommand("music/genres/exclude_genre_from_media_item", {
+ genre_id,
+ media_type,
+ media_id,
+ });
+ }
+
+ public removeGenreExclusion(
+ genre_id: string,
+ media_type: string,
+ media_id: string,
+ ): Promise {
+ return this.sendCommand("music/genres/remove_genre_exclusion", {
+ genre_id,
+ media_type,
+ media_id,
+ });
+ }
+
+ public getGenreExclusionsForItem(
+ media_type: string,
+ media_id: string,
+ ): Promise {
+ return this.sendCommand("music/genres/genre_exclusions_for_media_item", {
+ media_type,
+ media_id,
+ });
+ }
+
public async getGenreOverviewRows(
item_id: string,
provider_instance_id_or_domain: string,
diff --git a/src/plugins/eventbus.ts b/src/plugins/eventbus.ts
index baaaa8f80..92b5440f7 100644
--- a/src/plugins/eventbus.ts
+++ b/src/plugins/eventbus.ts
@@ -43,6 +43,7 @@ export type Events = {
deleteGenreDialog: DeleteGenreDialogEvent;
linkGenreDialog: LinkGenreDialogEvent;
clearSelection: void;
+ genreExcluded: void;
"homescreen-edit-toggle": void;
"mobile-sidebar-open": void;
};
diff --git a/src/translations/en.json b/src/translations/en.json
index 353763dbe..e002e1cd7 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -317,6 +317,9 @@
"remove": "Remove",
"remove_alias": "Remove alias",
"remove_alias_failed": "Failed to remove alias",
+ "exclude_genre": "Exclude genre",
+ "genre_exclusions": "Genre Exclusions",
+ "remove_genre_exclusion": "Remove exclusion",
"remove_library": "Remove from library",
"remove_playlist": "Remove from playlist",
"search": "Search",