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 <div>
148 <label for="transcode-covers-checkbox">Transcode covers to avif:</label>
149 <input
150 id="transcode-covers-checkbox"
151 type="checkbox"
152 checked={newOptions.transcodeCovers}
153 onChange={(e) => setNewOptions("transcodeCovers", e.target.checked)}
154 />
155 </div>
156 </Match>
157 <Match when={optionsPage() === 2}>
158 <div>
159 <label for="hide-volume-checkbox">Hide volume:</label>
160 <input
161 id="hide-volume-checkbox"
162 type="checkbox"
163 checked={newOptions.hideVolume}
164 onChange={(e) => setNewOptions("hideVolume", e.target.checked)}
165 />
166 </div>
167 <div>
168 <label for="hide-cover-checkbox">Hide file browser cover:</label>
169 <input
170 id="hide-cover-checkbox"
171 type="checkbox"
172 checked={newOptions.hideCover}
173 onChange={(e) => setNewOptions("hideCover", e.target.checked)}
174 />
175 </div>
176 <div>
177 <label for="hide-playlist-cover-checkbox">Hide current track cover:</label>
178 <input
179 id="hide-playlist-cover-checkbox"
180 type="checkbox"
181 checked={newOptions.hidePlaylistCover}
182 onChange={(e) => setNewOptions("hidePlaylistCover", e.target.checked)}
183 />
184 </div>
185 <div>
186 <label for="filebrowser-display-select">Filebrowser:</label>
187 <select
188 id="filebrowser-display-select"
189 value={newOptions.filebrowserDisplay}
190 onChange={(e) => setNewOptions("filebrowserDisplay", Number.parseInt(e.target.value))}
191 >
192 <option value={FileDisplay.FILENAME}>Filename</option>
193 <option value={FileDisplay.TITLE}>Media title</option>
194 <option value={FileDisplay.TRACKNO_TITLE}>Track number + Media title</option>
195 </select>
196 </div>
197 <div>
198 <label for="playlist-display-select">Playlist:</label>
199 <select
200 id="playlist-display-select"
201 value={newOptions.playlistDisplay}
202 onChange={(e) => setNewOptions("playlistDisplay", Number.parseInt(e.target.value))}
203 >
204 <option value={FileDisplay.FILENAME}>Filename</option>
205 <option value={FileDisplay.TITLE}>Media title</option>
206 <option value={FileDisplay.TRACKNO_TITLE}>Track number + Media title</option>
207 </select>
208 </div>
209 <div>
210 <label for="controls-display-select">Current track:</label>
211 <select
212 id="controls-display-select"
213 value={newOptions.controlsDisplay}
214 onChange={(e) => setNewOptions("controlsDisplay", Number.parseInt(e.target.value))}
215 >
216 <option value={FileDisplay.FILENAME}>Filename</option>
217 <option value={FileDisplay.TITLE}>Media title</option>
218 <option value={FileDisplay.TRACKNO_TITLE}>Track number + Media title</option>
219 </select>
220 </div>
221
222 <div>
223 <label for="font-resolution-select">Font resolution:</label>
224 <select
225 id="font-resolution-select"
226 value={newOptions.fontResolution}
227 onChange={(e) => setNewOptions("fontResolution", Number.parseInt(e.target.value))}
228 >
229 <option value={10}>10px</option>
230 <option value={12}>12px</option>
231 <option value={16}>16px</option>
232 </select>
233 </div>
234 </Match>
235 <Match when={optionsPage() === 3}>
236 <div>
237 <label for="remember-dir-checkbox">Remember last directory:</label>
238 <input
239 id="remember-dir-checkbox"
240 type="checkbox"
241 checked={newOptions.rememberDir}
242 onChange={(e) => setNewOptions("rememberDir", e.target.checked)}
243 />
244 </div>
245 <div>
246 <label for="duplicate-checkbox">Warn when adding duplicate to playlist:</label>
247 <input
248 id="duplicate-checkbox"
249 type="checkbox"
250 checked={newOptions.duplicateWarning}
251 onChange={(e) => setNewOptions("duplicateWarning", e.target.checked)}
252 />
253 </div>
254 </Match>
255 </Switch>
256 <div class="option-buttons">
257 <button title="Clear server caches" type="button" class="cache-button" onclick={deleteServerCache}>
258 Clear server caches
259 </button>
260 <button title="Save settings" type="button" onClick={handleSave}>
261 Save
262 </button>
263 <button title="Cancel changes settings" type="button" class="cancel-button" onClick={handleClose}>
264 Cancel
265 </button>
266 </div>
267 </form>
268 </dialog>
269 );
270}
271