Skip to content
Merged
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
35 changes: 35 additions & 0 deletions src/components/InfoHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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({
Expand Down
9 changes: 9 additions & 0 deletions src/components/ProviderDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,19 @@
</v-list>
</Container>
</section>
<GenreExclusionManager
v-if="
itemDetails.provider === 'library' &&
itemDetails.media_type !== MediaType.GENRE
"
:media-type="itemDetails.media_type"
:media-id="itemDetails.item_id"
/>
</template>

<script setup lang="ts">
import Container from "@/components/Container.vue";
import GenreExclusionManager from "@/components/genre/GenreExclusionManager.vue";
import ListItem from "@/components/ListItem.vue";
import ProviderIcon from "@/components/ProviderIcon.vue";
import { iconHiRes } from "@/components/QualityDetailsBtn.vue";
Expand Down
139 changes: 139 additions & 0 deletions src/components/genre/GenreExclusionManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<template>
<section v-if="isAdmin" style="margin-bottom: 10px">
<Toolbar
:title="exclusionTitle"
:menu-items="toolbarMenuItems"
@title-clicked="toggleSection"
/>
<v-divider />
<Container v-if="sectionExpanded">
<v-list>
<ListItem
v-for="genre in exclusions"
:key="genre.item_id"
show-menu-btn
@menu.stop="(evt) => onMenu(evt, genre)"
>
<template #prepend>
<div
style="
width: 30px;
margin-left: 10px;
margin-right: 10px;
display: flex;
align-items: center;
"
>
<Compass :size="30" />
</div>
</template>
<template #title>{{
getGenreDisplayName(genre.name, genre.translation_key, t, te)
}}</template>
</ListItem>
</v-list>
</Container>
</section>
</template>

<script setup lang="ts">
import Container from "@/components/Container.vue";
import ListItem from "@/components/ListItem.vue";
import Toolbar, { ToolBarMenuItem } from "@/components/Toolbar.vue";
import { getGenreDisplayName } from "@/helpers/utils";
import { api } from "@/plugins/api";
import { Genre, MediaType } from "@/plugins/api/interfaces";
import { authManager } from "@/plugins/auth";
import { eventbus } from "@/plugins/eventbus";
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { Compass, ChevronUp, ChevronDown } from "lucide-vue-next";

interface Props {
mediaType: MediaType;
mediaId: string;
}

const props = defineProps<Props>();

const { t, te } = useI18n();

const isAdmin = computed(() => authManager.isAdmin());
const sectionExpanded = ref(false);
const operationInProgress = ref(false);
const exclusions = ref<Genre[]>([]);

const exclusionTitle = computed(
() => `${t("genre_exclusions")} (${exclusions.value.length})`,
);

const toolbarMenuItems = computed<ToolBarMenuItem[]>(() => [
{
label: "tooltip.collapse_expand",
icon: sectionExpanded.value ? ChevronUp : ChevronDown,
action: toggleSection,
overflowAllowed: false,
},
]);

const loadExclusions = async () => {
try {
exclusions.value = await api.getGenreExclusionsForItem(
props.mediaType,
props.mediaId,
);
} catch {
exclusions.value = [];
}
};

const removeExclusion = async (genre: Genre) => {
operationInProgress.value = true;
try {
await api.removeGenreExclusion(
genre.item_id,
props.mediaType,
props.mediaId,
);
exclusions.value = exclusions.value.filter(
(g) => g.item_id !== genre.item_id,
);
} finally {
operationInProgress.value = false;
}
};

const onMenu = (evt: Event, genre: Genre) => {
const mouseEvt = evt as MouseEvent;
eventbus.emit("contextmenu", {
items: [
{
label: "remove_genre_exclusion",
icon: "mdi-delete",
action: () => removeExclusion(genre),
disabled: operationInProgress.value,
},
],
posX: mouseEvt.clientX,
posY: mouseEvt.clientY,
});
};

const toggleSection = () => {
sectionExpanded.value = !sectionExpanded.value;
};

onMounted(() => {
loadExclusions();
eventbus.on("genreExcluded", loadExclusions);
});

onBeforeUnmount(() => {
eventbus.off("genreExcluded", loadExclusions);
});

watch(
() => props.mediaId,
() => loadExclusions(),
);
</script>
34 changes: 34 additions & 0 deletions src/plugins/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,40 @@ export class MusicAssistantApi {
});
}

public excludeGenreFromItem(
genre_id: string,
media_type: string,
media_id: string,
): Promise<void> {
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<void> {
return this.sendCommand("music/genres/remove_genre_exclusion", {
genre_id,
media_type,
media_id,
});
}

public getGenreExclusionsForItem(
media_type: string,
media_id: string,
): Promise<Genre[]> {
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,
Expand Down
1 change: 1 addition & 0 deletions src/plugins/eventbus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type Events = {
deleteGenreDialog: DeleteGenreDialogEvent;
linkGenreDialog: LinkGenreDialogEvent;
clearSelection: void;
genreExcluded: void;
"homescreen-edit-toggle": void;
"mobile-sidebar-open": void;
};
Expand Down
3 changes: 3 additions & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading