Skip to content

Commit

Permalink
Switch workspace restricted settings to use indicators (#163601)
Browse files Browse the repository at this point in the history
* Change policy and workspace trust to indicators
* Add link to workspace trust indicator
* Remove restricted settings configured check
* Fix deprecated method usage
* fix - trigger event when restricted settings are updated
* feedback - use userDataProfilesService for profilesEnabled variable

Co-authored-by: Sandeep Somavarapu <sasomava@microsoft.com>
  • Loading branch information
rzhao271 and sandy081 authored Oct 14, 2022
1 parent 46e6b68 commit 740d837
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 145 deletions.
4 changes: 2 additions & 2 deletions src/vs/platform/configuration/common/configurationModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class ConfigurationModel implements IConfigurationModel {
}

private _rawConfiguration: ConfigurationModel | undefined;
private get rawConfiguration(): ConfigurationModel {
get rawConfiguration(): ConfigurationModel {
if (!this._rawConfiguration) {
if (this.raw?.length) {
const rawConfigurationModels = this.raw.map(raw => {
Expand Down Expand Up @@ -978,7 +978,7 @@ export class ConfigurationChangeEvent implements IConfigurationChangeEvent {
}

function compare(from: ConfigurationModel | undefined, to: ConfigurationModel | undefined): IConfigurationCompareResult {
const { added, removed, updated } = compareConfigurationContents(to, from);
const { added, removed, updated } = compareConfigurationContents(to?.rawConfiguration, from?.rawConfiguration);
const overrides: [string, string[]][] = [];

const fromOverrideIdentifiers = from?.getAllOverrideIdentifiers() || [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import { Emitter } from 'vs/base/common/event';
import { IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
import { getDefaultIgnoredSettings, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels';
import { MODIFIED_INDICATOR_USE_INLINE_ONLY } from 'vs/workbench/contrib/preferences/common/preferences';
import { MODIFIED_INDICATOR_USE_INLINE_ONLY, POLICY_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';

const $ = DOM.$;
Expand All @@ -34,20 +36,26 @@ export interface ISettingOverrideClickEvent {
*/
export class SettingsTreeIndicatorsLabel implements IDisposable {
private indicatorsContainerElement: HTMLElement;
private workspaceTrustElement: HTMLElement;
private scopeOverridesElement: HTMLElement;
private scopeOverridesLabel: SimpleIconLabel;
private scopeOverridesHover: MutableDisposable<ICustomHover>;
private syncIgnoredElement: HTMLElement;
private syncIgnoredHover: ICustomHover | undefined;
private defaultOverrideIndicatorElement: HTMLElement;
private hoverDelegate: IHoverDelegate;
private profilesEnabled: boolean;

// Holds hovers that contain a fixed message.
private simpleHoverStore: DisposableStore;

constructor(
container: HTMLElement,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IConfigurationService configurationService: IConfigurationService,
@IHoverService hoverService: IHoverService,
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
@ILanguageService private readonly languageService: ILanguageService) {
@ILanguageService private readonly languageService: ILanguageService,
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
@ICommandService private readonly commandService: ICommandService) {
this.indicatorsContainerElement = DOM.append(container, $('.misc-label'));
this.indicatorsContainerElement.style.display = 'inline';

Expand All @@ -60,12 +68,41 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
placement: 'element'
};

this.simpleHoverStore = new DisposableStore();
const scopeOverridesIndicator = this.createScopeOverridesIndicator();
this.workspaceTrustElement = this.createWorkspaceTrustElement();
this.scopeOverridesElement = scopeOverridesIndicator.element;
this.scopeOverridesLabel = scopeOverridesIndicator.label;
this.syncIgnoredElement = this.createSyncIgnoredElement();
this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator();
this.scopeOverridesHover = new MutableDisposable();
this.profilesEnabled = this.userDataProfilesService.isEnabled();
}

private createWorkspaceTrustElement(): HTMLElement {
const workspaceTrustElement = $('span.setting-item-workspace-trust');
const workspaceTrustLabel = new SimpleIconLabel(workspaceTrustElement);
workspaceTrustLabel.text = localize('workspaceUntrustedLabel', "Requires Workspace Trust");
const contentFallback = localize('trustLabel', "This setting can only be applied in a trusted workspace.");

const contentMarkdownString = contentFallback + ` [${localize('manageWorkspaceTrust', "Manage Workspace Trust")}](manage-workspace-trust).`;
const content: ITooltipMarkdownString = {
markdown: {
value: contentMarkdownString,
isTrusted: false,
supportHtml: false
},
markdownNotSupportedFallback: contentFallback
};
const options: IUpdatableHoverOptions = {
linkHandler: (url: string) => {
this.commandService.executeCommand('workbench.trust.manage');
this.scopeOverridesHover.value?.hide();
}
};

this.simpleHoverStore.add(setupCustomHover(this.hoverDelegate, workspaceTrustElement, content, options));
return workspaceTrustElement;
}

private createScopeOverridesIndicator(): { element: HTMLElement; label: SimpleIconLabel } {
Expand All @@ -79,7 +116,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement);
syncIgnoredLabel.text = localize('extensionSyncIgnoredLabel', 'Not synced');
const syncIgnoredHoverContent = localize('syncIgnoredTitle', "This setting is ignored during sync");
this.syncIgnoredHover = setupCustomHover(this.hoverDelegate, syncIgnoredElement, syncIgnoredHoverContent);
this.simpleHoverStore.add(setupCustomHover(this.hoverDelegate, syncIgnoredElement, syncIgnoredHoverContent));
return syncIgnoredElement;
}

Expand All @@ -91,7 +128,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
}

private render() {
const elementsToShow = [this.scopeOverridesElement, this.syncIgnoredElement, this.defaultOverrideIndicatorElement].filter(element => {
const elementsToShow = [this.scopeOverridesElement, this.workspaceTrustElement, this.syncIgnoredElement, this.defaultOverrideIndicatorElement].filter(element => {
return element.style.display !== 'none';
});

Expand All @@ -109,6 +146,11 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
}
}

updateWorkspaceTrust(element: SettingsTreeSettingElement) {
this.workspaceTrustElement.style.display = element.isUntrusted ? 'inline' : 'none';
this.render();
}

updateSyncIgnored(element: SettingsTreeSettingElement, ignoredSettings: string[]) {
this.syncIgnoredElement.style.display = this.userDataSyncEnablementService.isEnabled()
&& ignoredSettings.includes(element.setting.key) ? 'inline' : 'none';
Expand All @@ -128,14 +170,38 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {

dispose() {
this.scopeOverridesHover.dispose();
this.syncIgnoredHover?.dispose();
this.simpleHoverStore.dispose();
}

updateScopeOverrides(element: SettingsTreeSettingElement, elementDisposables: DisposableStore, onDidClickOverrideElement: Emitter<ISettingOverrideClickEvent>) {
updateScopeOverrides(element: SettingsTreeSettingElement, elementDisposables: DisposableStore, onDidClickOverrideElement: Emitter<ISettingOverrideClickEvent>, onApplyFilter: Emitter<string>) {
this.scopeOverridesElement.innerText = '';
this.scopeOverridesElement.style.display = 'none';
const profileFeatureEnabled = this.configurationService.getValue<boolean>('workbench.experimental.settingsProfiles.enabled');
if (profileFeatureEnabled && !element.setting.isLanguageTagSetting && element.matchesScope(ConfigurationTarget.APPLICATION, false)) {
if (element.hasPolicyValue) {
// If the setting falls under a policy, then no matter what the user sets, the policy value takes effect.
this.scopeOverridesElement.style.display = 'inline';
this.scopeOverridesElement.classList.add('with-custom-hover');

const policyText = localize('policyLabelText', "Managed by policy");
this.scopeOverridesLabel.text = policyText;

const contentFallback = localize('policyDescription', "This setting is managed by your organization.");
const contentMarkdownString = contentFallback + ` [${localize('policyFilterLink', "View policy settings")}](policy-settings).`;
const content: ITooltipMarkdownString = {
markdown: {
value: contentMarkdownString,
isTrusted: false,
supportHtml: false
},
markdownNotSupportedFallback: contentFallback
};
const options: IUpdatableHoverOptions = {
linkHandler: (url: string) => {
onApplyFilter.fire(`@${POLICY_SETTING_TAG}`);
this.scopeOverridesHover.value?.hide();
}
};
this.scopeOverridesHover.value = setupCustomHover(this.hoverDelegate, this.scopeOverridesElement, content, options);
} else if (this.profilesEnabled && element.matchesScope(ConfigurationTarget.APPLICATION, false)) {
// If the setting is an application-scoped setting, there are no overrides so we can use this
// indicator to display that information instead.
this.scopeOverridesElement.style.display = 'inline';
Expand Down Expand Up @@ -290,22 +356,29 @@ function getAccessibleScopeDisplayMidSentenceText(completeScope: string, languag
return localizedScope;
}

export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, configurationService: IConfigurationService, languageService: ILanguageService): string {
export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, configurationService: IConfigurationService, userDataProfilesService: IUserDataProfilesService, languageService: ILanguageService): string {
const ariaLabelSections: string[] = [];

const profileFeatureEnabled = configurationService.getValue<boolean>('workbench.experimental.settingsProfiles.enabled');
if (profileFeatureEnabled && element.matchesScope(ConfigurationTarget.APPLICATION, false)) {
ariaLabelSections.push(localize('applicationSettingDescriptionAccessible', "Setting value retained when switching profiles"));
// Add workspace trust text
if (element.isUntrusted) {
ariaLabelSections.push(localize('workspaceUntrustedAriaLabel', "Workspace untrusted; setting value will not take effect"));
}

// Add other overrides text
const otherOverridesStart = element.isConfigured ?
localize('alsoConfiguredIn', "Also modified in") :
localize('configuredIn', "Modified in");
const otherOverridesList = element.overriddenScopeList
.map(scope => getAccessibleScopeDisplayMidSentenceText(scope, languageService)).join(', ');
if (element.overriddenScopeList.length) {
ariaLabelSections.push(`${otherOverridesStart} ${otherOverridesList}`);
const profilesEnabled = userDataProfilesService.isEnabled();
if (element.hasPolicyValue) {
ariaLabelSections.push(localize('policyDescriptionAccessible', "Managed by organization policy"));
} else if (profilesEnabled && element.matchesScope(ConfigurationTarget.APPLICATION, false)) {
ariaLabelSections.push(localize('applicationSettingDescriptionAccessible', "Setting value retained when switching profiles"));
} else {
// Add other overrides text
const otherOverridesStart = element.isConfigured ?
localize('alsoConfiguredIn', "Also modified in") :
localize('configuredIn', "Modified in");
const otherOverridesList = element.overriddenScopeList
.map(scope => getAccessibleScopeDisplayMidSentenceText(scope, languageService)).join(', ');
if (element.overriddenScopeList.length) {
ariaLabelSections.push(`${otherOverridesStart} ${otherOverridesList}`);
}
}

// Add sync ignored text
Expand Down
Loading

0 comments on commit 740d837

Please sign in to comment.