this repo has no description
1import {
2 ExtensionSettingType,
3 ExtensionSettingsManifest,
4 MultiSelectSettingType,
5 NumberSettingType,
6 SelectOption,
7 SelectSettingType
8} from "@moonlight-mod/types/config";
9import WebpackRequire from "@moonlight-mod/types/discord/require";
10import { CircleXIconSVG, ExtensionState, MoonbaseExtension } from "../../types";
11
12type SettingsProps = {
13 ext: MoonbaseExtension;
14 name: string;
15 setting: ExtensionSettingsManifest;
16 disabled: boolean;
17};
18
19type SettingsComponent = React.ComponentType<SettingsProps>;
20
21export default (require: typeof WebpackRequire) => {
22 const React = require("common_react");
23 const CommonComponents = require("common_components");
24 const Flux = require("common_flux");
25 const spacepack = require("spacepack_spacepack").spacepack;
26
27 const { MoonbaseSettingsStore } =
28 require("moonbase_stores") as typeof import("../../webpackModules/stores");
29
30 const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
31
32 function useConfigEntry<T>(id: string, name: string) {
33 return Flux.useStateFromStores(
34 [MoonbaseSettingsStore],
35 () => {
36 return {
37 value: MoonbaseSettingsStore.getExtensionConfig<T>(id, name),
38 displayName: MoonbaseSettingsStore.getExtensionConfigName(id, name),
39 description: MoonbaseSettingsStore.getExtensionConfigDescription(
40 id,
41 name
42 )
43 };
44 },
45 [id, name]
46 );
47 }
48
49 function Boolean({ ext, name, setting, disabled }: SettingsProps) {
50 const { FormSwitch } = CommonComponents;
51 const { value, displayName, description } = useConfigEntry<boolean>(
52 ext.id,
53 name
54 );
55
56 return (
57 <FormSwitch
58 value={value ?? false}
59 hideBorder={true}
60 disabled={disabled}
61 onChange={(value: boolean) => {
62 MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
63 }}
64 note={description}
65 className={`${Margins.marginReset} ${Margins.marginTop20}`}
66 >
67 {displayName}
68 </FormSwitch>
69 );
70 }
71
72 function Number({ ext, name, setting, disabled }: SettingsProps) {
73 const { FormItem, FormText, Slider } = CommonComponents;
74 const { value, displayName, description } = useConfigEntry<number>(
75 ext.id,
76 name
77 );
78
79 const castedSetting = setting as NumberSettingType;
80 const min = castedSetting.min ?? 0;
81 const max = castedSetting.max ?? 100;
82
83 return (
84 <FormItem className={Margins.marginTop20} title={displayName}>
85 {description && <FormText>{description}</FormText>}
86 <Slider
87 initialValue={value ?? 0}
88 disabled={disabled}
89 minValue={castedSetting.min ?? 0}
90 maxValue={castedSetting.max ?? 100}
91 onValueChange={(value: number) => {
92 const rounded = Math.max(min, Math.min(max, Math.round(value)));
93 MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
94 }}
95 />
96 </FormItem>
97 );
98 }
99
100 function String({ ext, name, setting, disabled }: SettingsProps) {
101 const { FormItem, FormText, TextInput } = CommonComponents;
102 const { value, displayName, description } = useConfigEntry<string>(
103 ext.id,
104 name
105 );
106
107 return (
108 <FormItem className={Margins.marginTop20} title={displayName}>
109 {description && (
110 <FormText className={Margins.marginBottom8}>{description}</FormText>
111 )}
112 <TextInput
113 value={value ?? ""}
114 onChange={(value: string) => {
115 if (disabled) return;
116 MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
117 }}
118 />
119 </FormItem>
120 );
121 }
122
123 function Select({ ext, name, setting, disabled }: SettingsProps) {
124 const { FormItem, FormText, SingleSelect } = CommonComponents;
125 const { value, displayName, description } = useConfigEntry<string>(
126 ext.id,
127 name
128 );
129
130 const castedSetting = setting as SelectSettingType;
131 const options = castedSetting.options;
132
133 return (
134 <FormItem className={Margins.marginTop20} title={displayName}>
135 {description && (
136 <FormText className={Margins.marginBottom8}>{description}</FormText>
137 )}
138 <SingleSelect
139 autofocus={false}
140 clearable={false}
141 value={value ?? ""}
142 options={options.map((o: SelectOption) =>
143 typeof o === "string" ? { value: o, label: o } : o
144 )}
145 onChange={(value: string) => {
146 if (disabled) return;
147 MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
148 }}
149 />
150 </FormItem>
151 );
152 }
153
154 function MultiSelect({ ext, name, setting, disabled }: SettingsProps) {
155 const { FormItem, FormText, Select, useVariableSelect, multiSelect } =
156 CommonComponents;
157 const { value, displayName, description } = useConfigEntry<
158 string | string[]
159 >(ext.id, name);
160
161 const castedSetting = setting as MultiSelectSettingType;
162 const options = castedSetting.options;
163
164 return (
165 <FormItem className={Margins.marginTop20} title={displayName}>
166 {description && (
167 <FormText className={Margins.marginBottom8}>{description}</FormText>
168 )}
169 <Select
170 autofocus={false}
171 clearable={false}
172 closeOnSelect={false}
173 options={options.map((o: SelectOption) =>
174 typeof o === "string" ? { value: o, label: o } : o
175 )}
176 {...useVariableSelect({
177 onSelectInteraction: multiSelect,
178 value: new Set(Array.isArray(value) ? value : [value]),
179 onChange: (value: string) => {
180 if (disabled) return;
181 MoonbaseSettingsStore.setExtensionConfig(
182 ext.id,
183 name,
184 Array.from(value)
185 );
186 }
187 })}
188 />
189 </FormItem>
190 );
191 }
192
193 const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0]
194 .exports;
195 const CircleXIcon = spacepack.findByCode(CircleXIconSVG)[0].exports.default;
196 function RemoveEntryButton({
197 onClick,
198 disabled
199 }: {
200 onClick: () => void;
201 disabled: boolean;
202 }) {
203 const { Tooltip, Clickable } = CommonComponents;
204 return (
205 <div className={RemoveButtonClasses.removeButtonContainer}>
206 <Tooltip text="Remove entry" position="top">
207 {(props: any) => (
208 <Clickable
209 {...props}
210 className={RemoveButtonClasses.removeButton}
211 onClick={onClick}
212 >
213 <CircleXIcon width={16} height={16} />
214 </Clickable>
215 )}
216 </Tooltip>
217 </div>
218 );
219 }
220
221 function List({ ext, name, setting, disabled }: SettingsProps) {
222 const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents;
223 const { value, displayName, description } = useConfigEntry<string[]>(
224 ext.id,
225 name
226 );
227
228 const entries = value ?? [];
229 const updateConfig = () =>
230 MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries);
231
232 return (
233 <FormItem className={Margins.marginTop20} title={displayName}>
234 {description && (
235 <FormText className={Margins.marginBottom4}>{description}</FormText>
236 )}
237 <Flex direction={Flex.Direction.VERTICAL}>
238 {entries.map((val, i) => (
239 // FIXME: stylesheets
240 <div
241 key={i}
242 style={{
243 display: "grid",
244 height: "32px",
245 gap: "8px",
246 gridTemplateColumns: "1fr 32px",
247 alignItems: "center"
248 }}
249 >
250 <TextInput
251 size={TextInput.Sizes.MINI}
252 value={val}
253 disabled={disabled}
254 onChange={(newVal: string) => {
255 entries[i] = newVal;
256 updateConfig();
257 }}
258 />
259 <RemoveEntryButton
260 disabled={disabled}
261 onClick={() => {
262 entries.splice(i, 1);
263 updateConfig();
264 }}
265 />
266 </div>
267 ))}
268
269 <Button
270 look={Button.Looks.FILLED}
271 color={Button.Colors.GREEN}
272 size={Button.Sizes.SMALL}
273 disabled={disabled}
274 className={Margins.marginTop8}
275 onClick={() => {
276 entries.push("");
277 updateConfig();
278 }}
279 >
280 Add new entry
281 </Button>
282 </Flex>
283 </FormItem>
284 );
285 }
286
287 function Dictionary({ ext, name, setting, disabled }: SettingsProps) {
288 const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents;
289 const { value, displayName, description } = useConfigEntry<
290 Record<string, string>
291 >(ext.id, name);
292
293 const entries = Object.entries(value ?? {});
294 const updateConfig = () =>
295 MoonbaseSettingsStore.setExtensionConfig(
296 ext.id,
297 name,
298 Object.fromEntries(entries)
299 );
300
301 return (
302 <FormItem className={Margins.marginTop20} title={displayName}>
303 {description && (
304 <FormText className={Margins.marginBottom4}>{description}</FormText>
305 )}
306 <Flex direction={Flex.Direction.VERTICAL}>
307 {entries.map(([key, val], i) => (
308 // FIXME: stylesheets
309 <div
310 key={i}
311 style={{
312 display: "grid",
313 height: "32px",
314 gap: "8px",
315 gridTemplateColumns: "1fr 1fr 32px",
316 alignItems: "center"
317 }}
318 >
319 <TextInput
320 size={TextInput.Sizes.MINI}
321 value={key}
322 disabled={disabled}
323 onChange={(newKey: string) => {
324 entries[i][0] = newKey;
325 updateConfig();
326 }}
327 />
328 <TextInput
329 size={TextInput.Sizes.MINI}
330 value={val}
331 disabled={disabled}
332 onChange={(newValue: string) => {
333 entries[i][1] = newValue;
334 updateConfig();
335 }}
336 />
337 <RemoveEntryButton
338 disabled={disabled}
339 onClick={() => {
340 entries.splice(i, 1);
341 updateConfig();
342 }}
343 />
344 </div>
345 ))}
346
347 <Button
348 look={Button.Looks.FILLED}
349 color={Button.Colors.GREEN}
350 size={Button.Sizes.SMALL}
351 className={Margins.marginTop8}
352 disabled={disabled}
353 onClick={() => {
354 entries.push([`entry-${entries.length}`, ""]);
355 updateConfig();
356 }}
357 >
358 Add new entry
359 </Button>
360 </Flex>
361 </FormItem>
362 );
363 }
364
365 function Setting({ ext, name, setting, disabled }: SettingsProps) {
366 const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = {
367 [ExtensionSettingType.Boolean]: Boolean,
368 [ExtensionSettingType.Number]: Number,
369 [ExtensionSettingType.String]: String,
370 [ExtensionSettingType.Select]: Select,
371 [ExtensionSettingType.MultiSelect]: MultiSelect,
372 [ExtensionSettingType.List]: List,
373 [ExtensionSettingType.Dictionary]: Dictionary
374 };
375 const element = elements[setting.type];
376 if (element == null) return <></>;
377 return React.createElement(element, { ext, name, setting, disabled });
378 }
379
380 return function Settings({ ext }: { ext: MoonbaseExtension }) {
381 const { Flex } = CommonComponents;
382 return (
383 <Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}>
384 {Object.entries(ext.manifest.settings!).map(([name, setting]) => (
385 <Setting
386 ext={ext}
387 key={name}
388 name={name}
389 setting={setting}
390 disabled={ext.state === ExtensionState.NotDownloaded}
391 />
392 ))}
393 </Flex>
394 );
395 };
396};