提交 4f7f8587 编写于 作者: S Sandeep Somavarapu

#85216 More improvements to start up ux

上级 1ba18afb
......@@ -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,24 +349,38 @@ 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> {
const result = await this.dialogService.confirm({
type: 'info',
......@@ -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> {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册