OptionsModal.tsx
⎇
Raw
1import { type Accessor, createEffect, createSignal, type Setter, on } from "solid-js";
2import 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";
8
9interface OptionsModalProps {
10 showOptions: Accessor<boolean>;
11 setShowOptions: Setter<boolean>;
12 options: Accessor<AppOptions>;
13 setOptions: (options: AppOptions) => void;
14}
15
16export default function OptionsModal(props: OptionsModalProps) {
17 const [newOptions, setNewOptions] = createSignal<AppOptions>(props.options());
18 let dialogElement!: HTMLDialogElement;
19 let formElement!: HTMLFormElement;
20
21 createEffect(
22 on(props.showOptions, () => {
23 if (props.showOptions()) {
24 dialogElement.showModal();
25 setNewOptions({ ...props.options() });
26 }
27 }),
28 );
29
30 function handleSave() {
31 if (!formElement.reportValidity()) return;
32 toast("Saved changes", "success");
33 props.setOptions(newOptions());
34 handleClose();
35 }
36
37 function handleClose() {
38 dialogElement.close();
39 props.setShowOptions(false);
40 }
41
42 async function deleteServerCache() {
43 const response = await fetch("/reset-cache", { method: "POST" }).catch(() => {});
44 if (!response || response.status !== 200) {
45 toast("Clearing server cache failed", "error");
46 } else {
47 toast("Server cache cleared", "info");
48 }
49 }
50
51 return (
52 <dialog ref={dialogElement} onClose={handleClose}>
53 <form class="option-content" ref={formElement}>
54 <div class="title">Player Options</div>
55 <hr />
56 <div>
57 <label for="remember-dir-checkbox">Remember last directory:</label>
58 <input
59 id="remember-dir-checkbox"
60 type="checkbox"
61 checked={newOptions()?.rememberDir}
62 onChange={(e) => setNewOptions({ ...newOptions(), rememberDir: e.target.checked })}
63 />
64 </div>
65 <div>
66 <label for="transcoding-checkbox">Enable transcoding:</label>
67 <input
68 id="transcoding-checkbox"
69 type="checkbox"
70 checked={newOptions()?.enableTranscoding}
71 onChange={(e) => setNewOptions({ ...newOptions(), enableTranscoding: e.target.checked })}
72 />
73 </div>
74 <div>
75 <label for="transcoding-buffer-checkbox">Disable chunked transcoding:</label>
76 <input
77 id="transcoding-buffer-checkbox"
78 type="checkbox"
79 checked={newOptions()?.disableChunkedTranscoding}
80 onChange={(e) => setNewOptions({ ...newOptions(), disableChunkedTranscoding: e.target.checked })}
81 disabled={!newOptions()?.enableTranscoding}
82 />
83 </div>
84 <div>
85 <label for="bitrate-select">Bitrate:</label>
86 <select
87 id="bitrate-select"
88 value={newOptions()?.bitrate}
89 onChange={(e) =>
90 setNewOptions({
91 ...newOptions(),
92 bitrate: Number.parseInt(e.target.value),
93 })
94 }
95 disabled={!newOptions()?.enableTranscoding}
96 >
97 <option value="32000">32 kbps</option>
98 <option value="48000">48 kbps</option>
99 <option value="64000">64 kbps</option>
100 <option value="96000">96 kbps</option>
101 <option value="128000">128 kbps</option>
102 <option value="192000">192 kbps</option>
103 <option value="320000">320 kbps</option>
104 </select>
105 </div>
106 <div>
107 <label for="codec-select">Format:</label>
108 <select
109 id="codec-select"
110 value={newOptions()?.format}
111 onChange={(e) =>
112 setNewOptions({
113 ...newOptions(),
114 format: e.target.value as MediaFormat,
115 })
116 }
117 disabled={!newOptions()?.enableTranscoding}
118 >
119 <option>{MediaFormat["opus/ogg"]}</option>
120 <option>{MediaFormat["opus/webm"]}</option>
121 <option>{MediaFormat.mp3}</option>
122 <option>{MediaFormat["vorbis/ogg"]}</option>
123 <option>{MediaFormat["vorbis/webm"]}</option>
124 </select>
125 </div>
126 <div>
127 <label for="max-downloads-input">Max concurrent downloads:</label>
128 <input
129 id="max-downloads-input"
130 type="number"
131 min={1}
132 required
133 value={newOptions()?.maxDownloads}
134 onChange={(e) => setNewOptions({ ...newOptions(), maxDownloads: e.target.valueAsNumber })}
135 />
136 </div>
137 <div>
138 <label for="max-transcodes-input">Max concurrent transcodes:</label>
139 <input
140 id="max-transcodes-input"
141 type="number"
142 min={1}
143 required
144 value={newOptions()?.maxTranscodes}
145 onChange={(e) => setNewOptions({ ...newOptions(), maxTranscodes: e.target.valueAsNumber })}
146 />
147 </div>
148 <div>
149 <label for="hide-volume-checkbox">Hide volume:</label>
150 <input
151 id="hide-volume-checkbox"
152 type="checkbox"
153 checked={newOptions()?.hideVolume}
154 onChange={(e) => setNewOptions({ ...newOptions(), hideVolume: e.target.checked })}
155 />
156 </div>
157 <div>
158 <label for="duplicate-checkbox">Warn when adding duplicate to playlist:</label>
159 <input
160 id="duplicate-checkbox"
161 type="checkbox"
162 checked={newOptions()?.duplicateWarning}
163 onChange={(e) => setNewOptions({ ...newOptions(), duplicateWarning: e.target.checked })}
164 />
165 </div>
166 <div class="option-buttons">
167 <button title="Clear server caches" type="button" class="cache-button" onclick={deleteServerCache}>
168 Clear server caches
169 </button>
170 <button title="Save settings" type="button" onClick={handleSave}>
171 Save
172 </button>
173 <button title="Cancel changes settings" type="button" class="cancel-button" onClick={handleClose}>
174 Cancel
175 </button>
176 </div>
177 </form>
178 </dialog>
179 );
180}
181