@opensumi/ide-preferences 2.21.13 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/lib/browser/abstract-resource-preference-provider.d.ts +1 -0
  2. package/lib/browser/abstract-resource-preference-provider.d.ts.map +1 -1
  3. package/lib/browser/abstract-resource-preference-provider.js +22 -3
  4. package/lib/browser/abstract-resource-preference-provider.js.map +1 -1
  5. package/lib/browser/folder-file-preference-provider.d.ts +24 -0
  6. package/lib/browser/folder-file-preference-provider.d.ts.map +1 -0
  7. package/lib/browser/{folder-preference-provider.js → folder-file-preference-provider.js} +12 -14
  8. package/lib/browser/folder-file-preference-provider.js.map +1 -0
  9. package/lib/browser/folders-preferences-provider.d.ts +8 -8
  10. package/lib/browser/folders-preferences-provider.d.ts.map +1 -1
  11. package/lib/browser/folders-preferences-provider.js +7 -7
  12. package/lib/browser/folders-preferences-provider.js.map +1 -1
  13. package/lib/browser/index.d.ts.map +1 -1
  14. package/lib/browser/index.js +8 -8
  15. package/lib/browser/index.js.map +1 -1
  16. package/lib/browser/preference-contribution.d.ts.map +1 -1
  17. package/lib/browser/preference-contribution.js +3 -3
  18. package/lib/browser/preference-contribution.js.map +1 -1
  19. package/lib/browser/preference-settings.service.d.ts +22 -10
  20. package/lib/browser/preference-settings.service.d.ts.map +1 -1
  21. package/lib/browser/preference-settings.service.js +284 -101
  22. package/lib/browser/preference-settings.service.js.map +1 -1
  23. package/lib/browser/preference-widgets.js.map +1 -1
  24. package/lib/browser/preferenceItem.view.d.ts +4 -3
  25. package/lib/browser/preferenceItem.view.d.ts.map +1 -1
  26. package/lib/browser/preferenceItem.view.js +125 -62
  27. package/lib/browser/preferenceItem.view.js.map +1 -1
  28. package/lib/browser/preferences.module.less +100 -60
  29. package/lib/browser/preferences.view.d.ts +3 -17
  30. package/lib/browser/preferences.view.d.ts.map +1 -1
  31. package/lib/browser/preferences.view.js +194 -112
  32. package/lib/browser/preferences.view.js.map +1 -1
  33. package/lib/browser/user-preference-provider.js.map +1 -1
  34. package/lib/browser/userstorage/user-storage.contribution.js.map +1 -1
  35. package/lib/browser/userstorage/user-storage.service.js +7 -7
  36. package/lib/browser/userstorage/user-storage.service.js.map +1 -1
  37. package/lib/browser/workspace-file-preference-provider.d.ts +1 -1
  38. package/lib/browser/workspace-file-preference-provider.d.ts.map +1 -1
  39. package/lib/browser/workspace-file-preference-provider.js.map +1 -1
  40. package/lib/browser/workspace-preference-provider.js.map +1 -1
  41. package/lib/common/preference-id.d.ts +3 -0
  42. package/lib/common/preference-id.d.ts.map +1 -1
  43. package/lib/common/preference-id.js +4 -1
  44. package/lib/common/preference-id.js.map +1 -1
  45. package/lib/common/preference.d.ts +1 -0
  46. package/lib/common/preference.d.ts.map +1 -1
  47. package/lib/common/preference.js +26 -4
  48. package/lib/common/preference.js.map +1 -1
  49. package/lib/common/types.d.ts +18 -3
  50. package/lib/common/types.d.ts.map +1 -1
  51. package/lib/common/user-storage.d.ts +1 -1
  52. package/lib/common/user-storage.d.ts.map +1 -1
  53. package/package.json +12 -11
  54. package/src/browser/abstract-resource-preference-provider.ts +357 -0
  55. package/src/browser/folder-file-preference-provider.ts +56 -0
  56. package/src/browser/folders-preferences-provider.ts +262 -0
  57. package/src/browser/index.ts +125 -0
  58. package/src/browser/preference-contribution.ts +432 -0
  59. package/src/browser/preference-settings.service.ts +810 -0
  60. package/src/browser/preference-widgets.ts +368 -0
  61. package/src/browser/preferenceItem.view.tsx +787 -0
  62. package/src/browser/preferences.module.less +345 -0
  63. package/src/browser/preferences.view.tsx +388 -0
  64. package/src/browser/user-preference-provider.ts +18 -0
  65. package/src/browser/userstorage/index.ts +2 -0
  66. package/src/browser/userstorage/user-storage.contribution.ts +19 -0
  67. package/src/browser/userstorage/user-storage.service.ts +192 -0
  68. package/src/browser/workspace-file-preference-provider.ts +50 -0
  69. package/src/browser/workspace-preference-provider.ts +129 -0
  70. package/src/common/commands.ts +5 -0
  71. package/src/common/index.ts +4 -0
  72. package/src/common/preference-id.ts +11 -0
  73. package/src/common/preference.ts +51 -0
  74. package/src/common/types.ts +63 -0
  75. package/src/common/user-storage.ts +6 -0
  76. package/src/index.ts +1 -0
  77. package/lib/browser/folder-preference-provider.d.ts +0 -26
  78. package/lib/browser/folder-preference-provider.d.ts.map +0 -1
  79. package/lib/browser/folder-preference-provider.js.map +0 -1
  80. package/lib/browser/index.less +0 -36
@@ -0,0 +1,787 @@
1
+ import classnames from 'classnames';
2
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
+
4
+ import { Injectable, Autowired } from '@opensumi/di';
5
+ import { Button, CheckBox, Input, Option, Select, ValidateInput, ValidateMessage } from '@opensumi/ide-components';
6
+ import { DefaultMarkedRenderer, linkify, Markdown } from '@opensumi/ide-components/lib/markdown/index';
7
+ import {
8
+ DisposableCollection,
9
+ getIcon,
10
+ IPreferenceSettingsService,
11
+ localize,
12
+ PreferenceItem,
13
+ PreferenceProvider,
14
+ PreferenceSchemaProvider,
15
+ PreferenceScope,
16
+ PreferenceService,
17
+ replaceLocalizePlaceholder,
18
+ useInjectable,
19
+ formatLocalize,
20
+ ILogger,
21
+ IOpenerService,
22
+ IResolvedPreferenceViewDesc,
23
+ } from '@opensumi/ide-core-browser';
24
+
25
+ import { getPreferenceItemLabel, knownPrefIdMappings } from '../common';
26
+
27
+ import { PreferenceSettingsService } from './preference-settings.service';
28
+ import styles from './preferences.module.less';
29
+
30
+ interface IPreferenceItemProps {
31
+ preferenceName: string;
32
+ localizedName?: string;
33
+ localizedDescription?: {
34
+ description: string | undefined;
35
+ markdownDescription: string | undefined;
36
+ };
37
+ /**
38
+ * 自动处理了 markdown 和纯文本模式的选项
39
+ */
40
+ renderedDescription?: JSX.Element;
41
+ currentValue: any;
42
+ defaultValue: any;
43
+ schema: PreferenceItem;
44
+ scope: PreferenceScope;
45
+ effectingScope: PreferenceScope;
46
+ hasValueInScope: boolean;
47
+ isModified: boolean;
48
+ }
49
+
50
+ const DESCRIPTION_EXPRESSION_REGEXP = /`#(.+)#`/gi;
51
+ const NONE_SELECT_OPTION = 'none';
52
+
53
+ /**
54
+ * 用于展示单个设置项的视图
55
+ * 目前支持类型:
56
+ * string:
57
+ * 含有 enum - 下拉选项框
58
+ * 不含有 enum - 输入框
59
+ * number: 输入框
60
+ * array:
61
+ * 只允许 string类 - 可视化编辑项
62
+ * object:
63
+ * 暂不支持
64
+ */
65
+ export const NextPreferenceItem = ({
66
+ preferenceId,
67
+ localizedName,
68
+ preference,
69
+ scope,
70
+ }: {
71
+ preferenceId: string;
72
+ preference: IResolvedPreferenceViewDesc;
73
+ localizedName?: string;
74
+ scope: PreferenceScope;
75
+ }) => {
76
+ const preferenceService: PreferenceService = useInjectable(PreferenceService);
77
+ const settingsService: PreferenceSettingsService = useInjectable(IPreferenceSettingsService);
78
+ const schemaProvider: PreferenceSchemaProvider = useInjectable(PreferenceSchemaProvider);
79
+
80
+ const preferenceProvider: PreferenceProvider = preferenceService.getProvider(scope)!;
81
+
82
+ // 获得这个设置项的当前值
83
+ const { value: inherited, effectingScope } = settingsService.getPreference(preferenceId, scope);
84
+ const [value, setValue] = useState<boolean | string | string[] | undefined>(
85
+ preferenceProvider.get<boolean | string | string[]>(preferenceId),
86
+ );
87
+ const [schema, setSchema] = useState<PreferenceItem>();
88
+
89
+ // 当这个设置项被外部变更时,更新局部值
90
+ useEffect(() => {
91
+ // 获得当前的schema
92
+ const schemas = schemaProvider.getPreferenceProperty(preferenceId);
93
+ setSchema(schemas);
94
+
95
+ const disposableCollection = new DisposableCollection();
96
+ // 监听配置变化
97
+ disposableCollection.push(
98
+ preferenceProvider.onDidPreferencesChanged((e) => {
99
+ if (e.default && Object.prototype.hasOwnProperty.call(e.default, preferenceId)) {
100
+ if (e.default[preferenceId].scope === scope) {
101
+ const newValue = e.default[preferenceId].newValue;
102
+ setValue(newValue);
103
+ }
104
+ }
105
+ }),
106
+ );
107
+
108
+ disposableCollection.push(
109
+ settingsService.onDidEnumLabelsChange(() => {
110
+ const schemas = schemaProvider.getPreferenceProperty(preferenceId);
111
+ setSchema(schemas);
112
+ }),
113
+ );
114
+
115
+ return () => {
116
+ disposableCollection.dispose();
117
+ };
118
+ }, []);
119
+
120
+ let renderSchema = schema;
121
+ if (!renderSchema) {
122
+ // 渲染阶段可能存在还没获取到 schema 的情况
123
+ renderSchema = schemaProvider.getPreferenceProperty(preferenceId);
124
+ }
125
+
126
+ if (!renderSchema) {
127
+ return (
128
+ <div
129
+ className={classnames({
130
+ [styles.preference_item]: true,
131
+ })}
132
+ >
133
+ {preferenceId} schema not found.
134
+ </div>
135
+ );
136
+ }
137
+
138
+ const defaultValue =
139
+ preferenceService.resolve(preferenceId, undefined, undefined, undefined, PreferenceScope.Default).value ??
140
+ renderSchema.default;
141
+
142
+ // 目前还没法对 input 的数字值进行 === 校验,先全部转为 String
143
+ const isModified = value !== undefined && String(value) !== String(defaultValue);
144
+
145
+ const renderPreferenceItem = () => {
146
+ if (renderSchema) {
147
+ const props = {
148
+ preferenceName: preferenceId,
149
+ scope,
150
+ effectingScope,
151
+ schema: renderSchema,
152
+ currentValue: value === undefined ? inherited : value,
153
+ defaultValue,
154
+ localizedName,
155
+ localizedDescription: {
156
+ description: preference.description,
157
+ markdownDescription: preference.markdownDescription,
158
+ },
159
+ renderedDescription: renderDescription({
160
+ name: localizedName,
161
+ description: preference.description,
162
+ markdownDescription: preference.markdownDescription,
163
+ }),
164
+ hasValueInScope: value !== undefined,
165
+ isModified,
166
+ } as IPreferenceItemProps;
167
+
168
+ switch (renderSchema.type) {
169
+ case 'boolean':
170
+ return <CheckboxPreferenceItem {...props} />;
171
+ case 'integer':
172
+ case 'number':
173
+ if (renderSchema.enum) {
174
+ return <SelectPreferenceItem {...props} />;
175
+ } else {
176
+ return <InputPreferenceItem {...props} isNumber={true} />;
177
+ }
178
+ case 'string':
179
+ if (renderSchema.enum) {
180
+ return <SelectPreferenceItem {...props} />;
181
+ } else {
182
+ return <InputPreferenceItem {...props} />;
183
+ }
184
+ case 'array':
185
+ if (renderSchema.items && renderSchema.items.type === 'string') {
186
+ return <StringArrayPreferenceItem {...props} />;
187
+ } else {
188
+ return <EditInSettingsJsonPreferenceItem {...props} />;
189
+ }
190
+ default:
191
+ return <EditInSettingsJsonPreferenceItem {...props} />;
192
+ }
193
+ }
194
+ };
195
+
196
+ return (
197
+ <div
198
+ className={classnames({
199
+ [styles.preference_item]: true,
200
+ [styles.modified]: isModified,
201
+ })}
202
+ data-id={preferenceId}
203
+ >
204
+ {renderPreferenceItem()}
205
+ </div>
206
+ );
207
+ };
208
+
209
+ const renderDescriptionExpression = (description: string) => {
210
+ const preferenceSettingService: PreferenceSettingsService = useInjectable(IPreferenceSettingsService);
211
+ if (!description) {
212
+ return null;
213
+ }
214
+ const match = description.match(DESCRIPTION_EXPRESSION_REGEXP);
215
+ if (!match) {
216
+ return description;
217
+ }
218
+ let tmp = description;
219
+ const result = [] as JSX.Element[];
220
+ for (const expression of match) {
221
+ if (!tmp) {
222
+ continue;
223
+ }
224
+ const _preferenceId = expression.slice(2, expression.length - 2);
225
+ const preferenceId = knownPrefIdMappings[_preferenceId] ?? _preferenceId;
226
+
227
+ const preference = preferenceSettingService.getPreferenceViewDesc(preferenceId);
228
+ if (preference) {
229
+ const preferenceTitle = getPreferenceItemLabel(preference);
230
+ const [prev, next] = tmp.split(expression, 2);
231
+ prev && result.push(<span>{prev}</span>);
232
+ tmp = next;
233
+ const link = (
234
+ <a
235
+ onClick={() => {
236
+ preferenceSettingService.search(preferenceTitle);
237
+ }}
238
+ key={preferenceId}
239
+ >
240
+ {preferenceTitle}
241
+ </a>
242
+ );
243
+ result.push(link);
244
+ }
245
+ }
246
+ tmp && result.push(<span>{tmp}</span>);
247
+ return result;
248
+ };
249
+
250
+ @Injectable()
251
+ class PreferenceMarkedRender extends DefaultMarkedRenderer {
252
+ static openerScheme = 'prefTitle://';
253
+ @Autowired(IPreferenceSettingsService)
254
+ preferenceSettingService: PreferenceSettingsService;
255
+
256
+ codespan(text: string): string {
257
+ if (text.startsWith('#') && text.endsWith('#')) {
258
+ const _prefId = text.slice(1, text.length - 1);
259
+ const prefId = knownPrefIdMappings[_prefId] ?? _prefId;
260
+ const preference = this.preferenceSettingService.getPreferenceViewDesc(prefId);
261
+ if (preference) {
262
+ const preferenceTitle = getPreferenceItemLabel(preference);
263
+ return linkify(`${PreferenceMarkedRender.openerScheme}${preferenceTitle}`, prefId, preferenceTitle);
264
+ }
265
+ return super.codespan(prefId);
266
+ }
267
+ return super.codespan(text);
268
+ }
269
+ }
270
+
271
+ const renderMarkdownDescription = (message: string) => {
272
+ const openerService: IOpenerService = useInjectable(IOpenerService);
273
+ const renderer = useInjectable(PreferenceMarkedRender) as PreferenceMarkedRender;
274
+ const preferenceSettingService: PreferenceSettingsService = useInjectable(IPreferenceSettingsService);
275
+
276
+ return (
277
+ <Markdown
278
+ opener={{
279
+ open(uri: string) {
280
+ if (uri.startsWith(PreferenceMarkedRender.openerScheme)) {
281
+ const prefTitle = uri.slice(PreferenceMarkedRender.openerScheme.length);
282
+ preferenceSettingService.search(prefTitle);
283
+ return true;
284
+ }
285
+ return openerService.open(uri);
286
+ },
287
+ }}
288
+ value={message}
289
+ renderer={renderer}
290
+ />
291
+ );
292
+ };
293
+
294
+ const renderDescription = (data: { name?: string; description?: string; markdownDescription?: string }) => {
295
+ const description = replaceLocalizePlaceholder(data.description ?? data.markdownDescription);
296
+ if (!description) {
297
+ return <div className={styles.desc}>{data.name}</div>;
298
+ }
299
+
300
+ return (
301
+ <div className={styles.desc}>
302
+ {data.markdownDescription ? renderMarkdownDescription(description) : renderDescriptionExpression(description)}
303
+ </div>
304
+ );
305
+ };
306
+
307
+ const SettingStatus = ({
308
+ preferenceName,
309
+ scope,
310
+ effectingScope,
311
+ showReset,
312
+ }: {
313
+ preferenceName: string;
314
+ scope: PreferenceScope;
315
+ effectingScope: PreferenceScope;
316
+ showReset: boolean;
317
+ }) => {
318
+ const settingsService: PreferenceSettingsService = useInjectable(IPreferenceSettingsService);
319
+ return (
320
+ <span className={styles.preference_status}>
321
+ {effectingScope === PreferenceScope.Workspace && scope === PreferenceScope.User ? (
322
+ <span
323
+ onClick={() => {
324
+ settingsService.selectScope(PreferenceScope.Workspace);
325
+ settingsService.scrollToPreference(preferenceName);
326
+ }}
327
+ className={styles.preference_overwritten}
328
+ >
329
+ {localize('preference.overwrittenInWorkspace')}
330
+ </span>
331
+ ) : undefined}
332
+ {effectingScope === PreferenceScope.User && scope === PreferenceScope.Workspace ? (
333
+ <span
334
+ onClick={() => {
335
+ settingsService.selectScope(PreferenceScope.User);
336
+ settingsService.scrollToPreference(preferenceName);
337
+ }}
338
+ className={styles.preference_overwritten}
339
+ >
340
+ {localize('preference.overwrittenInUser')}
341
+ </span>
342
+ ) : undefined}
343
+ {showReset ? (
344
+ <span
345
+ className={classnames(styles.preference_reset, getIcon('rollback'))}
346
+ onClick={() => {
347
+ settingsService.reset(preferenceName, scope);
348
+ }}
349
+ ></span>
350
+ ) : undefined}
351
+ </span>
352
+ );
353
+ };
354
+
355
+ function InputPreferenceItem({
356
+ preferenceName,
357
+ localizedName,
358
+ currentValue,
359
+ renderedDescription,
360
+ isNumber,
361
+ effectingScope,
362
+ scope,
363
+ isModified,
364
+ }: IPreferenceItemProps & { isNumber?: boolean }) {
365
+ const preferenceService: PreferenceService = useInjectable(PreferenceService);
366
+ const schemaProvider: PreferenceSchemaProvider = useInjectable(PreferenceSchemaProvider);
367
+ const [value, setValue] = useState<string>();
368
+
369
+ useEffect(() => {
370
+ setValue(currentValue);
371
+ }, [currentValue]);
372
+
373
+ const handleValueChange = (value) => {
374
+ if (hasValidateError(isNumber && /^[0-9]+$/.test(value) ? Number(value) : value)) {
375
+ return;
376
+ }
377
+
378
+ preferenceService.set(preferenceName, value, scope);
379
+ };
380
+
381
+ function hasValidateError(value): ValidateMessage | undefined {
382
+ const res = schemaProvider.validate(preferenceName, value);
383
+ if (res.valid) {
384
+ return undefined;
385
+ } else {
386
+ return {
387
+ type: 2,
388
+ message: res.reason,
389
+ };
390
+ }
391
+ }
392
+
393
+ return (
394
+ <>
395
+ <div className={classnames(styles.key, styles.item)}>
396
+ {localizedName}{' '}
397
+ <SettingStatus
398
+ preferenceName={preferenceName}
399
+ scope={scope}
400
+ effectingScope={effectingScope}
401
+ showReset={isModified}
402
+ />
403
+ </div>
404
+ {renderedDescription}
405
+ <div className={styles.control_wrap}>
406
+ <div className={styles.text_control}>
407
+ <ValidateInput
408
+ type={isNumber ? 'number' : 'text'}
409
+ validate={hasValidateError}
410
+ onBlur={() => {
411
+ handleValueChange(value);
412
+ }}
413
+ onValueChange={(value) => {
414
+ setValue(value);
415
+ }}
416
+ value={value}
417
+ />
418
+ </div>
419
+ </div>
420
+ </>
421
+ );
422
+ }
423
+
424
+ function CheckboxPreferenceItem({
425
+ preferenceName,
426
+ localizedName,
427
+ renderedDescription,
428
+ currentValue,
429
+ effectingScope,
430
+ scope,
431
+ isModified,
432
+ }: IPreferenceItemProps) {
433
+ const preferenceService: PreferenceService = useInjectable(PreferenceService);
434
+
435
+ const [value, setValue] = useState<boolean>();
436
+
437
+ useEffect(() => {
438
+ setValue(currentValue);
439
+ }, [currentValue]);
440
+
441
+ const handleValueChange = (value) => {
442
+ setValue(value);
443
+ preferenceService.set(preferenceName, value, scope);
444
+ };
445
+
446
+ return (
447
+ <>
448
+ <div className={classnames(styles.key)}>
449
+ {localizedName}{' '}
450
+ <SettingStatus
451
+ preferenceName={preferenceName}
452
+ scope={scope}
453
+ effectingScope={effectingScope}
454
+ showReset={isModified}
455
+ />
456
+ </div>
457
+ <div className={styles.check}>
458
+ <CheckBox
459
+ checked={value}
460
+ onChange={(event) => {
461
+ handleValueChange((event.target as HTMLInputElement).checked);
462
+ }}
463
+ />
464
+ {renderedDescription}
465
+ </div>
466
+ </>
467
+ );
468
+ }
469
+
470
+ function SelectPreferenceItem({
471
+ preferenceName: preferenceId,
472
+ localizedName,
473
+ renderedDescription,
474
+ currentValue,
475
+ defaultValue,
476
+ schema,
477
+ effectingScope,
478
+ scope,
479
+ isModified,
480
+ }: IPreferenceItemProps) {
481
+ const preferenceService: PreferenceService = useInjectable(PreferenceService);
482
+ const settingsService: PreferenceSettingsService = useInjectable(IPreferenceSettingsService);
483
+ const logger: ILogger = useInjectable(ILogger);
484
+ const value = currentValue ?? defaultValue;
485
+
486
+ // 鼠标还没有划过来的时候,需要一个默认的描述信息
487
+ const defaultDescription = useMemo((): string => {
488
+ if (schema.enumDescriptions && schema.enum) {
489
+ return schema.enumDescriptions[schema.enum.indexOf(currentValue)] || '';
490
+ }
491
+ return '';
492
+ }, [schema]);
493
+ const [description, setDescription] = useState<string>(defaultDescription);
494
+
495
+ const handleValueChange = useCallback(
496
+ (val) => {
497
+ preferenceService.set(preferenceId, val, scope);
498
+ },
499
+ [preferenceService],
500
+ );
501
+
502
+ // enum 本身为 string[] | number[]
503
+ const labels = settingsService.getEnumLabels(preferenceId);
504
+ const renderEnumOptions = useCallback(() => {
505
+ const enums = schema.enum ? [...schema.enum] : [];
506
+ if (!enums.includes(defaultValue)) {
507
+ logger.warn(`default value(${defaultValue}) of ${preferenceId} not found in its enum field`);
508
+ enums.push(defaultValue);
509
+ }
510
+ return enums.map((item, idx) => {
511
+ if (typeof item === 'boolean') {
512
+ item = String(item);
513
+ }
514
+
515
+ return (
516
+ <Option
517
+ value={item}
518
+ label={replaceLocalizePlaceholder((labels[item] || item).toString())}
519
+ key={`${idx} - ${item}`}
520
+ className={styles.select_option}
521
+ >
522
+ {replaceLocalizePlaceholder((labels[item] || item).toString())}
523
+ {String(item) === String(defaultValue) && (
524
+ <div className={styles.select_default_option_tips}>{localize('preference.enum.default')}</div>
525
+ )}
526
+ </Option>
527
+ );
528
+ });
529
+ }, [schema.enum]);
530
+
531
+ const renderNoneOptions = () => (
532
+ <Option
533
+ value={localize('preference.stringArray.none')}
534
+ key={NONE_SELECT_OPTION}
535
+ label={localize('preference.stringArray.none')}
536
+ disabled
537
+ >
538
+ {localize('preference.stringArray.none')}
539
+ </Option>
540
+ );
541
+
542
+ const options = schema.enum && schema.enum.length > 0 ? renderEnumOptions() : renderNoneOptions();
543
+
544
+ // 处理鼠标移动时候对应枚举值描述的变化
545
+ const handleDescriptionChange = useCallback(
546
+ (_, index) => {
547
+ if (schema.enumDescriptions) {
548
+ const description = schema.enumDescriptions[index];
549
+ if (description) {
550
+ setDescription(description);
551
+ } else {
552
+ // 对应的描述不存在,则设置为空,在渲染时会过滤掉 falsy 的值
553
+ setDescription('');
554
+ }
555
+ }
556
+ },
557
+ [schema.enumDescriptions, setDescription],
558
+ );
559
+
560
+ return (
561
+ <>
562
+ <div className={styles.key}>
563
+ {localizedName}{' '}
564
+ <SettingStatus
565
+ preferenceName={preferenceId}
566
+ scope={scope}
567
+ effectingScope={effectingScope}
568
+ showReset={isModified}
569
+ />
570
+ </div>
571
+ {renderedDescription}
572
+ <div className={styles.control_wrap}>
573
+ <Select
574
+ dropdownRenderType='absolute'
575
+ maxHeight='200'
576
+ onChange={handleValueChange}
577
+ value={value}
578
+ className={styles.select_control}
579
+ description={description}
580
+ onMouseEnter={handleDescriptionChange}
581
+ notMatchWarning={isModified ? formatLocalize('preference.item.notValid', value) : ''}
582
+ >
583
+ {options}
584
+ </Select>
585
+ </div>
586
+ </>
587
+ );
588
+ }
589
+
590
+ function EditInSettingsJsonPreferenceItem({
591
+ preferenceName,
592
+ localizedName,
593
+ schema,
594
+ renderedDescription,
595
+ effectingScope,
596
+ scope,
597
+ hasValueInScope,
598
+ }: IPreferenceItemProps) {
599
+ const settingsService: PreferenceSettingsService = useInjectable(IPreferenceSettingsService);
600
+
601
+ const editSettingsJson = async () => {
602
+ settingsService.openJSON(scope, preferenceName);
603
+ };
604
+
605
+ return (
606
+ <>
607
+ <div className={styles.key}>
608
+ {localizedName}{' '}
609
+ <SettingStatus
610
+ preferenceName={preferenceName}
611
+ scope={scope}
612
+ effectingScope={effectingScope}
613
+ showReset={hasValueInScope}
614
+ />
615
+ </div>
616
+ {renderedDescription}
617
+ <div className={styles.control_wrap}>
618
+ <a onClick={editSettingsJson}>{localize('preference.editSettingsJson')}</a>
619
+ </div>
620
+ </>
621
+ );
622
+ }
623
+
624
+ function StringArrayPreferenceItem({
625
+ preferenceName,
626
+ localizedName,
627
+ currentValue,
628
+ renderedDescription,
629
+ effectingScope,
630
+ scope,
631
+ isModified,
632
+ }: IPreferenceItemProps) {
633
+ const preferenceService: PreferenceService = useInjectable(PreferenceService);
634
+ const [value, setValue] = useState<string[]>([]);
635
+ const [inputValue, setInputValue] = useState<string>();
636
+ const [editValue, setEditValue] = useState<string>();
637
+ const [currentEditIndex, setCurrentEditIndex] = useState<number>(-1);
638
+
639
+ useEffect(() => {
640
+ setValue(currentValue || []);
641
+ }, [currentValue]);
642
+
643
+ useEffect(() => {
644
+ if (currentEditIndex >= 0) {
645
+ setEditValue(value[currentEditIndex]);
646
+ } else {
647
+ setEditValue('');
648
+ }
649
+ }, [currentEditIndex]);
650
+
651
+ const handleValueChange = (value) => {
652
+ setValue(value);
653
+ preferenceService.set(preferenceName, value, scope);
654
+ };
655
+
656
+ const addItem = () => {
657
+ if (inputValue) {
658
+ const newValue = value.slice(0);
659
+ if (newValue.indexOf(inputValue) > -1) {
660
+ return;
661
+ }
662
+ newValue.push(inputValue);
663
+ setInputValue('');
664
+ handleValueChange(newValue);
665
+ }
666
+ };
667
+
668
+ const removeItem = (idx: number) => {
669
+ const newValue = value.slice(0);
670
+ newValue.splice(idx, 1);
671
+ if (newValue.length) {
672
+ handleValueChange(newValue);
673
+ } else {
674
+ handleValueChange([]);
675
+ }
676
+ };
677
+
678
+ const editItem = (index: number) => {
679
+ setCurrentEditIndex(index);
680
+ };
681
+
682
+ const handleInputValueChange = (value: string) => {
683
+ setInputValue(value);
684
+ };
685
+
686
+ const items = value.map((item, idx) => {
687
+ if (currentEditIndex >= 0 && currentEditIndex === idx) {
688
+ return <li className={styles.array_items} key={`${idx} - ${JSON.stringify(item)}`}></li>;
689
+ } else {
690
+ return (
691
+ <li className={styles.array_items} key={`${idx} - ${JSON.stringify(item)}`}>
692
+ <div className={styles.array_item}>{typeof item === 'string' ? item : JSON.stringify(item)}</div>
693
+ <div className={styles.operate}>
694
+ <Button
695
+ type='icon'
696
+ title={localize('preference.stringArray.operate.edit')}
697
+ onClick={() => {
698
+ editItem(idx);
699
+ }}
700
+ className={classnames(getIcon('edit'), styles.array_item)}
701
+ ></Button>
702
+ <Button
703
+ type='icon'
704
+ title={localize('preference.stringArray.operate.delete')}
705
+ onClick={() => {
706
+ removeItem(idx);
707
+ }}
708
+ className={classnames(getIcon('delete'), styles.array_item)}
709
+ ></Button>
710
+ </div>
711
+ </li>
712
+ );
713
+ }
714
+ });
715
+
716
+ const renderEditInput = () => {
717
+ const commit = () => {
718
+ const newValue = value.slice(0);
719
+ if (editValue) {
720
+ newValue[currentEditIndex] = editValue;
721
+ } else {
722
+ newValue.splice(currentEditIndex, 1);
723
+ }
724
+ setValue(newValue);
725
+ preferenceService.set(preferenceName, newValue, scope);
726
+ setCurrentEditIndex(-1);
727
+ };
728
+
729
+ const handleEditValueChange = (value: string) => {
730
+ setEditValue(value);
731
+ };
732
+
733
+ if (currentEditIndex >= 0) {
734
+ return (
735
+ <div
736
+ className={styles.array_edit_wrapper}
737
+ style={{
738
+ top: currentEditIndex * 24,
739
+ }}
740
+ >
741
+ <Input
742
+ type='text'
743
+ className={styles.array_edit_input}
744
+ value={editValue}
745
+ onValueChange={handleEditValueChange}
746
+ onPressEnter={commit}
747
+ addonAfter={[
748
+ <div className={styles.array_edit_input_tip}>{localize('preference.stringArray.operate.editTip')}</div>,
749
+ ]}
750
+ />
751
+ </div>
752
+ );
753
+ }
754
+ };
755
+
756
+ return (
757
+ <>
758
+ <div className={styles.key}>
759
+ {localizedName}{' '}
760
+ <SettingStatus
761
+ preferenceName={preferenceName}
762
+ scope={scope}
763
+ effectingScope={effectingScope}
764
+ showReset={isModified}
765
+ />
766
+ </div>
767
+ {renderedDescription}
768
+ <div className={styles.control_wrap}>
769
+ <ul className={styles.array_items_wrapper}>
770
+ {items}
771
+ {renderEditInput()}
772
+ </ul>
773
+ <div className={styles.preferences_flex_row}>
774
+ <Input
775
+ type='text'
776
+ className={styles.text_control}
777
+ value={inputValue}
778
+ onValueChange={handleInputValueChange}
779
+ />
780
+ <Button className={styles.add_button} onClick={addItem}>
781
+ {localize('preference.array.additem', '添加')}
782
+ </Button>
783
+ </div>
784
+ </div>
785
+ </>
786
+ );
787
+ }