Skip to content

Commit 34db0b6

Browse files
authored
Integrate create profile with import flow (microsoft#188510)
1 parent 698b8ad commit 34db0b6

File tree

6 files changed

+307
-283
lines changed

6 files changed

+307
-283
lines changed

src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts

Lines changed: 7 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import 'vs/css!./media/userDataProfileCreateWidget';
76
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
87
import { isWeb } from 'vs/base/common/platform';
9-
import { Event } from 'vs/base/common/event';
108
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
119
import { localize } from 'vs/nls';
1210
import { Action2, IMenuService, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
1311
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
14-
import { IUserDataProfile, IUserDataProfilesService, ProfileResourceType, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile';
12+
import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
1513
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
1614
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
17-
import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, PROFILES_CATEGORY, PROFILE_FILTER, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ProfilesMenu, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TITLE } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
15+
import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, PROFILES_CATEGORY, PROFILE_FILTER, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ProfilesMenu, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TITLE, IProfileTemplateInfo } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
1816
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
1917
import { INotificationService } from 'vs/platform/notification/common/notification';
2018
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
@@ -25,22 +23,6 @@ import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspac
2523
import { getErrorMessage } from 'vs/base/common/errors';
2624
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
2725
import { IOpenerService } from 'vs/platform/opener/common/opener';
28-
import { IProductService } from 'vs/platform/product/common/productService';
29-
import { IRequestService, asJson } from 'vs/platform/request/common/request';
30-
import { CancellationToken } from 'vs/base/common/cancellation';
31-
import { ILogService } from 'vs/platform/log/common/log';
32-
import Severity from 'vs/base/common/severity';
33-
import { $, append } from 'vs/base/browser/dom';
34-
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
35-
import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
36-
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
37-
import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
38-
import { isString } from 'vs/base/common/types';
39-
40-
interface IProfileTemplateInfo {
41-
readonly name: string;
42-
readonly url: string;
43-
}
4426

4527
type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo;
4628

@@ -59,14 +41,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
5941
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
6042
@IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService,
6143
@IContextKeyService contextKeyService: IContextKeyService,
62-
@IQuickInputService private readonly quickInputService: IQuickInputService,
63-
@INotificationService private readonly notificationService: INotificationService,
6444
@ILifecycleService private readonly lifecycleService: ILifecycleService,
65-
@IProductService private readonly productService: IProductService,
66-
@IRequestService private readonly requestService: IRequestService,
67-
@IInstantiationService private readonly instantiationService: IInstantiationService,
68-
@IContextViewService private readonly contextViewService: IContextViewService,
69-
@ILogService private readonly logService: ILogService,
7045
) {
7146
super();
7247

@@ -228,7 +203,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
228203
});
229204
}
230205
run() {
231-
return that.saveProfile(that.userDataProfileService.currentProfile);
206+
return that.userDataProfileImportExportService.editProfile(that.userDataProfileService.currentProfile);
232207
}
233208
});
234209
}
@@ -375,7 +350,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
375350
}
376351
try {
377352
if ((<IProfileTemplateQuickPickItem>selectedItem).url) {
378-
return await that.saveProfile(undefined, (<IProfileTemplateQuickPickItem>selectedItem).url);
353+
return await that.userDataProfileImportExportService.createProfile(URI.parse((<IProfileTemplateQuickPickItem>selectedItem).url));
379354
}
380355
const profile = selectedItem.label === quickPick.value ? URI.parse(quickPick.value) : await this.getProfileUriFromFileSystem(fileDialogService);
381356
if (profile) {
@@ -433,194 +408,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
433408
}
434409

435410
run(accessor: ServicesAccessor) {
436-
return that.saveProfile(undefined, that.userDataProfileService.currentProfile);
411+
return that.userDataProfileImportExportService.createProfile(that.userDataProfileService.currentProfile);
437412
}
438413
}));
439414
}
440415

441-
private async saveProfile(profile: IUserDataProfile): Promise<void>;
442-
private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | string): Promise<void>;
443-
private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | string): Promise<void> {
444-
445-
type CreateProfileInfoClassification = {
446-
owner: 'sandy081';
447-
comment: 'Report when profile is about to be created';
448-
};
449-
this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.startCreate');
450-
451-
const disposables = new DisposableStore();
452-
const title = profile ? localize('save profile', "Edit {0} Profile...", profile.name) : localize('create new profle', "Create New Profile...");
453-
454-
const settings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: !profile?.useDefaultFlags?.settings };
455-
const keybindings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: !profile?.useDefaultFlags?.keybindings };
456-
const snippets: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: !profile?.useDefaultFlags?.snippets };
457-
const tasks: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: !profile?.useDefaultFlags?.tasks };
458-
const extensions: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: !profile?.useDefaultFlags?.extensions };
459-
const resources = [settings, keybindings, snippets, tasks, extensions];
460-
461-
const quickPick = this.quickInputService.createQuickPick();
462-
quickPick.title = title;
463-
quickPick.placeholder = localize('name placeholder', "Profile name");
464-
quickPick.value = profile?.name ?? '';
465-
quickPick.canSelectMany = true;
466-
quickPick.matchOnDescription = false;
467-
quickPick.matchOnDetail = false;
468-
quickPick.matchOnLabel = false;
469-
quickPick.sortByLabel = false;
470-
quickPick.hideCountBadge = true;
471-
quickPick.ok = false;
472-
quickPick.customButton = true;
473-
quickPick.hideCheckAll = true;
474-
quickPick.ignoreFocusOut = true;
475-
quickPick.customLabel = profile ? localize('save', "Save") : localize('create', "Create");
476-
quickPick.description = localize('customise the profile', "Choose what to configure in your Profile:");
477-
quickPick.items = [...resources];
478-
479-
const update = () => {
480-
quickPick.items = resources;
481-
quickPick.selectedItems = resources.filter(item => item.picked);
482-
};
483-
update();
484-
485-
const validate = () => {
486-
if (!profile && this.userDataProfilesService.profiles.some(p => p.name === quickPick.value)) {
487-
quickPick.validationMessage = localize('profileExists', "Profile with name {0} already exists.", quickPick.value);
488-
quickPick.severity = Severity.Warning;
489-
return;
490-
}
491-
if (resources.every(resource => !resource.picked)) {
492-
quickPick.validationMessage = localize('invalid configurations', "The profile should contain at least one configuration.");
493-
quickPick.severity = Severity.Warning;
494-
return;
495-
}
496-
quickPick.severity = Severity.Ignore;
497-
quickPick.validationMessage = undefined;
498-
};
499-
500-
disposables.add(quickPick.onDidChangeSelection(items => {
501-
let needUpdate = false;
502-
for (const resource of resources) {
503-
resource.picked = items.includes(resource);
504-
const description = resource.picked ? undefined : localize('use default profile', "Using Default Profile");
505-
if (resource.description !== description) {
506-
resource.description = description;
507-
needUpdate = true;
508-
}
509-
}
510-
if (needUpdate) {
511-
update();
512-
}
513-
validate();
514-
}));
515-
516-
disposables.add(quickPick.onDidChangeValue(validate));
517-
518-
let result: { name: string; items: ReadonlyArray<IQuickPickItem> } | undefined;
519-
disposables.add(Event.any(quickPick.onDidCustom, quickPick.onDidAccept)(() => {
520-
if (!quickPick.value) {
521-
quickPick.validationMessage = localize('name required', "Provide a name for the new profile");
522-
quickPick.severity = Severity.Error;
523-
}
524-
if (quickPick.validationMessage) {
525-
return;
526-
}
527-
result = { name: quickPick.value, items: quickPick.selectedItems };
528-
quickPick.hide();
529-
quickPick.severity = Severity.Ignore;
530-
quickPick.validationMessage = undefined;
531-
}));
532-
533-
if (!profile) {
534-
const domNode = $('.profile-type-widget');
535-
append(domNode, $('.profile-type-create-label', undefined, localize('create from', "Copy from:")));
536-
const separator = { text: '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500', isDisabled: true };
537-
const profileOptions: (ISelectOptionItem & { id?: string; source?: IUserDataProfile | string })[] = [];
538-
profileOptions.push({ text: localize('empty profile', "None") });
539-
const templates = await this.getProfileTemplatesFromProduct();
540-
if (templates.length) {
541-
profileOptions.push({ ...separator, decoratorRight: localize('from templates', "Profile Templates") });
542-
for (const template of templates) {
543-
profileOptions.push({ text: template.name, id: template.url, source: template.url });
544-
}
545-
}
546-
profileOptions.push({ ...separator, decoratorRight: localize('from existing profiles', "Existing Profiles") });
547-
for (const profile of this.userDataProfilesService.profiles) {
548-
profileOptions.push({ text: profile.name, id: profile.id, source: profile });
549-
}
550-
551-
const findOptionIndex = () => {
552-
const index = profileOptions.findIndex(option => {
553-
if (isString(source)) {
554-
return option.id === source;
555-
} else if (source) {
556-
return option.id === source.id;
557-
}
558-
return false;
559-
});
560-
return index > -1 ? index : 0;
561-
};
562-
563-
const selectBox = disposables.add(this.instantiationService.createInstance(SelectBox, profileOptions, findOptionIndex(), this.contextViewService, defaultSelectBoxStyles, { useCustomDrawn: true }));
564-
selectBox.render(append(domNode, $('.profile-type-select-container')));
565-
quickPick.widget = domNode;
566-
567-
const updateQuickpickInfo = () => {
568-
const option = profileOptions[findOptionIndex()];
569-
for (const resource of resources) {
570-
resource.picked = option.source && !isString(option.source) ? !option.source?.useDefaultFlags?.[resource.id] : true;
571-
}
572-
update();
573-
};
574-
575-
updateQuickpickInfo();
576-
disposables.add(selectBox.onDidSelect(({ index }) => {
577-
source = profileOptions[index].source;
578-
updateQuickpickInfo();
579-
}));
580-
}
581-
582-
quickPick.show();
583-
584-
await new Promise<void>((c, e) => {
585-
disposables.add(quickPick.onDidHide(() => {
586-
disposables.dispose();
587-
c();
588-
}));
589-
});
590-
591-
if (!result) {
592-
this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.cancelCreate');
593-
return;
594-
}
595-
596-
this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.successCreate');
597-
598-
try {
599-
const useDefaultFlags: UseDefaultProfileFlags | undefined = result.items.length === resources.length
600-
? undefined
601-
: {
602-
settings: !result.items.includes(settings),
603-
keybindings: !result.items.includes(keybindings),
604-
snippets: !result.items.includes(snippets),
605-
tasks: !result.items.includes(tasks),
606-
extensions: !result.items.includes(extensions)
607-
};
608-
if (profile) {
609-
await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags: profile.useDefaultFlags && !useDefaultFlags ? {} : useDefaultFlags });
610-
} else {
611-
if (isString(source)) {
612-
await this.userDataProfileImportExportService.importProfile(URI.parse(source), { mode: 'apply', name: result.name, useDefaultFlags });
613-
} else if (source) {
614-
await this.userDataProfileImportExportService.createFromProfile(source, result.name, { useDefaultFlags });
615-
} else {
616-
await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags });
617-
}
618-
}
619-
} catch (error) {
620-
this.notificationService.error(error);
621-
}
622-
}
623-
624416
private registerCreateProfileAction(): void {
625417
const that = this;
626418
this._register(registerAction2(class CreateProfileAction extends Action2 {
@@ -646,7 +438,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
646438
}
647439

648440
async run(accessor: ServicesAccessor) {
649-
return that.saveProfile();
441+
return that.userDataProfileImportExportService.createProfile();
650442
}
651443
}));
652444
}
@@ -726,7 +518,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
726518

727519
private async getProfileTemplatesQuickPickItems(): Promise<IProfileTemplateQuickPickItem[]> {
728520
const quickPickItems: IProfileTemplateQuickPickItem[] = [];
729-
const profileTemplates = await this.getProfileTemplatesFromProduct();
521+
const profileTemplates = await this.userDataProfileManagementService.getBuiltinProfileTemplates();
730522
for (const template of profileTemplates) {
731523
quickPickItems.push({
732524
label: template.name,
@@ -736,22 +528,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
736528
return quickPickItems;
737529
}
738530

739-
private async getProfileTemplatesFromProduct(): Promise<IProfileTemplateInfo[]> {
740-
if (this.productService.profileTemplatesUrl) {
741-
try {
742-
const context = await this.requestService.request({ type: 'GET', url: this.productService.profileTemplatesUrl }, CancellationToken.None);
743-
if (context.res.statusCode === 200) {
744-
return (await asJson<IProfileTemplateInfo[]>(context)) || [];
745-
} else {
746-
this.logService.error('Could not get profile templates.', context.res.statusCode);
747-
}
748-
} catch (error) {
749-
this.logService.error(error);
750-
}
751-
}
752-
return [];
753-
}
754-
755531
private async reportWorkspaceProfileInfo(): Promise<void> {
756532
await this.lifecycleService.when(LifecyclePhase.Eventually);
757533
const workspaceId = await this.workspaceTagsService.getTelemetryWorkspaceId(this.workspaceContextService.getWorkspace(), this.workspaceContextService.getWorkbenchState());

0 commit comments

Comments
 (0)