-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathusePlayerBarLayout.ts
More file actions
149 lines (136 loc) Β· 4.78 KB
/
usePlayerBarLayout.ts
File metadata and controls
149 lines (136 loc) Β· 4.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { useEffect, useState } from "react";
import { getProfileSetting } from "../lib/tauri/profile";
/**
* What clicking the small cover thumbnail on the left side of the
* player bar does. Mirrors common patterns from Spotify (opens the
* Now Playing right panel) and Apple Music (opens immersive full
* screen). `none` opts out entirely for users who keep clicking it
* by accident.
*/
export type CoverAction = "none" | "now_playing" | "immersive";
/**
* Resolved per-button visibility for the player bar plus the
* cover-click behaviour. Backed by `profile_setting` rows so the
* choice is per-listener (a kid's profile may want a calmer bar).
*
* Defaults are picked to match the pre-customization behaviour so
* existing users see zero change after the upgrade.
*/
export interface PlayerBarLayout {
showLyrics: boolean;
showQueue: boolean;
showDevice: boolean;
showMiniPlayer: boolean;
showImmersive: boolean;
showEqPreset: boolean;
showSleepTimer: boolean;
showAbLoop: boolean;
showAudioQualityFooter: boolean;
coverAction: CoverAction;
}
type BoolKey = Exclude<keyof PlayerBarLayout, "coverAction">;
const DEFAULTS: PlayerBarLayout = {
showLyrics: true,
showQueue: true,
showDevice: true,
showMiniPlayer: true,
showImmersive: true,
showEqPreset: false,
showSleepTimer: false,
showAbLoop: false,
showAudioQualityFooter: false,
coverAction: "immersive",
};
/**
* Setting keys. Names re-use the existing `ui.show_*` pattern
* established for sleep-timer / A-B loop pins so legacy code (and
* any in-flight migrations) keeps working. The cover action lives
* under its own non-`show_` key because it isn't a boolean.
*/
export const PLAYER_BAR_LAYOUT_KEYS: Record<BoolKey, string> = {
showLyrics: "ui.show_lyrics",
showQueue: "ui.show_queue",
showDevice: "ui.show_device",
showMiniPlayer: "ui.show_mini_player",
showImmersive: "ui.show_immersive",
showEqPreset: "ui.show_eq_preset",
showSleepTimer: "ui.show_sleep_timer",
showAbLoop: "ui.show_ab_loop",
showAudioQualityFooter: "ui.show_audio_quality_footer",
};
export const COVER_ACTION_KEY = "ui.cover_action";
/**
* Unified window event the Settings panel dispatches after any
* write. Replaces the three feature-specific events used previously
* (`waveflow:sleep-timer-visibility`, etc.) by re-fetching the full
* payload. The legacy events are still observed below so any other
* code that dispatches them keeps triggering a refresh.
*/
export const PLAYER_BAR_LAYOUT_EVENT = "waveflow:playerbar-layout-changed";
function parseBool(raw: string | null, fallback: boolean): boolean {
if (raw == null) return fallback;
return raw === "1" || raw === "true";
}
function parseCoverAction(raw: string | null): CoverAction {
if (raw === "none" || raw === "now_playing" || raw === "immersive") {
return raw;
}
return DEFAULTS.coverAction;
}
/**
* React hook returning the resolved player-bar layout for the
* active profile. Re-reads on the unified
* `waveflow:playerbar-layout-changed` event and on the legacy
* per-feature events so old Settings code that still dispatches
* them isn't silently ignored.
*
* Failures are swallowed (with a console error) so a backend hiccup
* never blanks the player bar β the previous value stays in state.
*/
export function usePlayerBarLayout(): PlayerBarLayout {
const [layout, setLayout] = useState<PlayerBarLayout>(DEFAULTS);
useEffect(() => {
let cancelled = false;
const refresh = async () => {
try {
const boolKeys = Object.entries(PLAYER_BAR_LAYOUT_KEYS) as Array<
[BoolKey, string]
>;
const boolResults = await Promise.all(
boolKeys.map(async ([prop, key]) => {
const v = await getProfileSetting(key);
return [prop, parseBool(v, DEFAULTS[prop])] as const;
}),
);
const coverRaw = await getProfileSetting(COVER_ACTION_KEY);
if (cancelled) return;
const next: PlayerBarLayout = { ...DEFAULTS };
for (const [prop, value] of boolResults) {
next[prop] = value;
}
next.coverAction = parseCoverAction(coverRaw);
setLayout(next);
} catch (err) {
console.error("[usePlayerBarLayout] read failed", err);
}
};
void refresh();
const legacyEvents = [
"waveflow:sleep-timer-visibility",
"waveflow:ab-loop-visibility",
"waveflow:audio-quality-footer-visibility",
];
window.addEventListener(PLAYER_BAR_LAYOUT_EVENT, refresh);
for (const evt of legacyEvents) {
window.addEventListener(evt, refresh);
}
return () => {
cancelled = true;
window.removeEventListener(PLAYER_BAR_LAYOUT_EVENT, refresh);
for (const evt of legacyEvents) {
window.removeEventListener(evt, refresh);
}
};
}, []);
return layout;
}