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
62 onClick={(e) => {
63 e.preventDefault();
64 return setOptionsPage(1);
65 }}
66 classList={{ "page-button-active": optionsPage() === 1 }}
67 >
68 1
69 </button>
70 <button
71 onClick={(e) => {
72 e.preventDefault();
73 return setOptionsPage(2);
74 }}
75 classList={{ "page-button-active": optionsPage() === 2 }}
76 >
77 2
78 </button>
79 <button
80 onClick={(e) => {
81 e.preventDefault();
82 return setOptionsPage(3);
83 }}
84 classList={{ "page-button-active": optionsPage() === 3 }}
85 >
86 3
87 </button>
88 </div>
89 <Switch>
90 <Match when={optionsPage() === 1}>
91 <div>
92 <label for="transcoding-checkbox">Enable transcoding:</label>
93 <input
94 id="transcoding-checkbox"
95 type="checkbox"
96 checked={newOptions.enableTranscoding}
97 onChange={(e) => setNewOptions("enableTranscoding", e.target.checked)}
98 />
99 </div>
100 <div>
101 <label for="transcoding-buffer-checkbox">Disable chunked transcoding:</label>
102 <input
103 id="transcoding-buffer-checkbox"
104 type="checkbox"
105 checked={newOptions.disableChunkedTranscoding}
106 onChange={(e) => setNewOptions("disableChunkedTranscoding", e.target.checked)}
107 disabled={!newOptions.enableTranscoding}
108 />
109 </div>
110 <div>
111 <label for="bitrate-select">Bitrate:</label>
112 <select
113 id="bitrate-select"
114 value={newOptions.bitrate}
115 onChange={(e) => setNewOptions("bitrate", Number.parseInt(e.target.value))}
116 disabled={!newOptions.enableTranscoding}
117 >
118 <option value="32000">32 kbps</option>
119 <option value="48000">48 kbps</option>
120 <option value="64000">64 kbps</option>
121 <option value="96000">96 kbps</option>
122 <option value="128000">128 kbps</option>
123 <option value="192000">192 kbps</option>
124 <option value="320000">320 kbps</option>
125 </select>
126 </div>
127 <div>
128 <label for="codec-select">Format:</label>
129 <select
130 id="codec-select"
131 value={newOptions.format}
132 onChange={(e) => setNewOptions("format", e.target.value as MediaFormat)}
133 disabled={!newOptions.enableTranscoding}
134 >
135 <option>{MediaFormat["opus/ogg"]}</option>
136 <option>{MediaFormat["opus/webm"]}</option>
137 <option>{MediaFormat.mp3}</option>
138 <option>{MediaFormat["vorbis/ogg"]}</option>
139 <option>{MediaFormat["vorbis/webm"]}</option>
140 </select>
141 </div>
142 <div>
143 <label for="max-transcodes-input">Max concurrent transcodes:</label>
144 <input
145 id="max-transcodes-input"
146 type="number"
147 min={1}
148 required
149 value={newOptions.maxTranscodes}
150 disabled={!newOptions.enableTranscoding}
151 onChange={(e) => setNewOptions("maxTranscodes", e.target.valueAsNumber)}
152 />
153 </div>
154 <div>
155 <label for="max-downloads-input">Max concurrent downloads:</label>
156 <input
157 id="max-downloads-input"
158 type="number"
159 min={1}
160 required
161 value={newOptions.maxDownloads}
162 onChange={(e) => setNewOptions("maxDownloads", e.target.valueAsNumber)}
163 />
164 </div>
165 <div>
166 <label for="transcode-covers-checkbox">Transcode covers to avif:</label>
167 <input
168 id="transcode-covers-checkbox"
169 type="checkbox"
170 checked={newOptions.transcodeCovers}
171 onChange={(e) => setNewOptions("transcodeCovers", e.target.checked)}
172 />
173 </div>
174 </Match>
175 <Match when={optionsPage() === 2}>
176 <div>
177 <label for="hide-volume-checkbox">Hide volume:</label>
178 <input
179 id="hide-volume-checkbox"
180 type="checkbox"
181 checked={newOptions.hideVolume}
182 onChange={(e) => setNewOptions("hideVolume", e.target.checked)}
183 />
184 </div>
185 <div>
186 <label for="hide-cover-checkbox">Hide file browser cover:</label>
187 <input
188 id="hide-cover-checkbox"
189 type="checkbox"
190 checked={newOptions.hideCover}
191 onChange={(e) => setNewOptions("hideCover", e.target.checked)}
192 />
193 </div>
194 <div>
195 <label for="hide-playlist-cover-checkbox">Hide current track cover:</label>
196 <input
197 id="hide-playlist-cover-checkbox"
198 type="checkbox"
199 checked={newOptions.hidePlaylistCover}
200 onChange={(e) => setNewOptions("hidePlaylistCover", e.target.checked)}
201 />
202 </div>
203 <div>
204 <label for="filebrowser-display-select">Filebrowser:</label>
205 <select
206 id="filebrowser-display-select"
207 value={newOptions.filebrowserDisplay}
208 onChange={(e) => setNewOptions("filebrowserDisplay", Number.parseInt(e.target.value))}
209 >
210 <option value={FileDisplay.FILENAME}>Filename</option>
211 <option value={FileDisplay.TITLE}>Media title</option>
212 <option value={FileDisplay.TRACKNO_TITLE}>Track number + Media title</option>
213 </select>
214 </div>
215 <div>
216 <label for="playlist-display-select">Playlist:</label>
217 <select
218 id="playlist-display-select"
219 value={newOptions.playlistDisplay}
220 onChange={(e) => setNewOptions("playlistDisplay", Number.parseInt(e.target.value))}
221 >
222 <option value={FileDisplay.FILENAME}>Filename</option>
223 <option value={FileDisplay.TITLE}>Media title</option>
224 <option value={FileDisplay.TRACKNO_TITLE}>Track number + Media title</option>
225 </select>
226 </div>
227 <div>
228 <label for="controls-display-select">Current track:</label>
229 <select
230 id="controls-display-select"
231 value={newOptions.controlsDisplay}
232 onChange={(e) => setNewOptions("controlsDisplay", Number.parseInt(e.target.value))}
233 >
234 <option value={FileDisplay.FILENAME}>Filename</option>
235 <option value={FileDisplay.TITLE}>Media title</option>
236 <option value={FileDisplay.TRACKNO_TITLE}>Track number + Media title</option>
237 </select>
238 </div>
239
240 <div>
241 <label for="font-resolution-select">Font resolution:</label>
242 <select
243 id="font-resolution-select"
244 value={newOptions.fontResolution}
245 onChange={(e) => setNewOptions("fontResolution", Number.parseInt(e.target.value))}
246 >
247 <option value={10}>10px</option>
248 <option value={12}>12px</option>
249 <option value={16}>16px</option>
250 </select>
251 </div>
252 </Match>
253 <Match when={optionsPage() === 3}>
254 <div>
255 <label for="remember-dir-checkbox">Remember last directory:</label>
256 <input
257 id="remember-dir-checkbox"
258 type="checkbox"
259 checked={newOptions.rememberDir}
260 onChange={(e) => setNewOptions("rememberDir", e.target.checked)}
261 />
262 </div>
263 <div>
264 <label for="duplicate-checkbox">Warn when adding duplicate to playlist:</label>
265 <input
266 id="duplicate-checkbox"
267 type="checkbox"
268 checked={newOptions.duplicateWarning}
269 onChange={(e) => setNewOptions("duplicateWarning", e.target.checked)}
270 />
271 </div>
272 </Match>
273 </Switch>
274 <div class="option-buttons">
275 <button title="Clear server caches" type="button" class="cache-button" onclick={deleteServerCache}>
276 Clear server caches
277 </button>
278 <button title="Save settings" type="button" onClick={handleSave}>
279 Save
280 </button>
281 <button title="Cancel changes settings" type="button" class="cancel-button" onClick={handleClose}>
282 Cancel
283 </button>
284 </div>
285 </form>
286 </dialog>
287 );
288}
289