feat: snjs app groups (#468)

* feat: snjs app groups

* fix: update snjs version to point to wip commit

* wip: account switcher

* feat: rename lock manager to auto lock service

* fix: more relevant sign out copy

* chore(deps): update snjs

* fix: use setTimeout instead of setImmediate

* feat: make account switcher expiremental feature

* chore(deps): upgrade snjs
This commit is contained in:
Mo Bitar
2020-09-15 10:55:32 -05:00
committed by GitHub
parent ae6ef50f88
commit 2b6abeebfc
46 changed files with 590 additions and 375 deletions

View File

@@ -1,3 +1,4 @@
import { AccountSwitcherScope } from './../types';
import { ComponentGroup } from './component_group';
import { EditorGroup } from '@/ui_models/editor_group';
import { InputModalScope } from '@/directives/views/inputModal';
@@ -16,7 +17,7 @@ import { AlertService } from '@/services/alertService';
import { WebDeviceInterface } from '@/web_device_interface';
import {
DesktopManager,
LockManager,
AutolockService,
ArchiveManager,
NativeExtManager,
StatusManager,
@@ -27,11 +28,12 @@ import {
import { AppState } from '@/ui_models/app_state';
import { SNWebCrypto } from 'sncrypto/dist/sncrypto-web';
import { Bridge } from '@/services/bridge';
import { DeinitSource } from 'snjs/dist/@types/types';
type WebServices = {
appState: AppState
desktopService: DesktopManager
lockService: LockManager
autolockService: AutolockService
archiveService: ArchiveManager
nativeExtService: NativeExtManager
statusService: StatusManager
@@ -44,7 +46,6 @@ export class WebApplication extends SNApplication {
private $compile?: ng.ICompileService
private scope?: ng.IScope
private onDeinit?: (app: WebApplication) => void
private webServices!: WebServices
private currentAuthenticationElement?: JQLite
public editorGroup: EditorGroup
@@ -52,38 +53,33 @@ export class WebApplication extends SNApplication {
/* @ngInject */
constructor(
deviceInterface: WebDeviceInterface,
identifier: string,
$compile: ng.ICompileService,
$timeout: ng.ITimeoutService,
scope: ng.IScope,
onDeinit: (app: WebApplication) => void,
defaultSyncServerHost: string,
bridge: Bridge,
) {
const deviceInterface = new WebDeviceInterface(
$timeout,
bridge
);
super(
bridge.environment,
platformFromString(getPlatformString()),
deviceInterface,
new SNWebCrypto(),
new AlertService(),
undefined,
identifier,
undefined,
undefined,
defaultSyncServerHost
);
this.$compile = $compile;
this.scope = scope;
this.onDeinit = onDeinit;
deviceInterface.setApplication(this);
this.editorGroup = new EditorGroup(this);
this.componentGroup = new ComponentGroup(this);
}
/** @override */
deinit() {
deinit(source: DeinitSource) {
for (const key of Object.keys(this.webServices)) {
const service = (this.webServices as any)[key];
if (service.deinit) {
@@ -92,8 +88,6 @@ export class WebApplication extends SNApplication {
service.application = undefined;
}
this.webServices = {} as WebServices;
this.onDeinit!(this);
this.onDeinit = undefined;
this.$compile = undefined;
this.editorGroup.deinit();
this.componentGroup.deinit();
@@ -102,9 +96,9 @@ export class WebApplication extends SNApplication {
this.scope = undefined;
/** Allow our Angular directives to be destroyed and any pending digest cycles
* to complete before destroying the global application instance and all its services */
setImmediate(() => {
super.deinit();
})
setTimeout(() => {
super.deinit(source);
}, 0)
}
setWebServices(services: WebServices) {
@@ -119,8 +113,8 @@ export class WebApplication extends SNApplication {
return this.webServices.desktopService;
}
public getLockService() {
return this.webServices.lockService;
public getAutolockService() {
return this.webServices.autolockService;
}
public getArchiveService() {
@@ -257,4 +251,15 @@ export class WebApplication extends SNApplication {
)(scope);
angular.element(document.body).append(el);
}
public openAccountSwitcher() {
const scope = this.scope!.$new(true) as Partial<AccountSwitcherScope>;
scope.application = this;
const el = this.$compile!(
"<account-switcher application='application' "
+ "class='sk-modal'></account-switcher>"
)(scope as any);
angular.element(document.body).append(el);
}
}

View File

@@ -1,10 +1,11 @@
import { WebDeviceInterface } from '@/web_device_interface';
import { WebApplication } from './application';
import { removeFromArray } from 'snjs';
import { ApplicationDescriptor, SNApplicationGroup, DeviceInterface } from 'snjs';
import {
ArchiveManager,
DesktopManager,
KeyboardManager,
LockManager,
AutolockService,
NativeExtManager,
PreferencesManager,
StatusManager,
@@ -13,16 +14,11 @@ import {
import { AppState } from '@/ui_models/app_state';
import { Bridge } from '@/services/bridge';
type AppManagerChangeCallback = () => void
export class ApplicationGroup {
export class ApplicationGroup extends SNApplicationGroup {
$compile: ng.ICompileService
$rootScope: ng.IRootScopeService
$timeout: ng.ITimeoutService
applications: WebApplication[] = []
changeObservers: AppManagerChangeCallback[] = []
activeApplication?: WebApplication
/* @ngInject */
constructor(
@@ -32,46 +28,35 @@ export class ApplicationGroup {
private defaultSyncServerHost: string,
private bridge: Bridge,
) {
super(new WebDeviceInterface(
$timeout,
bridge
));
this.$compile = $compile;
this.$timeout = $timeout;
this.$rootScope = $rootScope;
this.onApplicationDeinit = this.onApplicationDeinit.bind(this);
this.createDefaultApplication();
}
async initialize(callback?: any) {
await super.initialize({
applicationCreator: this.createApplication
});
/** FIXME(baptiste): rely on a less fragile method to detect Electron */
if ((window as any).isElectron) {
Object.defineProperty(window, 'desktopManager', {
get: () => this.activeApplication?.getDesktopService()
get: () => (this.primaryApplication as WebApplication).getDesktopService()
});
}
}
private createDefaultApplication() {
this.activeApplication = this.createNewApplication();
this.applications.push(this.activeApplication!);
this.notifyObserversOfAppChange();
}
/** @callback */
onApplicationDeinit(application: WebApplication) {
removeFromArray(this.applications, application);
if (this.activeApplication === application) {
this.activeApplication = undefined;
}
if (this.applications.length === 0) {
this.createDefaultApplication();
} else {
this.notifyObserversOfAppChange();
}
}
private createNewApplication() {
private createApplication = (descriptor: ApplicationDescriptor, deviceInterface: DeviceInterface) => {
const scope = this.$rootScope.$new(true);
const application = new WebApplication(
deviceInterface as WebDeviceInterface,
descriptor.identifier,
this.$compile,
this.$timeout,
scope,
this.onApplicationDeinit,
this.defaultSyncServerHost,
this.bridge,
);
@@ -90,7 +75,7 @@ export class ApplicationGroup {
this.bridge,
);
const keyboardService = new KeyboardManager();
const lockService = new LockManager(
const autolockService = new AutolockService(
application
);
const nativeExtService = new NativeExtManager(
@@ -108,7 +93,7 @@ export class ApplicationGroup {
archiveService,
desktopService,
keyboardService,
lockService,
autolockService,
nativeExtService,
prefsService,
statusService,
@@ -116,34 +101,4 @@ export class ApplicationGroup {
});
return application;
}
get application() {
return this.activeApplication;
}
public getApplications() {
return this.applications.slice();
}
/**
* Notifies observer when the active application has changed.
* Any application which is no longer active is destroyed, and
* must be removed from the interface.
*/
public addApplicationChangeObserver(callback: AppManagerChangeCallback) {
this.changeObservers.push(callback);
if (this.application) {
callback();
}
return () => {
removeFromArray(this.changeObservers, callback);
}
}
private notifyObserversOfAppChange() {
for (const observer of this.changeObservers) {
observer();
}
}
}

View File

@@ -1,6 +1,6 @@
import { SNComponent, ComponentArea, removeFromArray, addIfUnique } from 'snjs';
import { WebApplication } from './application';
import { UuidString } from '@node_modules/snjs/dist/@types/types';
import { UuidString } from 'snjs/dist/@types/types';
/** Areas that only allow a single component to be active */
const SingleComponentAreas = [
@@ -48,8 +48,8 @@ export class ComponentGroup {
}
removeFromArray(this.activeComponents, component.uuid);
/** If this function is called as part of global application deinit (locking),
* componentManager can be destroyed. In this case, it's harmless to not take any
* action since the componentManager will be destroyed, and the component will
* componentManager can be destroyed. In this case, it's harmless to not take any
* action since the componentManager will be destroyed, and the component will
* essentially be deregistered. */
if(this.componentManager) {
await this.componentManager.deactivateComponent(component.uuid);