Skip to content

Commit ca760ed

Browse files
Fix merge
2 parents c61d76b + c0e0d87 commit ca760ed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+5099
-184
lines changed

src/App.vue

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<script setup lang="ts">
3434
import { Toaster } from "@/components/ui/sonner";
3535
import { api, ConnectionState } from "@/plugins/api";
36-
import { CoreState, EventType } from "@/plugins/api/interfaces";
36+
import { CoreState, EventType, ProviderType } from "@/plugins/api/interfaces";
3737
import { toast } from "vue-sonner";
3838
import { getDeviceName } from "@/plugins/api/helpers";
3939
import authManager from "@/plugins/auth";
@@ -219,24 +219,49 @@ const completeInitialization = async () => {
219219
webPlayer.setBaseUrl(api.baseUrl);
220220
}
221221
222-
await api.fetchState();
223-
store.libraryArtistsCount = await api.getLibraryArtistsCount();
224-
store.libraryAlbumsCount = await api.getLibraryAlbumsCount();
225-
store.libraryPlaylistsCount = await api.getLibraryPlaylistsCount();
226-
store.libraryRadiosCount = await api.getLibraryRadiosCount();
227-
store.libraryTracksCount = await api.getLibraryTracksCount();
228-
store.libraryPodcastsCount = await api.getLibraryPodcastsCount();
229-
store.libraryAudiobooksCount = await api.getLibraryAudiobooksCount();
230-
store.libraryGenresCount = await api.getLibraryGenresCount();
222+
const isPartyModeGuest = authManager.isPartyModeGuest();
223+
224+
if (!isPartyModeGuest) {
225+
// Full initialization for regular and non-party guest users
226+
await api.fetchState();
227+
store.libraryArtistsCount = await api.getLibraryArtistsCount();
228+
store.libraryAlbumsCount = await api.getLibraryAlbumsCount();
229+
store.libraryPlaylistsCount = await api.getLibraryPlaylistsCount();
230+
store.libraryRadiosCount = await api.getLibraryRadiosCount();
231+
store.libraryTracksCount = await api.getLibraryTracksCount();
232+
store.libraryPodcastsCount = await api.getLibraryPodcastsCount();
233+
store.libraryAudiobooksCount = await api.getLibraryAudiobooksCount();
234+
store.libraryGenresCount = await api.getLibraryGenresCount();
235+
} else {
236+
console.debug("[App] Party mode guest - skipping full state fetch");
237+
}
238+
239+
// Check if party mode plugin is enabled
240+
try {
241+
const partyModeProviders = await api.getProviderConfigs(
242+
ProviderType.PLUGIN,
243+
"party_mode",
244+
);
245+
if (partyModeProviders.length > 0 && partyModeProviders[0].enabled) {
246+
store.enabledPlugins.add("party_mode");
247+
} else {
248+
store.enabledPlugins.delete("party_mode");
249+
}
250+
} catch (error) {
251+
console.error("[App] Failed to check party mode status:", error);
252+
store.enabledPlugins.delete("party_mode");
253+
}
231254
232255
// Enable Sendspin if available and not explicitly disabled
233256
// Sendspin works over WebRTC DataChannel which requires signaling via the API server
234257
const webPlayerEnabledPref =
235258
localStorage.getItem("frontend.settings.web_player_enabled") || "true";
236259
const browserControlsEnabledPref =
237260
localStorage.getItem("frontend.settings.enable_browser_controls") || "true";
238-
if (companionMode.value) {
239-
// the webplayer is completely disabled if we're running companion mode (no sendspin, no controls)
261+
262+
// Disable web player for party mode guests, companion mode, and party dashboard
263+
const isPartyDashboard = router.currentRoute.value.path.startsWith("/party");
264+
if (isPartyModeGuest || companionMode.value || isPartyDashboard) {
240265
webPlayer.setMode(WebPlayerMode.DISABLED);
241266
} else if (
242267
webPlayerEnabledPref !== "false" &&
@@ -268,6 +293,9 @@ const completeInitialization = async () => {
268293
) {
269294
store.isOnboarding = true;
270295
router.push("/settings/providers");
296+
} else if (isPartyModeGuest) {
297+
// Party mode guests should always be redirected to the guest view
298+
router.push("/guest");
271299
}
272300
// Don't push to any route here - let the router handle navigation naturally
273301
// from the URL hash. The router config already redirects "/" to "/discover"
@@ -408,6 +436,23 @@ onMounted(async () => {
408436
) {
409437
await completeInitialization();
410438
}
439+
440+
// Subscribe to PROVIDERS_UPDATED to keep enabledPlugins in sync
441+
api.subscribe(EventType.PROVIDERS_UPDATED, async () => {
442+
try {
443+
const partyModeProviders = await api.getProviderConfigs(
444+
ProviderType.PLUGIN,
445+
"party_mode",
446+
);
447+
if (partyModeProviders.length > 0 && partyModeProviders[0].enabled) {
448+
store.enabledPlugins.add("party_mode");
449+
} else {
450+
store.enabledPlugins.delete("party_mode");
451+
}
452+
} catch (error) {
453+
console.error("[App] Failed to update party mode status:", error);
454+
}
455+
});
411456
});
412457
413458
onUnmounted(() => {

src/components/ItemsListing.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,11 @@ const redirectSearch = function () {
680680
router.push({ name: "search" });
681681
};
682682
683-
const loadNextPage = async function ({ done }: { done: (status: "ok" | "empty" | "loading" | "error") => void }) {
683+
const loadNextPage = async function ({
684+
done,
685+
}: {
686+
done: (status: "ok" | "empty" | "loading" | "error") => void;
687+
}) {
684688
if (allItemsReceived.value) {
685689
done("empty");
686690
return;
@@ -1380,7 +1384,9 @@ onMounted(async () => {
13801384
if (idx >= 0) {
13811385
const playData = evt.data as Record<string, unknown>;
13821386
if ("fully_played" in pagedItems.value[idx])
1383-
pagedItems.value[idx].fully_played = playData["fully_played"] as boolean;
1387+
pagedItems.value[idx].fully_played = playData[
1388+
"fully_played"
1389+
] as boolean;
13841390
if ("resume_position_ms" in pagedItems.value[idx])
13851391
pagedItems.value[idx].resume_position_ms =
13861392
(playData["seconds_played"] as number) * 1000;

src/components/navigation/AppSidebar.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,15 @@ import { getMenuItems } from "./utils/getMenuItems";
1919
const router = useRouter();
2020
const { t } = useI18n();
2121
22-
const menuItems = getMenuItems();
23-
2422
const navItems = computed(() => {
25-
return menuItems
23+
return getMenuItems()
2624
.filter((item) => !item.hidden)
2725
.map((item) => ({
2826
title: t(item.label),
2927
url: item.path,
3028
icon: item.icon,
3129
disabled: item.disabled,
30+
openInNewTab: item.openInNewTab,
3231
}));
3332
});
3433

src/components/navigation/NavMain.vue

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { markRaw, type Component } from "vue";
3-
import { RouterLink, useRoute } from "vue-router";
3+
import { RouterLink, useRoute, useRouter } from "vue-router";
44
55
const RouterLinkComponent = markRaw(RouterLink);
66
@@ -18,19 +18,25 @@ interface NavItem {
1818
url: string;
1919
icon?: Component;
2020
disabled?: boolean;
21+
openInNewTab?: boolean;
2122
}
2223
2324
const props = defineProps<{
2425
items: NavItem[];
2526
}>();
2627
2728
const route = useRoute();
29+
const router = useRouter();
2830
const { isMobile, setOpenMobile } = useSidebar();
2931
3032
const isActive = (url: string) =>
3133
route.path === url || route.path.startsWith(url + "/");
3234
33-
const handleClick = () => {
35+
const handleClick = (item: NavItem, event: Event) => {
36+
if (item.openInNewTab) {
37+
event.preventDefault();
38+
window.open(router.resolve(item.url).href, "_blank");
39+
}
3440
if (isMobile.value) {
3541
setOpenMobile(false);
3642
}
@@ -43,8 +49,12 @@ const handleClick = () => {
4349
<SidebarMenu>
4450
<SidebarMenuItem v-for="item in items" :key="item.title" class="mr-1.5">
4551
<SidebarMenuButton
46-
:as="item.disabled ? 'button' : RouterLinkComponent"
47-
v-bind="item.disabled ? {} : { to: item.url }"
52+
:as="
53+
item.disabled || item.openInNewTab
54+
? 'button'
55+
: RouterLinkComponent
56+
"
57+
v-bind="item.disabled || item.openInNewTab ? {} : { to: item.url }"
4858
:is-active="isActive(item.url)"
4959
:tooltip="item.title"
5060
:disabled="item.disabled"
@@ -54,7 +64,7 @@ const handleClick = () => {
5464
: 'no-underline font-medium text-sm',
5565
item.disabled ? 'opacity-50 cursor-not-allowed' : '',
5666
]"
57-
@click="handleClick"
67+
@click="(e: Event) => handleClick(item, e)"
5868
>
5969
<component
6070
:is="item.icon"

src/components/navigation/utils/getMenuItems.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
House,
99
ListMusic,
1010
Music2,
11+
PartyPopper,
1112
Podcast,
1213
Radio,
1314
Search,
@@ -23,6 +24,7 @@ export interface MenuItem {
2324
isLibraryNode: boolean;
2425
hidden?: boolean;
2526
disabled?: boolean;
27+
openInNewTab?: boolean;
2628
}
2729

2830
export const getMenuItems = function () {
@@ -52,6 +54,16 @@ export const getMenuItems = function () {
5254
isLibraryNode: false,
5355
});
5456
}
57+
if (enabledMenuItemStr === "party") {
58+
items.push({
59+
label: "Party Mode",
60+
icon: PartyPopper,
61+
path: "/party",
62+
isLibraryNode: false,
63+
hidden: !store.enabledPlugins.has("party_mode"),
64+
openInNewTab: true,
65+
});
66+
}
5567
if (enabledMenuItemStr === "artists") {
5668
items.push({
5769
label: "artists",
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<template>
2+
<span class="party-mode-badge">
3+
<v-icon size="x-small">{{ icon }}</v-icon>
4+
{{ label }}
5+
</span>
6+
</template>
7+
8+
<script setup lang="ts">
9+
import { $t } from "@/plugins/i18n";
10+
import { computed } from "vue";
11+
12+
interface Props {
13+
type: "request" | "boost";
14+
badgeColor?: string;
15+
}
16+
17+
const props = defineProps<Props>();
18+
19+
const icon = computed(() =>
20+
props.type === "boost" ? "mdi-rocket-launch" : "mdi-account-music",
21+
);
22+
23+
const label = computed(() =>
24+
props.type === "boost"
25+
? $t("providers.party_mode.boost")
26+
: $t("providers.party_mode.request"),
27+
);
28+
</script>
29+
30+
<style scoped>
31+
.party-mode-badge {
32+
display: inline-flex;
33+
align-items: center;
34+
gap: 0.25rem;
35+
background: v-bind("props.badgeColor");
36+
padding: 2px 8px;
37+
border-radius: 999px;
38+
font-size: 0.55rem;
39+
font-weight: 600;
40+
text-transform: uppercase;
41+
letter-spacing: 0.5px;
42+
color: #fff;
43+
margin-right: 0.5rem;
44+
}
45+
</style>

0 commit comments

Comments
 (0)