@theia/toolbar 1.45.0 → 1.46.0-next.72

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 (53) hide show
  1. package/README.md +32 -32
  2. package/lib/browser/abstract-toolbar-contribution.d.ts +16 -16
  3. package/lib/browser/abstract-toolbar-contribution.js +68 -68
  4. package/lib/browser/application-shell-with-toolbar-override.d.ts +15 -15
  5. package/lib/browser/application-shell-with-toolbar-override.js +101 -101
  6. package/lib/browser/codicons.d.ts +1 -1
  7. package/lib/browser/codicons.js +20 -20
  8. package/lib/browser/font-awesome-icons.d.ts +1 -1
  9. package/lib/browser/font-awesome-icons.js +20 -20
  10. package/lib/browser/package.spec.js +18 -18
  11. package/lib/browser/toolbar-command-contribution.d.ts +25 -25
  12. package/lib/browser/toolbar-command-contribution.js +211 -211
  13. package/lib/browser/toolbar-command-quick-input-service.d.ts +19 -19
  14. package/lib/browser/toolbar-command-quick-input-service.js +112 -112
  15. package/lib/browser/toolbar-constants.d.ts +23 -23
  16. package/lib/browser/toolbar-constants.js +75 -75
  17. package/lib/browser/toolbar-controller.d.ts +34 -34
  18. package/lib/browser/toolbar-controller.js +186 -186
  19. package/lib/browser/toolbar-defaults.d.ts +3 -3
  20. package/lib/browser/toolbar-defaults.js +60 -60
  21. package/lib/browser/toolbar-frontend-module.d.ts +4 -4
  22. package/lib/browser/toolbar-frontend-module.js +25 -25
  23. package/lib/browser/toolbar-icon-selector-dialog.d.ts +65 -65
  24. package/lib/browser/toolbar-icon-selector-dialog.js +235 -235
  25. package/lib/browser/toolbar-interfaces.d.ts +45 -45
  26. package/lib/browser/toolbar-interfaces.js +42 -42
  27. package/lib/browser/toolbar-preference-contribution.d.ts +9 -9
  28. package/lib/browser/toolbar-preference-contribution.js +34 -34
  29. package/lib/browser/toolbar-preference-schema.d.ts +5 -5
  30. package/lib/browser/toolbar-preference-schema.js +73 -73
  31. package/lib/browser/toolbar-storage-provider.d.ts +47 -47
  32. package/lib/browser/toolbar-storage-provider.js +357 -357
  33. package/lib/browser/toolbar.d.ts +56 -56
  34. package/lib/browser/toolbar.js +380 -380
  35. package/package.json +11 -11
  36. package/src/browser/abstract-toolbar-contribution.tsx +53 -53
  37. package/src/browser/application-shell-with-toolbar-override.ts +98 -98
  38. package/src/browser/codicons.ts +18 -18
  39. package/src/browser/font-awesome-icons.ts +18 -18
  40. package/src/browser/package.spec.ts +19 -19
  41. package/src/browser/style/toolbar.css +255 -255
  42. package/src/browser/toolbar-command-contribution.ts +211 -211
  43. package/src/browser/toolbar-command-quick-input-service.ts +86 -86
  44. package/src/browser/toolbar-constants.ts +79 -79
  45. package/src/browser/toolbar-controller.ts +185 -185
  46. package/src/browser/toolbar-defaults.ts +58 -58
  47. package/src/browser/toolbar-frontend-module.ts +30 -30
  48. package/src/browser/toolbar-icon-selector-dialog.tsx +296 -296
  49. package/src/browser/toolbar-interfaces.ts +76 -76
  50. package/src/browser/toolbar-preference-contribution.ts +38 -38
  51. package/src/browser/toolbar-preference-schema.ts +75 -75
  52. package/src/browser/toolbar-storage-provider.ts +352 -352
  53. package/src/browser/toolbar.tsx +424 -424
@@ -1,30 +1,30 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2022 Ericsson and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- import '../../src/browser/style/toolbar.css';
18
- import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
19
- import { bindToolbarApplicationShell } from './application-shell-with-toolbar-override';
20
- import { bindToolbar } from './toolbar-command-contribution';
21
-
22
- export default new ContainerModule((
23
- bind: interfaces.Bind,
24
- unbind: interfaces.Unbind,
25
- _isBound: interfaces.IsBound,
26
- rebind: interfaces.Rebind,
27
- ) => {
28
- bindToolbarApplicationShell(bind, rebind, unbind);
29
- bindToolbar(bind);
30
- });
1
+ // *****************************************************************************
2
+ // Copyright (C) 2022 Ericsson and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import '../../src/browser/style/toolbar.css';
18
+ import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
19
+ import { bindToolbarApplicationShell } from './application-shell-with-toolbar-override';
20
+ import { bindToolbar } from './toolbar-command-contribution';
21
+
22
+ export default new ContainerModule((
23
+ bind: interfaces.Bind,
24
+ unbind: interfaces.Unbind,
25
+ _isBound: interfaces.IsBound,
26
+ rebind: interfaces.Rebind,
27
+ ) => {
28
+ bindToolbarApplicationShell(bind, rebind, unbind);
29
+ bindToolbar(bind);
30
+ });
@@ -1,296 +1,296 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2022 Ericsson and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- import * as React from '@theia/core/shared/react';
18
- import { createRoot, Root } from '@theia/core/shared/react-dom/client';
19
- import { injectable, interfaces, inject, postConstruct } from '@theia/core/shared/inversify';
20
- import debounce = require('@theia/core/shared/lodash.debounce');
21
- import { ReactDialog } from '@theia/core/lib/browser/dialogs/react-dialog';
22
- import { DEFAULT_SCROLL_OPTIONS, Dialog, DialogProps, Message } from '@theia/core/lib/browser';
23
- import { Command, Disposable, nls } from '@theia/core';
24
- import { FileService } from '@theia/filesystem/lib/browser/file-service';
25
- import { Deferred } from '@theia/core/lib/common/promise-util';
26
- import PerfectScrollbar from 'perfect-scrollbar';
27
- import { FuzzySearch } from '@theia/core/lib/browser/tree/fuzzy-search';
28
- import { codicons } from './codicons';
29
- import { fontAwesomeIcons } from './font-awesome-icons';
30
- import { IconSet } from './toolbar-interfaces';
31
- import { ReactInteraction, ReactKeyboardEvent } from './toolbar-constants';
32
-
33
- export interface ToolbarIconDialogFactory {
34
- (command: Command): ToolbarIconSelectorDialog;
35
- }
36
-
37
- export const ToolbarIconDialogFactory = Symbol('ToolbarIconDialogFactory');
38
- export const ToolbarCommand = Symbol('ToolbarCommand');
39
- export const FontAwesomeIcons = Symbol('FontAwesomeIcons');
40
- export const CodiconIcons = Symbol('CodiconIcons');
41
-
42
- const FIFTY_MS = 50;
43
- @injectable()
44
- export class ToolbarIconSelectorDialog extends ReactDialog<string | undefined> {
45
- @inject(ToolbarCommand) protected readonly toolbarCommand: Command;
46
- @inject(FileService) protected readonly fileService: FileService;
47
- @inject(FontAwesomeIcons) protected readonly faIcons: string[];
48
- @inject(CodiconIcons) protected readonly codiconIcons: string[];
49
- @inject(FuzzySearch) protected readonly fuzzySearch: FuzzySearch;
50
-
51
- static ID = 'toolbar-icon-selector-dialog';
52
- protected deferredScrollContainer = new Deferred<HTMLDivElement>();
53
- override scrollOptions: PerfectScrollbar.Options = { ...DEFAULT_SCROLL_OPTIONS };
54
- protected filterRef: HTMLInputElement;
55
-
56
- protected selectedIcon: string | undefined;
57
- protected activeIconPrefix: IconSet = IconSet.CODICON;
58
- protected iconSets = new Map<string, string[]>();
59
- protected filteredIcons: string[] = [];
60
- protected doShowFilterPlaceholder = false;
61
- protected debounceHandleSearch = debounce(this.doHandleSearch.bind(this), FIFTY_MS, { trailing: true });
62
- protected controlPanelRoot: Root;
63
-
64
- constructor(
65
- @inject(DialogProps) protected override readonly props: DialogProps,
66
- ) {
67
- super(props);
68
- this.controlPanelRoot = createRoot(this.controlPanel);
69
- this.toDispose.push(Disposable.create(() => this.controlPanelRoot.unmount()));
70
- }
71
-
72
- protected override onUpdateRequest(msg: Message): void {
73
- super.onUpdateRequest(msg);
74
- this.controlPanelRoot.render(this.renderControls());
75
- }
76
-
77
- @postConstruct()
78
- protected init(): void {
79
- this.node.id = ToolbarIconSelectorDialog.ID;
80
- this.iconSets.set(IconSet.FA, this.faIcons);
81
- this.iconSets.set(IconSet.CODICON, this.codiconIcons);
82
- this.activeIconPrefix = IconSet.CODICON;
83
- const initialIcons = this.iconSets.get(this.activeIconPrefix);
84
- if (initialIcons) {
85
- this.filteredIcons = initialIcons;
86
- }
87
- }
88
-
89
- override async getScrollContainer(): Promise<HTMLElement> {
90
- return this.deferredScrollContainer.promise;
91
- }
92
-
93
- protected assignScrollContainerRef = (element: HTMLDivElement): void => this.doAssignScrollContainerRef(element);
94
- protected doAssignScrollContainerRef(element: HTMLDivElement): void {
95
- this.deferredScrollContainer.resolve(element);
96
- }
97
-
98
- protected assignFilterRef = (element: HTMLInputElement): void => this.doAssignFilterRef(element);
99
- protected doAssignFilterRef(element: HTMLInputElement): void {
100
- this.filterRef = element;
101
- }
102
-
103
- get value(): string | undefined {
104
- return this.selectedIcon;
105
- }
106
-
107
- protected handleSelectOnChange = async (e: React.ChangeEvent<HTMLSelectElement>): Promise<void> => this.doHandleSelectOnChange(e);
108
- protected async doHandleSelectOnChange(e: React.ChangeEvent<HTMLSelectElement>): Promise<void> {
109
- const { value } = e.target;
110
- this.activeIconPrefix = value as IconSet;
111
- this.filteredIcons = [];
112
- await this.doHandleSearch();
113
- this.update();
114
- }
115
-
116
- protected renderIconSelectorOptions(): React.ReactNode {
117
- return (
118
- <div className='icon-selector-options'>
119
- <div className='icon-set-selector-wrapper'>
120
- {nls.localize('theia/toolbar/iconSet', 'Icon Set')}
121
- {': '}
122
- <select
123
- className='toolbar-icon-select theia-select'
124
- onChange={this.handleSelectOnChange}
125
- defaultValue={IconSet.CODICON}
126
- >
127
- <option key={IconSet.CODICON} value={IconSet.CODICON}>Codicon</option>
128
- <option key={IconSet.FA} value={IconSet.FA}>Font Awesome</option>
129
- </select>
130
- </div>
131
- <div className='icon-fuzzy-filter'>
132
- <input
133
- ref={this.assignFilterRef}
134
- placeholder={nls.localize('theia/toolbar/filterIcons', 'Filter Icons')}
135
- type='text'
136
- className='icon-filter-input theia-input'
137
- onChange={this.debounceHandleSearch}
138
- spellCheck={false}
139
- />
140
- </div>
141
- </div >
142
- );
143
- }
144
-
145
- protected renderIconGrid(): React.ReactNode {
146
- return (
147
- <div
148
- className='toolbar-scroll-container'
149
- ref={this.assignScrollContainerRef}
150
- >
151
- <div
152
- className={`toolbar-icon-dialog-content ${this.doShowFilterPlaceholder ? '' : 'grid'}`}
153
- >
154
- {!this.doShowFilterPlaceholder ? this.filteredIcons?.map(icon => (
155
- <div
156
- className='icon-wrapper'
157
- key={icon}
158
- role='button'
159
- onClick={this.handleOnIconClick}
160
- onBlur={this.handleOnIconBlur}
161
- tabIndex={0}
162
- data-id={`${this.activeIconPrefix} ${icon}`}
163
- title={icon}
164
- onKeyPress={this.handleOnIconClick}
165
- >
166
- <div className={`${this.activeIconPrefix} ${icon}`} />
167
- </div>
168
- ))
169
- : <div className='search-placeholder'>{nls.localizeByDefault('No results found')}</div>}
170
- </div>
171
- </div>
172
- );
173
- }
174
-
175
- protected render(): React.ReactNode {
176
- return (
177
- <>
178
- {this.renderIconSelectorOptions()}
179
- {this.renderIconGrid()}
180
- </>
181
- );
182
- }
183
-
184
- protected async doHandleSearch(): Promise<void> {
185
- const query = this.filterRef.value;
186
- const pattern = query;
187
- const items = this.iconSets.get(this.activeIconPrefix);
188
- if (items) {
189
- if (pattern.length) {
190
- const transform = (item: string): string => item;
191
- const filterResults = await this.fuzzySearch.filter({ pattern, items, transform });
192
- this.filteredIcons = filterResults.map(result => result.item);
193
- if (!this.filteredIcons.length) {
194
- this.doShowFilterPlaceholder = true;
195
- } else {
196
- this.doShowFilterPlaceholder = false;
197
- }
198
- } else {
199
- this.doShowFilterPlaceholder = false;
200
- this.filteredIcons = items;
201
- }
202
- this.update();
203
- }
204
- }
205
-
206
- protected handleOnIconClick = (e: ReactInteraction<HTMLDivElement>): void => this.doHandleOnIconClick(e);
207
- protected doHandleOnIconClick(e: ReactInteraction<HTMLDivElement>): void {
208
- e.currentTarget.classList.add('selected');
209
- if (ReactKeyboardEvent.is(e) && e.key !== 'Enter') {
210
- return;
211
- }
212
- const iconId = e.currentTarget.getAttribute('data-id');
213
- if (iconId) {
214
- this.selectedIcon = iconId;
215
- this.update();
216
- }
217
- }
218
-
219
- protected handleOnIconBlur = (e: React.FocusEvent<HTMLDivElement>): void => this.doHandleOnIconBlur(e);
220
- protected doHandleOnIconBlur(e: React.FocusEvent<HTMLDivElement>): void {
221
- e.currentTarget.classList.remove('selected');
222
- }
223
-
224
- protected doAccept = (e: ReactInteraction<HTMLButtonElement>): void => {
225
- const dataId = e.currentTarget.getAttribute('data-id');
226
- if (dataId === 'default-accept') {
227
- this.selectedIcon = this.toolbarCommand.iconClass;
228
- }
229
- this.accept();
230
- };
231
-
232
- protected doClose = (): void => {
233
- this.selectedIcon = undefined;
234
- this.close();
235
- };
236
-
237
- protected renderControls(): React.ReactElement {
238
- return (
239
- <div className='toolbar-icon-controls'>
240
- <div>
241
- {this.toolbarCommand.iconClass
242
- && (
243
- <button
244
- type='button'
245
- className='theia-button main default-button'
246
- data-id='default-accept'
247
- onClick={this.doAccept}
248
- >
249
- <span>
250
- {`${nls.localize('theia/toolbar/useDefaultIcon', 'Use Default Icon')}:`}
251
- </span>
252
- <div className={`toolbar-default-icon ${this.toolbarCommand.iconClass}`} />
253
- </button>
254
- )}
255
- </div>
256
- <div>
257
- <button
258
- type='button'
259
- disabled={!this.selectedIcon}
260
- className='theia-button main'
261
- onClick={this.doAccept}
262
- >
263
- {nls.localize('theia/toolbar/selectIcon', 'Select Icon')}
264
- </button>
265
- <button
266
- type='button'
267
- className='theia-button secondary'
268
- onClick={this.doClose}
269
- >
270
- {Dialog.CANCEL}
271
- </button>
272
-
273
- </div>
274
- </div>
275
- );
276
- }
277
- }
278
-
279
- export const ICON_DIALOG_WIDTH = 600;
280
- export const ICON_DIALOG_PADDING = 24;
281
-
282
- export const bindToolbarIconDialog = (bind: interfaces.Bind): void => {
283
- bind(ToolbarIconDialogFactory).toFactory(ctx => (command: Command): ToolbarIconSelectorDialog => {
284
- const child = ctx.container.createChild();
285
- child.bind(DialogProps).toConstantValue({
286
- title: nls.localize('theia/toolbar/iconSelectDialog', "Select an Icon for '{0}'", command.label),
287
- maxWidth: ICON_DIALOG_WIDTH + ICON_DIALOG_PADDING,
288
- });
289
- child.bind(FontAwesomeIcons).toConstantValue(fontAwesomeIcons);
290
- child.bind(CodiconIcons).toConstantValue(codicons);
291
- child.bind(ToolbarCommand).toConstantValue(command);
292
- child.bind(FuzzySearch).toSelf().inSingletonScope();
293
- child.bind(ToolbarIconSelectorDialog).toSelf().inSingletonScope();
294
- return child.get(ToolbarIconSelectorDialog);
295
- });
296
- };
1
+ // *****************************************************************************
2
+ // Copyright (C) 2022 Ericsson and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import * as React from '@theia/core/shared/react';
18
+ import { createRoot, Root } from '@theia/core/shared/react-dom/client';
19
+ import { injectable, interfaces, inject, postConstruct } from '@theia/core/shared/inversify';
20
+ import debounce = require('@theia/core/shared/lodash.debounce');
21
+ import { ReactDialog } from '@theia/core/lib/browser/dialogs/react-dialog';
22
+ import { DEFAULT_SCROLL_OPTIONS, Dialog, DialogProps, Message } from '@theia/core/lib/browser';
23
+ import { Command, Disposable, nls } from '@theia/core';
24
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
25
+ import { Deferred } from '@theia/core/lib/common/promise-util';
26
+ import PerfectScrollbar from 'perfect-scrollbar';
27
+ import { FuzzySearch } from '@theia/core/lib/browser/tree/fuzzy-search';
28
+ import { codicons } from './codicons';
29
+ import { fontAwesomeIcons } from './font-awesome-icons';
30
+ import { IconSet } from './toolbar-interfaces';
31
+ import { ReactInteraction, ReactKeyboardEvent } from './toolbar-constants';
32
+
33
+ export interface ToolbarIconDialogFactory {
34
+ (command: Command): ToolbarIconSelectorDialog;
35
+ }
36
+
37
+ export const ToolbarIconDialogFactory = Symbol('ToolbarIconDialogFactory');
38
+ export const ToolbarCommand = Symbol('ToolbarCommand');
39
+ export const FontAwesomeIcons = Symbol('FontAwesomeIcons');
40
+ export const CodiconIcons = Symbol('CodiconIcons');
41
+
42
+ const FIFTY_MS = 50;
43
+ @injectable()
44
+ export class ToolbarIconSelectorDialog extends ReactDialog<string | undefined> {
45
+ @inject(ToolbarCommand) protected readonly toolbarCommand: Command;
46
+ @inject(FileService) protected readonly fileService: FileService;
47
+ @inject(FontAwesomeIcons) protected readonly faIcons: string[];
48
+ @inject(CodiconIcons) protected readonly codiconIcons: string[];
49
+ @inject(FuzzySearch) protected readonly fuzzySearch: FuzzySearch;
50
+
51
+ static ID = 'toolbar-icon-selector-dialog';
52
+ protected deferredScrollContainer = new Deferred<HTMLDivElement>();
53
+ override scrollOptions: PerfectScrollbar.Options = { ...DEFAULT_SCROLL_OPTIONS };
54
+ protected filterRef: HTMLInputElement;
55
+
56
+ protected selectedIcon: string | undefined;
57
+ protected activeIconPrefix: IconSet = IconSet.CODICON;
58
+ protected iconSets = new Map<string, string[]>();
59
+ protected filteredIcons: string[] = [];
60
+ protected doShowFilterPlaceholder = false;
61
+ protected debounceHandleSearch = debounce(this.doHandleSearch.bind(this), FIFTY_MS, { trailing: true });
62
+ protected controlPanelRoot: Root;
63
+
64
+ constructor(
65
+ @inject(DialogProps) protected override readonly props: DialogProps,
66
+ ) {
67
+ super(props);
68
+ this.controlPanelRoot = createRoot(this.controlPanel);
69
+ this.toDispose.push(Disposable.create(() => this.controlPanelRoot.unmount()));
70
+ }
71
+
72
+ protected override onUpdateRequest(msg: Message): void {
73
+ super.onUpdateRequest(msg);
74
+ this.controlPanelRoot.render(this.renderControls());
75
+ }
76
+
77
+ @postConstruct()
78
+ protected init(): void {
79
+ this.node.id = ToolbarIconSelectorDialog.ID;
80
+ this.iconSets.set(IconSet.FA, this.faIcons);
81
+ this.iconSets.set(IconSet.CODICON, this.codiconIcons);
82
+ this.activeIconPrefix = IconSet.CODICON;
83
+ const initialIcons = this.iconSets.get(this.activeIconPrefix);
84
+ if (initialIcons) {
85
+ this.filteredIcons = initialIcons;
86
+ }
87
+ }
88
+
89
+ override async getScrollContainer(): Promise<HTMLElement> {
90
+ return this.deferredScrollContainer.promise;
91
+ }
92
+
93
+ protected assignScrollContainerRef = (element: HTMLDivElement): void => this.doAssignScrollContainerRef(element);
94
+ protected doAssignScrollContainerRef(element: HTMLDivElement): void {
95
+ this.deferredScrollContainer.resolve(element);
96
+ }
97
+
98
+ protected assignFilterRef = (element: HTMLInputElement): void => this.doAssignFilterRef(element);
99
+ protected doAssignFilterRef(element: HTMLInputElement): void {
100
+ this.filterRef = element;
101
+ }
102
+
103
+ get value(): string | undefined {
104
+ return this.selectedIcon;
105
+ }
106
+
107
+ protected handleSelectOnChange = async (e: React.ChangeEvent<HTMLSelectElement>): Promise<void> => this.doHandleSelectOnChange(e);
108
+ protected async doHandleSelectOnChange(e: React.ChangeEvent<HTMLSelectElement>): Promise<void> {
109
+ const { value } = e.target;
110
+ this.activeIconPrefix = value as IconSet;
111
+ this.filteredIcons = [];
112
+ await this.doHandleSearch();
113
+ this.update();
114
+ }
115
+
116
+ protected renderIconSelectorOptions(): React.ReactNode {
117
+ return (
118
+ <div className='icon-selector-options'>
119
+ <div className='icon-set-selector-wrapper'>
120
+ {nls.localize('theia/toolbar/iconSet', 'Icon Set')}
121
+ {': '}
122
+ <select
123
+ className='toolbar-icon-select theia-select'
124
+ onChange={this.handleSelectOnChange}
125
+ defaultValue={IconSet.CODICON}
126
+ >
127
+ <option key={IconSet.CODICON} value={IconSet.CODICON}>Codicon</option>
128
+ <option key={IconSet.FA} value={IconSet.FA}>Font Awesome</option>
129
+ </select>
130
+ </div>
131
+ <div className='icon-fuzzy-filter'>
132
+ <input
133
+ ref={this.assignFilterRef}
134
+ placeholder={nls.localize('theia/toolbar/filterIcons', 'Filter Icons')}
135
+ type='text'
136
+ className='icon-filter-input theia-input'
137
+ onChange={this.debounceHandleSearch}
138
+ spellCheck={false}
139
+ />
140
+ </div>
141
+ </div >
142
+ );
143
+ }
144
+
145
+ protected renderIconGrid(): React.ReactNode {
146
+ return (
147
+ <div
148
+ className='toolbar-scroll-container'
149
+ ref={this.assignScrollContainerRef}
150
+ >
151
+ <div
152
+ className={`toolbar-icon-dialog-content ${this.doShowFilterPlaceholder ? '' : 'grid'}`}
153
+ >
154
+ {!this.doShowFilterPlaceholder ? this.filteredIcons?.map(icon => (
155
+ <div
156
+ className='icon-wrapper'
157
+ key={icon}
158
+ role='button'
159
+ onClick={this.handleOnIconClick}
160
+ onBlur={this.handleOnIconBlur}
161
+ tabIndex={0}
162
+ data-id={`${this.activeIconPrefix} ${icon}`}
163
+ title={icon}
164
+ onKeyPress={this.handleOnIconClick}
165
+ >
166
+ <div className={`${this.activeIconPrefix} ${icon}`} />
167
+ </div>
168
+ ))
169
+ : <div className='search-placeholder'>{nls.localizeByDefault('No results found')}</div>}
170
+ </div>
171
+ </div>
172
+ );
173
+ }
174
+
175
+ protected render(): React.ReactNode {
176
+ return (
177
+ <>
178
+ {this.renderIconSelectorOptions()}
179
+ {this.renderIconGrid()}
180
+ </>
181
+ );
182
+ }
183
+
184
+ protected async doHandleSearch(): Promise<void> {
185
+ const query = this.filterRef.value;
186
+ const pattern = query;
187
+ const items = this.iconSets.get(this.activeIconPrefix);
188
+ if (items) {
189
+ if (pattern.length) {
190
+ const transform = (item: string): string => item;
191
+ const filterResults = await this.fuzzySearch.filter({ pattern, items, transform });
192
+ this.filteredIcons = filterResults.map(result => result.item);
193
+ if (!this.filteredIcons.length) {
194
+ this.doShowFilterPlaceholder = true;
195
+ } else {
196
+ this.doShowFilterPlaceholder = false;
197
+ }
198
+ } else {
199
+ this.doShowFilterPlaceholder = false;
200
+ this.filteredIcons = items;
201
+ }
202
+ this.update();
203
+ }
204
+ }
205
+
206
+ protected handleOnIconClick = (e: ReactInteraction<HTMLDivElement>): void => this.doHandleOnIconClick(e);
207
+ protected doHandleOnIconClick(e: ReactInteraction<HTMLDivElement>): void {
208
+ e.currentTarget.classList.add('selected');
209
+ if (ReactKeyboardEvent.is(e) && e.key !== 'Enter') {
210
+ return;
211
+ }
212
+ const iconId = e.currentTarget.getAttribute('data-id');
213
+ if (iconId) {
214
+ this.selectedIcon = iconId;
215
+ this.update();
216
+ }
217
+ }
218
+
219
+ protected handleOnIconBlur = (e: React.FocusEvent<HTMLDivElement>): void => this.doHandleOnIconBlur(e);
220
+ protected doHandleOnIconBlur(e: React.FocusEvent<HTMLDivElement>): void {
221
+ e.currentTarget.classList.remove('selected');
222
+ }
223
+
224
+ protected doAccept = (e: ReactInteraction<HTMLButtonElement>): void => {
225
+ const dataId = e.currentTarget.getAttribute('data-id');
226
+ if (dataId === 'default-accept') {
227
+ this.selectedIcon = this.toolbarCommand.iconClass;
228
+ }
229
+ this.accept();
230
+ };
231
+
232
+ protected doClose = (): void => {
233
+ this.selectedIcon = undefined;
234
+ this.close();
235
+ };
236
+
237
+ protected renderControls(): React.ReactElement {
238
+ return (
239
+ <div className='toolbar-icon-controls'>
240
+ <div>
241
+ {this.toolbarCommand.iconClass
242
+ && (
243
+ <button
244
+ type='button'
245
+ className='theia-button main default-button'
246
+ data-id='default-accept'
247
+ onClick={this.doAccept}
248
+ >
249
+ <span>
250
+ {`${nls.localize('theia/toolbar/useDefaultIcon', 'Use Default Icon')}:`}
251
+ </span>
252
+ <div className={`toolbar-default-icon ${this.toolbarCommand.iconClass}`} />
253
+ </button>
254
+ )}
255
+ </div>
256
+ <div>
257
+ <button
258
+ type='button'
259
+ disabled={!this.selectedIcon}
260
+ className='theia-button main'
261
+ onClick={this.doAccept}
262
+ >
263
+ {nls.localize('theia/toolbar/selectIcon', 'Select Icon')}
264
+ </button>
265
+ <button
266
+ type='button'
267
+ className='theia-button secondary'
268
+ onClick={this.doClose}
269
+ >
270
+ {Dialog.CANCEL}
271
+ </button>
272
+
273
+ </div>
274
+ </div>
275
+ );
276
+ }
277
+ }
278
+
279
+ export const ICON_DIALOG_WIDTH = 600;
280
+ export const ICON_DIALOG_PADDING = 24;
281
+
282
+ export const bindToolbarIconDialog = (bind: interfaces.Bind): void => {
283
+ bind(ToolbarIconDialogFactory).toFactory(ctx => (command: Command): ToolbarIconSelectorDialog => {
284
+ const child = ctx.container.createChild();
285
+ child.bind(DialogProps).toConstantValue({
286
+ title: nls.localize('theia/toolbar/iconSelectDialog', "Select an Icon for '{0}'", command.label),
287
+ maxWidth: ICON_DIALOG_WIDTH + ICON_DIALOG_PADDING,
288
+ });
289
+ child.bind(FontAwesomeIcons).toConstantValue(fontAwesomeIcons);
290
+ child.bind(CodiconIcons).toConstantValue(codicons);
291
+ child.bind(ToolbarCommand).toConstantValue(command);
292
+ child.bind(FuzzySearch).toSelf().inSingletonScope();
293
+ child.bind(ToolbarIconSelectorDialog).toSelf().inSingletonScope();
294
+ return child.get(ToolbarIconSelectorDialog);
295
+ });
296
+ };