mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
#85216 More improvements to start up ux
This commit is contained in:
@@ -18,6 +18,7 @@ import { Queue } from 'vs/base/common/async';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { localize } from 'vs/nls';
|
||||
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly added: ISyncExtension[];
|
||||
@@ -171,11 +172,23 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
return !!lastSyncData;
|
||||
}
|
||||
|
||||
async hasRemote(): Promise<boolean> {
|
||||
async hasRemoteData(): Promise<boolean> {
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
return remoteUserData.content !== null;
|
||||
}
|
||||
|
||||
async hasLocalData(): Promise<boolean> {
|
||||
try {
|
||||
const localExtensions = await this.getLocalExtensions();
|
||||
if (isNonEmptyArray(localExtensions)) {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
/* ignore error */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
removeExtension(identifier: IExtensionIdentifier): Promise<void> {
|
||||
return this.replaceQueue.queue(async () => {
|
||||
const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null);
|
||||
|
||||
@@ -150,11 +150,23 @@ export class GlobalStateSynchroniser extends Disposable implements ISynchroniser
|
||||
return !!lastSyncData;
|
||||
}
|
||||
|
||||
async hasRemote(): Promise<boolean> {
|
||||
async hasRemoteData(): Promise<boolean> {
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
return remoteUserData.content !== null;
|
||||
}
|
||||
|
||||
async hasLocalData(): Promise<boolean> {
|
||||
try {
|
||||
const localGloablState = await this.getLocalGlobalState();
|
||||
if (localGloablState.argv['locale'] !== 'en') {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
/* ignore error */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async resetLocal(): Promise<void> {
|
||||
try {
|
||||
await this.fileService.del(this.lastSyncGlobalStateResource);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files';
|
||||
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
@@ -20,6 +20,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
|
||||
interface ISyncContent {
|
||||
mac?: string;
|
||||
@@ -216,11 +217,28 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
|
||||
return !!lastSyncData;
|
||||
}
|
||||
|
||||
async hasRemote(): Promise<boolean> {
|
||||
async hasRemoteData(): Promise<boolean> {
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
return remoteUserData.content !== null;
|
||||
}
|
||||
|
||||
async hasLocalData(): Promise<boolean> {
|
||||
try {
|
||||
const localFileContent = await this.getLocalFileContent();
|
||||
if (localFileContent) {
|
||||
const keybindings = parse(localFileContent.value.toString());
|
||||
if (isNonEmptyArray(keybindings)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async resetLocal(): Promise<void> {
|
||||
try {
|
||||
await this.fileService.del(this.lastSyncKeybindingsResource);
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse, ParseError } from 'vs/base/common/json';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -21,6 +21,8 @@ import { updateIgnoredSettings, merge } from 'vs/platform/userDataSync/common/se
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly fileContent: IFileContent | null;
|
||||
@@ -207,11 +209,30 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer
|
||||
return !!lastSyncData;
|
||||
}
|
||||
|
||||
async hasRemote(): Promise<boolean> {
|
||||
async hasRemoteData(): Promise<boolean> {
|
||||
const remoteUserData = await this.getRemoteUserData();
|
||||
return remoteUserData.content !== null;
|
||||
}
|
||||
|
||||
async hasLocalData(): Promise<boolean> {
|
||||
try {
|
||||
const localFileContent = await this.getLocalFileContent();
|
||||
if (localFileContent) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const content = edit(localFileContent.value.toString(), [CONFIGURATION_SYNC_STORE_KEY], undefined, formatUtils);
|
||||
const settings = parse(content);
|
||||
if (!isEmptyObject(settings)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise<void> {
|
||||
if (this.status === SyncStatus.HasConflicts) {
|
||||
this.syncPreviewResultPromise!.cancel();
|
||||
|
||||
@@ -72,7 +72,7 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer
|
||||
}
|
||||
|
||||
private async isTurnedOffEverywhere(): Promise<boolean> {
|
||||
const hasRemote = await this.userDataSyncService.hasRemote();
|
||||
const hasRemote = await this.userDataSyncService.hasRemoteData();
|
||||
const hasPreviouslySynced = await this.userDataSyncService.hasPreviouslySynced();
|
||||
return !hasRemote && hasPreviouslySynced;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store';
|
||||
export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store';
|
||||
|
||||
export const DEFAULT_IGNORED_SETTINGS = [
|
||||
CONFIGURATION_SYNC_STORE_KEY,
|
||||
@@ -190,7 +190,8 @@ export interface ISynchroniser {
|
||||
sync(_continue?: boolean): Promise<boolean>;
|
||||
stop(): void;
|
||||
hasPreviouslySynced(): Promise<boolean>
|
||||
hasRemote(): Promise<boolean>;
|
||||
hasRemoteData(): Promise<boolean>;
|
||||
hasLocalData(): Promise<boolean>;
|
||||
resetLocal(): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -198,6 +199,7 @@ export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUser
|
||||
export interface IUserDataSyncService extends ISynchroniser {
|
||||
_serviceBrand: any;
|
||||
readonly conflictsSource: SyncSource | null;
|
||||
isFirstTimeSyncAndHasUserData(): Promise<boolean>;
|
||||
reset(): Promise<void>;
|
||||
resetLocal(): Promise<void>;
|
||||
removeExtension(identifier: IExtensionIdentifier): Promise<void>;
|
||||
|
||||
@@ -35,7 +35,9 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
case 'reset': return this.service.reset();
|
||||
case 'resetLocal': return this.service.resetLocal();
|
||||
case 'hasPreviouslySynced': return this.service.hasPreviouslySynced();
|
||||
case 'hasRemote': return this.service.hasRemote();
|
||||
case 'hasRemoteData': return this.service.hasRemoteData();
|
||||
case 'hasLocalData': return this.service.hasLocalData();
|
||||
case 'isFirstTimeSyncAndHasUserData': return this.service.isFirstTimeSyncAndHasUserData();
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
@@ -64,7 +66,8 @@ export class SettingsSyncChannel implements IServerChannel {
|
||||
case 'stop': this.service.stop(); return Promise.resolve();
|
||||
case 'resetLocal': return this.service.resetLocal();
|
||||
case 'hasPreviouslySynced': return this.service.hasPreviouslySynced();
|
||||
case 'hasRemote': return this.service.hasRemote();
|
||||
case 'hasRemoteData': return this.service.hasRemoteData();
|
||||
case 'hasLocalData': return this.service.hasLocalData();
|
||||
case 'resolveConflicts': return this.service.resolveConflicts(args[0]);
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
|
||||
@@ -131,7 +131,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
return false;
|
||||
}
|
||||
|
||||
async hasRemote(): Promise<boolean> {
|
||||
async hasRemoteData(): Promise<boolean> {
|
||||
if (!this.userDataSyncStoreService.userDataSyncStore) {
|
||||
throw new Error('Not enabled');
|
||||
}
|
||||
@@ -139,13 +139,41 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
throw new Error('Not Authenticated. Please sign in to start sync.');
|
||||
}
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
if (await synchroniser.hasRemote()) {
|
||||
if (await synchroniser.hasRemoteData()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async hasLocalData(): Promise<boolean> {
|
||||
if (!this.userDataSyncStoreService.userDataSyncStore) {
|
||||
throw new Error('Not enabled');
|
||||
}
|
||||
if (!(await this.userDataAuthTokenService.getToken())) {
|
||||
throw new Error('Not Authenticated. Please sign in to start sync.');
|
||||
}
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
if (await synchroniser.hasLocalData()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async isFirstTimeSyncAndHasUserData(): Promise<boolean> {
|
||||
if (!this.userDataSyncStoreService.userDataSyncStore) {
|
||||
throw new Error('Not enabled');
|
||||
}
|
||||
if (!(await this.userDataAuthTokenService.getToken())) {
|
||||
throw new Error('Not Authenticated. Please sign in to start sync.');
|
||||
}
|
||||
if (await this.hasPreviouslySynced()) {
|
||||
return false;
|
||||
}
|
||||
return await this.hasLocalData();
|
||||
}
|
||||
|
||||
async reset(): Promise<void> {
|
||||
await this.resetRemote();
|
||||
await this.resetLocal();
|
||||
|
||||
@@ -35,7 +35,8 @@ import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
|
||||
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { Session } from 'vs/editor/common/modes';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { isPromiseCanceledError, canceled } from 'vs/base/common/errors';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
|
||||
const enum MSAAuthStatus {
|
||||
Initializing = 'Initializing',
|
||||
@@ -192,7 +193,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
label: localize('resolve', "Resolve Conflicts"),
|
||||
run: () => this.handleConflicts()
|
||||
}
|
||||
]);
|
||||
],
|
||||
{
|
||||
sticky: true
|
||||
}
|
||||
);
|
||||
this.conflictsWarningDisposable.value = toDisposable(() => handle.close());
|
||||
handle.onDidClose(() => this.conflictsWarningDisposable.clear());
|
||||
}
|
||||
@@ -210,7 +215,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
const enabled = this.configurationService.getValue<boolean>(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING);
|
||||
if (enabled) {
|
||||
if (this.authenticationState.get() === MSAAuthStatus.SignedOut) {
|
||||
const handle = this.notificationService.prompt(Severity.Info, this.getSignInAndTurnOnDetailString(),
|
||||
const handle = this.notificationService.prompt(Severity.Info, localize('sign in message', "Please sign in with your {0} account to continue sync", this.userDataSyncStore!.account),
|
||||
[
|
||||
{
|
||||
label: localize('Sign in', "Sign in"),
|
||||
@@ -247,18 +252,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
}
|
||||
|
||||
private getSignInAndTurnOnDetailString(): string {
|
||||
return localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.{1}", this.userDataSyncStore!.account, this.getSyncAreasString());
|
||||
}
|
||||
|
||||
private async turnOn(): Promise<void> {
|
||||
const message = localize('turn on sync', "Turn on Sync");
|
||||
let detail: string, primaryButton: string;
|
||||
if (this.authenticationState.get() === MSAAuthStatus.SignedIn) {
|
||||
detail = localize('turn on sync detail', "This will synchronize your following data across all your devices.{0}", this.getSyncAreasString());
|
||||
detail = `${localize('turn on sync detail', "This will synchronize your following data across all your devices.")}\n${this.getSyncAreasString()}`;
|
||||
primaryButton = localize('turn on', "Turn on");
|
||||
} else {
|
||||
detail = this.getSignInAndTurnOnDetailString();
|
||||
detail = `${localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", this.userDataSyncStore!.account)}\n${this.getSyncAreasString()}`;
|
||||
primaryButton = localize('sign in and turn on sync', "Sign in & Turn on");
|
||||
}
|
||||
const result = await this.dialogService.show(
|
||||
@@ -266,7 +267,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
[
|
||||
primaryButton,
|
||||
localize('cancel', "Cancel"),
|
||||
localize('configure', "Configure What to Sync")
|
||||
localize('configure', "Configure...")
|
||||
],
|
||||
{
|
||||
detail,
|
||||
@@ -281,8 +282,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
await this.handleFirstTimeSync();
|
||||
await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true);
|
||||
this.notificationService.info(localize('Sync Started', "Sync Started."));
|
||||
await this.enableSync();
|
||||
}
|
||||
|
||||
private getSyncAreasString(): string {
|
||||
@@ -349,22 +349,36 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private async handleFirstTimeSync(): Promise<void> {
|
||||
|
||||
const hasRemote = await this.userDataSyncService.hasRemote();
|
||||
const hasPreviouslySynced = await this.userDataSyncService.hasPreviouslySynced();
|
||||
|
||||
if (hasRemote && !hasPreviouslySynced) {
|
||||
const result = await this.dialogService.confirm({
|
||||
type: 'info',
|
||||
message: localize('firs time sync', "First time Sync"),
|
||||
primaryButton: localize('download', "Download"),
|
||||
detail: localize('first time sync detail', "Would you like to download and replace with the data from cloud?"),
|
||||
});
|
||||
|
||||
if (result.confirmed) {
|
||||
await this.userDataSyncService.pull();
|
||||
}
|
||||
const hasRemote = await this.userDataSyncService.hasRemoteData();
|
||||
if (!hasRemote) {
|
||||
return;
|
||||
}
|
||||
const isFirstSyncAndHasUserData = await this.userDataSyncService.isFirstTimeSyncAndHasUserData();
|
||||
if (!isFirstSyncAndHasUserData) {
|
||||
return;
|
||||
}
|
||||
const result = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
localize('firs time sync', "First time Sync"),
|
||||
[
|
||||
localize('merge', "Merge"),
|
||||
localize('cancel', "Cancel"),
|
||||
localize('replace', "Replace (Overwrite Local)"),
|
||||
],
|
||||
{
|
||||
cancelId: 1,
|
||||
detail: localize('first time sync detail', "Synchronizing from this device for the first time.\nWould you like to merge or replace with the data from cloud?"),
|
||||
}
|
||||
);
|
||||
switch (result.choice) {
|
||||
case 0: await this.userDataSyncService.sync(); break;
|
||||
case 1: throw canceled();
|
||||
case 2: await this.userDataSyncService.pull(); break;
|
||||
}
|
||||
}
|
||||
|
||||
private enableSync(): Promise<void> {
|
||||
return this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true);
|
||||
}
|
||||
|
||||
private async turnOff(): Promise<void> {
|
||||
@@ -474,7 +488,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
await this.turnOn();
|
||||
} catch (e) {
|
||||
if (!isPromiseCanceledError(e)) {
|
||||
this.notificationService.error(e);
|
||||
this.notificationService.error(localize('turn on failed', "Error while starting Sync: {0}", toErrorMessage(e)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -69,8 +69,12 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ
|
||||
return this.channel.call('hasPreviouslySynced');
|
||||
}
|
||||
|
||||
hasRemote(): Promise<boolean> {
|
||||
return this.channel.call('hasRemote');
|
||||
hasRemoteData(): Promise<boolean> {
|
||||
return this.channel.call('hasRemoteData');
|
||||
}
|
||||
|
||||
hasLocalData(): Promise<boolean> {
|
||||
return this.channel.call('hasLocalData');
|
||||
}
|
||||
|
||||
resolveConflicts(conflicts: { key: string, value: any | undefined }[]): Promise<void> {
|
||||
|
||||
@@ -66,8 +66,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
return this.channel.call('hasPreviouslySynced');
|
||||
}
|
||||
|
||||
hasRemote(): Promise<boolean> {
|
||||
return this.channel.call('hasRemote');
|
||||
hasRemoteData(): Promise<boolean> {
|
||||
return this.channel.call('hasRemoteData');
|
||||
}
|
||||
|
||||
hasLocalData(): Promise<boolean> {
|
||||
return this.channel.call('hasLocalData');
|
||||
}
|
||||
|
||||
isFirstTimeSyncAndHasUserData(): Promise<boolean> {
|
||||
return this.channel.call('isFirstTimeSyncAndHasUserData');
|
||||
}
|
||||
|
||||
removeExtension(identifier: IExtensionIdentifier): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user