Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/chat/browser/runScriptCustomTaskWidget.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './media/runScriptAction.css';
7
8
import * as dom from '../../../../base/browser/dom.js';
9
import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
10
import { Button } from '../../../../base/browser/ui/button/button.js';
11
import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
12
import { Radio } from '../../../../base/browser/ui/radio/radio.js';
13
import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js';
14
import { Emitter, Event } from '../../../../base/common/event.js';
15
import { Disposable } from '../../../../base/common/lifecycle.js';
16
import { KeyCode } from '../../../../base/common/keyCodes.js';
17
import { localize } from '../../../../nls.js';
18
import { defaultButtonStyles, defaultCheckboxStyles, defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
19
import { TaskStorageTarget } from './sessionsConfigurationService.js';
20
21
export const WORKTREE_CREATED_RUN_ON = 'worktreeCreated' as const;
22
23
export interface IRunScriptCustomTaskWidgetState {
24
readonly label?: string;
25
readonly labelDisabledReason?: string;
26
readonly command?: string;
27
readonly commandDisabledReason?: string;
28
readonly target?: TaskStorageTarget;
29
readonly targetDisabledReason?: string;
30
readonly runOn?: typeof WORKTREE_CREATED_RUN_ON;
31
readonly mode?: 'add' | 'add-existing' | 'configure';
32
}
33
34
export interface IRunScriptCustomTaskWidgetResult {
35
readonly label?: string;
36
readonly command: string;
37
readonly target: TaskStorageTarget;
38
readonly runOn?: typeof WORKTREE_CREATED_RUN_ON;
39
}
40
41
export class RunScriptCustomTaskWidget extends Disposable {
42
43
readonly domNode: HTMLElement;
44
45
private readonly _labelInput: InputBox;
46
private readonly _commandInput: InputBox;
47
private readonly _runOnCheckbox: Checkbox;
48
private readonly _storageOptions: Radio;
49
private readonly _submitButton: Button;
50
private readonly _cancelButton: Button;
51
private readonly _labelLocked: boolean;
52
private readonly _commandLocked: boolean;
53
private readonly _targetLocked: boolean;
54
private readonly _isExistingTask: boolean;
55
private readonly _isAddExistingTask: boolean;
56
private readonly _initialLabel: string;
57
private readonly _initialCommand: string;
58
private readonly _initialRunOn: boolean;
59
private readonly _initialTarget: TaskStorageTarget;
60
private _selectedTarget: TaskStorageTarget;
61
62
private readonly _onDidSubmit = this._register(new Emitter<IRunScriptCustomTaskWidgetResult>());
63
readonly onDidSubmit: Event<IRunScriptCustomTaskWidgetResult> = this._onDidSubmit.event;
64
65
private readonly _onDidCancel = this._register(new Emitter<void>());
66
readonly onDidCancel: Event<void> = this._onDidCancel.event;
67
68
constructor(state: IRunScriptCustomTaskWidgetState) {
69
super();
70
71
this._labelLocked = !!state.labelDisabledReason;
72
this._commandLocked = !!state.commandDisabledReason;
73
this._targetLocked = !!state.targetDisabledReason && state.target !== undefined;
74
this._isExistingTask = state.mode === 'configure';
75
this._isAddExistingTask = state.mode === 'add-existing';
76
this._selectedTarget = state.target ?? (state.targetDisabledReason ? 'user' : 'workspace');
77
this._initialLabel = state.label ?? '';
78
this._initialCommand = state.command ?? '';
79
this._initialRunOn = state.runOn === WORKTREE_CREATED_RUN_ON;
80
this._initialTarget = this._selectedTarget;
81
82
this.domNode = dom.$('.run-script-action-widget');
83
84
const labelSection = dom.append(this.domNode, dom.$('.run-script-action-section'));
85
dom.append(labelSection, dom.$('label.run-script-action-label', undefined, localize('labelFieldLabel', "Name")));
86
const labelInputContainer = dom.append(labelSection, dom.$('.run-script-action-input'));
87
this._labelInput = this._register(new InputBox(labelInputContainer, undefined, {
88
placeholder: localize('enterLabelPlaceholder', "Enter a name for this task (optional)"),
89
tooltip: state.labelDisabledReason,
90
ariaLabel: localize('enterLabelAriaLabel', "Task name"),
91
inputBoxStyles: defaultInputBoxStyles,
92
}));
93
this._labelInput.value = state.label ?? '';
94
if (state.labelDisabledReason) {
95
this._labelInput.disable();
96
}
97
98
const commandSection = dom.append(this.domNode, dom.$('.run-script-action-section'));
99
dom.append(commandSection, dom.$('label.run-script-action-label', undefined, localize('commandFieldLabel', "Command")));
100
const commandInputContainer = dom.append(commandSection, dom.$('.run-script-action-input'));
101
this._commandInput = this._register(new InputBox(commandInputContainer, undefined, {
102
placeholder: localize('enterCommandPlaceholder', "Enter command (for example, npm run dev)"),
103
tooltip: state.commandDisabledReason,
104
ariaLabel: localize('enterCommandAriaLabel', "Task command"),
105
inputBoxStyles: defaultInputBoxStyles,
106
}));
107
this._commandInput.value = state.command ?? '';
108
if (state.commandDisabledReason) {
109
this._commandInput.disable();
110
}
111
112
const runOnSection = dom.append(this.domNode, dom.$('.run-script-action-section'));
113
dom.append(runOnSection, dom.$('div.run-script-action-label', undefined, localize('runOptionsLabel', "Run Options")));
114
const runOnRow = dom.append(runOnSection, dom.$('.run-script-action-option-row'));
115
this._runOnCheckbox = this._register(new Checkbox(localize('runOnWorktreeCreated', "Run When Worktree Is Created"), state.runOn === WORKTREE_CREATED_RUN_ON, defaultCheckboxStyles));
116
runOnRow.appendChild(this._runOnCheckbox.domNode);
117
const runOnText = dom.append(runOnRow, dom.$('span.run-script-action-option-text', undefined, localize('runOnWorktreeCreatedDescription', "Automatically run this task when the session worktree is created")));
118
this._register(dom.addDisposableListener(runOnText, dom.EventType.CLICK, () => this._runOnCheckbox.checked = !this._runOnCheckbox.checked));
119
120
const storageSection = dom.append(this.domNode, dom.$('.run-script-action-section'));
121
dom.append(storageSection, dom.$('div.run-script-action-label', undefined, localize('storageLabel', "Save In")));
122
const storageDisabledReason = state.targetDisabledReason;
123
if (storageDisabledReason) {
124
dom.append(storageSection, dom.$('div.run-script-action-hint', undefined, storageDisabledReason));
125
}
126
const workspaceTargetDisabled = !!storageDisabledReason;
127
this._storageOptions = this._register(new Radio({
128
items: [
129
{
130
text: localize('workspaceStorageLabel', "Workspace"),
131
tooltip: storageDisabledReason ?? localize('workspaceStorageTooltip', "Save this task in the current workspace"),
132
isActive: this._selectedTarget === 'workspace',
133
disabled: workspaceTargetDisabled,
134
},
135
{
136
text: localize('userStorageLabel', "User"),
137
tooltip: this._targetLocked ? storageDisabledReason : localize('userStorageTooltip', "Save this task in your user tasks and make it available in all sessions"),
138
isActive: this._selectedTarget === 'user',
139
disabled: this._targetLocked,
140
}
141
]
142
}));
143
this._storageOptions.domNode.setAttribute('aria-label', localize('storageAriaLabel', "Task storage target"));
144
this._storageOptions.domNode.classList.toggle('run-script-action-radio-disabled', this._targetLocked);
145
this._storageOptions.setEnabled(!this._targetLocked);
146
storageSection.appendChild(this._storageOptions.domNode);
147
148
const buttonRow = dom.append(this.domNode, dom.$('.run-script-action-buttons'));
149
this._cancelButton = this._register(new Button(buttonRow, { ...defaultButtonStyles, secondary: true }));
150
this._cancelButton.label = localize('cancelAddAction', "Cancel");
151
this._submitButton = this._register(new Button(buttonRow, defaultButtonStyles));
152
this._submitButton.label = this._getSubmitLabel();
153
154
this._register(this._labelInput.onDidChange(() => this._updateButtonState()));
155
this._register(this._commandInput.onDidChange(() => this._updateButtonState()));
156
this._register(this._storageOptions.onDidSelect(index => {
157
this._selectedTarget = index === 0 ? 'workspace' : 'user';
158
this._updateButtonState();
159
}));
160
this._register(this._runOnCheckbox.onChange(() => this._updateButtonState()));
161
this._register(this._submitButton.onDidClick(() => this._submit()));
162
this._register(this._cancelButton.onDidClick(() => this._onDidCancel.fire()));
163
this._register(dom.addDisposableListener(this._labelInput.inputElement, dom.EventType.KEY_DOWN, event => {
164
const keyboardEvent = new StandardKeyboardEvent(event);
165
if (keyboardEvent.equals(KeyCode.Enter)) {
166
keyboardEvent.preventDefault();
167
keyboardEvent.stopPropagation();
168
this._submit();
169
}
170
}));
171
this._register(dom.addDisposableListener(this._commandInput.inputElement, dom.EventType.KEY_DOWN, event => {
172
const keyboardEvent = new StandardKeyboardEvent(event);
173
if (keyboardEvent.equals(KeyCode.Enter)) {
174
keyboardEvent.preventDefault();
175
keyboardEvent.stopPropagation();
176
this._submit();
177
}
178
}));
179
this._register(dom.addDisposableListener(this.domNode, dom.EventType.KEY_DOWN, event => {
180
const keyboardEvent = new StandardKeyboardEvent(event);
181
if (keyboardEvent.equals(KeyCode.Escape)) {
182
keyboardEvent.preventDefault();
183
keyboardEvent.stopPropagation();
184
this._onDidCancel.fire();
185
}
186
}));
187
188
this._updateButtonState();
189
}
190
191
focus(): void {
192
if (!this._labelLocked) {
193
this._labelInput.focus();
194
return;
195
}
196
if (this._commandLocked) {
197
this._runOnCheckbox.focus();
198
return;
199
}
200
this._commandInput.focus();
201
}
202
203
private _submit(): void {
204
const label = this._labelInput.value.trim();
205
const command = this._commandInput.value.trim();
206
if (!command) {
207
return;
208
}
209
210
this._onDidSubmit.fire({
211
label: label.length > 0 ? label : undefined,
212
command,
213
target: this._selectedTarget,
214
runOn: this._runOnCheckbox.checked ? WORKTREE_CREATED_RUN_ON : undefined,
215
});
216
}
217
218
private _updateButtonState(): void {
219
this._submitButton.enabled = this._commandInput.value.trim().length > 0;
220
this._submitButton.label = this._getSubmitLabel();
221
}
222
223
private _getSubmitLabel(): string {
224
if (this._isAddExistingTask) {
225
return localize('confirmAddToAgents', "Add to Agents Window");
226
}
227
if (!this._isExistingTask) {
228
return localize('confirmAddTask', "Add Task");
229
}
230
231
const targetChanged = this._selectedTarget !== this._initialTarget;
232
const labelChanged = this._labelInput.value !== this._initialLabel;
233
const commandChanged = this._commandInput.value !== this._initialCommand;
234
const runOnChanged = this._runOnCheckbox.checked !== this._initialRunOn;
235
const otherChanged = labelChanged || commandChanged || runOnChanged;
236
237
if (targetChanged && otherChanged) {
238
return localize('confirmMoveAndUpdateTask', "Move and Update Task");
239
}
240
if (targetChanged) {
241
return localize('confirmMoveTask', "Move Task");
242
}
243
return localize('confirmUpdateTask', "Update Task");
244
}
245
}
246
247