OptionsModal.tsx
⎇
Raw
1import { type Accessor, createEffect, type Setter, on, createSignal, Switch, Match } from "solid-js";
2import { FileDisplay, type AppOptions } from "../types";
3import "./OptionsModal.css";
4import "./Dialog.css";
5import "toastify-js/src/toastify.css";
6import { toast } from "../utils";
7import { MediaFormat } from "music-server-shared/types";
8import { createStore } from "solid-js/store";
9
10interface OptionsModalProps {
11 showOptions: Accessor<boolean>;
12 setShowOptions: Setter<boolean>;
13 options: AppOptions;
14 setOptions: (options: AppOptions) => void;
15}
16
17export default function OptionsModal(props: OptionsModalProps) {
18 //clone, otherwise it shares the reference
19 const [newOptions, setNewOptions] = createStore<AppOptions>({ ...props.options });
20 const [optionsPage, setOptionsPage] = createSignal(1);
21 let dialogElement!: HTMLDialogElement;
22 let formElement!: HTMLFormElement;
23
24 createEffect(
25 on(props.showOptions, () => {
26 if (props.showOptions()) {
27 dialogElement.showModal();
28 setNewOptions(props.options);
29 setOptionsPage(1);
30 }
31 }),
32 );
33
34 function handleSave() {
35 if (!formElement.reportValidity()) return;
36 toast("Saved changes", "success");
37 props.setOptions(newOptions);
38 handleClose();
39 }
40
41 function handleClose() {
42 dialogElement.close();
43 props.setShowOptions(false);
44 }
45
46 async function deleteServerCache() {
47 const response = await fetch("/reset-cache", { method: "POST" }).catch(() => {});
48 if (!response || response.status !== 200) {
49 toast("Clearing server cache failed", "error");
50 } else {
51 toast("Server cache cleared", "info");
52 }
53 }
54
55 return (
56 <dialog ref={dialogElement} onClose={handleClose} onclick={(e) => e.target.nodeName === "DIALOG" && handleClose()}>
57 <form class="option-content" ref={formElement}>
58 <div class="title">Player Options</div>
59 <hr />
60 <div class="options-page-buttons">
61 <button onClick={() => setOptionsPage(1)} classList={{ "page-button-active": optionsPage() === 1 }}>
62 1
63 </button>
64 <button onClick={() => setOptionsPage(2)} classList={{ "page-button-active": optionsPage() === 2 }}>
65 2
66 </button>
67 <button onClick={() => setOptionsPage(3)} classList={{ "page-button-active": optionsPage() === 3 }}>
68 3
69 </button>
70 </div>
71 <Switch>
72 <Match when={optionsPage() === 1}>
73 <div>
74 <label for="transcoding-checkbox">Enable transcoding:</label>
75 <input
76 id="transcoding-checkbox"
77 type="checkbox"
78 checked={newOptions.enableTranscoding}
79 onChange={(e) => setNewOptions("enableTranscoding", e.target.checked)}
80 />
81 </div>
82 <div>
83 <label for="transcoding-buffer-checkbox">Disable chunked transcoding:</label>
84 <input
85 id="transcoding-buffer-checkbox"
86 type="checkbox"
87 checked={newOptions.disableChunkedTranscoding}
88 onChange={(e) => setNewOptions("disableChunkedTranscoding", e.target.checked)}
89 disabled={!newOptions.enableTranscoding}
90 />
91 </div>
92 <div>
93 <label for="bitrate-select">Bitrate:</label>
94 <select
95 id="bitrate-select"
96 value={newOptions.bitrate}
97 onChange={(e) => setNewOptions("bitrate", Number.parseInt(e.target.value))}
98 disabled={!newOptions.enableTranscoding}
99 >
100 <option value="32000">32 kbps</option>
101 <option value="48000">48 kbps</option>
102 <option value="64000">64 kbps</option>
103 <option value="96000">96 kbps</option>
104 <option value="128000">128 kbps</option>
105 <option value="192000">192 kbps</option>
106 <option value="320000">320 kbps</option>
107 </select>
108 </div>
109 <div>
110 <label for="codec-select">Format:</label>
111 <select
112 id="codec-select"
113 value={newOptions.format}
114 onChange={(e) => setNewOptions("format", e.target.value as MediaFormat)}
115 disabled={!newOptions.enableTranscoding}
116 >
117 <option>{MediaFormat["opus/ogg"]}</option>
118 <option>{MediaFormat["opus/webm"]}</option>
119 <option>{MediaFormat.mp3}</option>
120 <option>{MediaFormat["vorbis/ogg"]}</option>
121 <option>{MediaFormat["vorbis/webm"]}</option>
122 </select>
123 </div>
124 <div>
125 <label for="max-transcodes-input">Max concurrent transcodes:</label>
126 <input
127 id="max-transcodes-input"
128 type="number"
129 min={1}
130 required
131 value={newOptions.maxTranscodes}
132 disabled={!newOptions.enableTranscoding}
133 onChange={(e) => setNewOptions("maxTranscodes", e.target.valueAsNumber)}
134 />
135 </div>
136 <div>
137 <label for="max-downloads-input">Max concurrent downloads:</label>
138 <input
139 id="max-downloads-input"
140 type="number"
141 min={1}
142 required
143 value={newOptions.maxDownloads}
144 onChange={(e) => setNewOptions("maxDownloads", e.target.valueAsNumber)}
145 />
146 </div>
147 </Match>
148 <Match when={optionsPage() === 2}>
149 <div>
150 <label for="hide-volume-checkbox">Hide volume:</label>
151 <input
152 id="hide-volume-checkbox"
153 type="checkbox"
154 checked={newOptions.hideVolume}
155 onChange={(e) => setNewOptions("hideVolume", e.target.checked)}
156 />
157 </div>
158 <div>
159 <label for="hide-cover-checkbox">Hide file browser cover:</label>
160 <input
161 id="hide-cover-checkbox"
162 type="checkbox"
163 checked={newOptions.hideCover}
164 onChange={(e) => setNewOptions("hideCover", e.target.checked)}
165 />
166 </div>
167 <div>
168 <label for="hide-playlist-cover-checkbox">Hide current track cover:</label>
169 <input
170 id="hide-playlist-cover-checkbox"
171 type="checkbox"
172 checked={newOptions.hidePlaylistCover}
173 onChange={(e) => setNewOptions("hidePlaylistCover", e.target.checked)}
174 />
175 </div>
176 <div>
177 <label for="filebrowser-display-select">Filebrowser:</label>
178 <select
179 id="filebrowser-display-select"
180 value={newOptions.filebrowserDisplay}
181 onChange={(e) => setNewOptions("filebrowserDisplay", Number.parseInt(e.target.value))}
182 >
183 <option value={FileDisplay.FILENAME}>Filename</option>
184 <option value={FileDisplay.TITLE}>Media title</option>
185 <option value={FileDisplay.TRACKNO_TITLE}>Track number + Media title</option>
186 </select>
187 </div>
188 <div>
189 <label for="playlist-display-select">Playlist:</label>
190 <select
191 id="playlist-display-select"
192 value={newOptions.playlistDisplay}
193 onChange={(e) => setNewOptions("playlistDisplay", Number.parseInt(e.target.value))}
194 >
195 <option value={FileDisplay.FILENAME}>Filename</option>
196 <option value={FileDisplay.TITLE}>Media title</option>
197 <option value={FileDisplay.TRACKNO_TITLE}>Track number + Media title</option>
198 </select>
199 </div>
200 <div>
201 <label for="controls-display-select">Current track:</label>
202 <select
203 id="controls-display-select"
204 value={newOptions.controlsDisplay}
205 onChange={(e) => setNewOptions("controlsDisplay", Number.parseInt(e.target.value))}
206 >
207 <option value={FileDisplay.FILENAME}>Filename</option>
208 <option value={FileDisplay.TITLE}>Media title</option>
209 <option value={FileDisplay.TRACKNO_TITLE}>Track number + Media title</option>
210 </select>
211 </div>
212
213 <div>
214 <label for="font-resolution-select">Font resolution:</label>
215 <select
216 id="font-resolution-select"
217 value={newOptions.fontResolution}
218 onChange={(e) => setNewOptions("fontResolution", Number.parseInt(e.target.value))}
219 >
220 <option value={10}>10px</option>
221 <option value={12}>12px</option>
222 <option value={16}>16px</option>
223 </select>
224 </div>
225 </Match>
226 <Match when={optionsPage() === 3}>
227 <div>
228 <label for="remember-dir-checkbox">Remember last directory:</label>
229 <input
230 id="remember-dir-checkbox"
231 type="checkbox"
232 checked={newOptions.rememberDir}
233 onChange={(e) => setNewOptions("rememberDir", e.target.checked)}
234 />
235 </div>
236 <div>
237 <label for="duplicate-checkbox">Warn when adding duplicate to playlist:</label>
238 <input
239 id="duplicate-checkbox"
240 type="checkbox"
241 checked={newOptions.duplicateWarning}
242 onChange={(e) => setNewOptions("duplicateWarning", e.target.checked)}
243 />
244 </div>
245 </Match>
246 </Switch>
247 <div class="option-buttons">
248 <button title="Clear server caches" type="button" class="cache-button" onclick={deleteServerCache}>
249 Clear server caches
250 </button>
251 <button title="Save settings" type="button" onClick={handleSave}>
252 Save
253 </button>
254 <button title="Cancel changes settings" type="button" class="cancel-button" onClick={handleClose}>
255 Cancel
256 </button>
257 </div>
258 </form>
259 </dialog>
260 );
261}
262