ApplicationManager and better memory management
This commit is contained in:
@@ -1,16 +1,10 @@
|
||||
import { EncryptionIntents, ProtectedActions } from 'snjs';
|
||||
|
||||
export class ArchiveManager {
|
||||
/* @ngInject */
|
||||
constructor(lockManager, application, godService) {
|
||||
this.lockManager = lockManager;
|
||||
constructor(application) {
|
||||
this.application = application;
|
||||
this.godService = godService;
|
||||
}
|
||||
|
||||
/*
|
||||
Public
|
||||
*/
|
||||
/** @public */
|
||||
async downloadBackup(encrypted) {
|
||||
return this.downloadBackupOfItems(this.application.modelManager.allItems, encrypted);
|
||||
@@ -37,7 +31,7 @@ export class ArchiveManager {
|
||||
};
|
||||
|
||||
if (await this.application.privilegesService.actionRequiresPrivilege(ProtectedActions.ManageBackups)) {
|
||||
this.godService.presentPrivilegesModal(ProtectedActions.ManageBackups, () => {
|
||||
this.application.presentPrivilegesModal(ProtectedActions.ManageBackups, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -9,23 +9,25 @@ const COMPONENT_CONTENT_KEY_PACKAGE_INFO = 'package_info';
|
||||
const COMPONENT_CONTENT_KEY_LOCAL_URL = 'local_url';
|
||||
|
||||
export class DesktopManager extends ApplicationService {
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$rootScope,
|
||||
$timeout,
|
||||
application,
|
||||
appState,
|
||||
application
|
||||
) {
|
||||
super(application);
|
||||
this.$rootScope = $rootScope;
|
||||
this.$timeout = $timeout;
|
||||
this.appState = appState;
|
||||
this.application = application;
|
||||
this.componentActivationObservers = [];
|
||||
this.updateObservers = [];
|
||||
this.isDesktop = isDesktopApplication();
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.componentActivationObservers.length = 0;
|
||||
this.updateObservers.length = 0;
|
||||
super.deinit();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
onAppEvent(eventName) {
|
||||
super.onAppEvent(eventName);
|
||||
@@ -177,7 +179,7 @@ export class DesktopManager extends ApplicationService {
|
||||
/* Used to resolve 'sn://' */
|
||||
desktop_setExtServerHost(host) {
|
||||
this.extServerHost = host;
|
||||
this.appState.desktopExtensionsReady();
|
||||
this.application.getAppState().desktopExtensionsReady();
|
||||
}
|
||||
|
||||
desktop_setComponentInstallationSyncHandler(handler) {
|
||||
@@ -207,11 +209,11 @@ export class DesktopManager extends ApplicationService {
|
||||
}
|
||||
|
||||
desktop_didBeginBackup() {
|
||||
this.appState.beganBackupDownload();
|
||||
this.application.getAppState().beganBackupDownload();
|
||||
}
|
||||
|
||||
desktop_didFinishBackup(success) {
|
||||
this.appState.endedBackupDownload({
|
||||
this.application.getAppState().endedBackupDownload({
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import angular from 'angular';
|
||||
|
||||
export class GodService {
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$rootScope,
|
||||
$compile,
|
||||
application
|
||||
) {
|
||||
this.$rootScope = $rootScope;
|
||||
this.$compile = $compile;
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
async checkForSecurityUpdate() {
|
||||
return this.application.protocolUpgradeAvailable();
|
||||
}
|
||||
|
||||
presentPasswordWizard(type) {
|
||||
const scope = this.$rootScope.$new(true);
|
||||
scope.type = type;
|
||||
const el = this.$compile("<password-wizard type='type'></password-wizard>")(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
promptForChallenge(challenge, orchestrator) {
|
||||
const scope = this.$rootScope.$new(true);
|
||||
scope.challenge = challenge;
|
||||
scope.orchestrator = orchestrator;
|
||||
const el = this.$compile(
|
||||
"<challenge-modal " +
|
||||
"class='sk-modal' challenge='challenge' orchestrator='orchestrator'>" +
|
||||
"</challenge-modal>"
|
||||
)(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
async performProtocolUpgrade() {
|
||||
const errors = await this.application.upgradeProtocolVersion();
|
||||
if (errors.length === 0) {
|
||||
this.application.alertService.alert({
|
||||
text: "Success! Your encryption version has been upgraded." +
|
||||
" You'll be asked to enter your credentials again on other devices you're signed into."
|
||||
});
|
||||
} else {
|
||||
this.application.alertService.alert({
|
||||
text: "Unable to upgrade encryption version. Please try again."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async presentPrivilegesModal(action, onSuccess, onCancel) {
|
||||
if (this.authenticationInProgress()) {
|
||||
onCancel && onCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
const customSuccess = async () => {
|
||||
onSuccess && await onSuccess();
|
||||
this.currentAuthenticationElement = null;
|
||||
};
|
||||
const customCancel = async () => {
|
||||
onCancel && await onCancel();
|
||||
this.currentAuthenticationElement = null;
|
||||
};
|
||||
|
||||
const scope = this.$rootScope.$new(true);
|
||||
scope.action = action;
|
||||
scope.onSuccess = customSuccess;
|
||||
scope.onCancel = customCancel;
|
||||
const el = this.$compile(`
|
||||
<privileges-auth-modal action='action' on-success='onSuccess'
|
||||
on-cancel='onCancel' class='sk-modal'></privileges-auth-modal>
|
||||
`)(scope);
|
||||
angular.element(document.body).append(el);
|
||||
|
||||
this.currentAuthenticationElement = el;
|
||||
}
|
||||
|
||||
presentPrivilegesManagementModal() {
|
||||
var scope = this.$rootScope.$new(true);
|
||||
var el = this.$compile("<privileges-management-modal class='sk-modal'></privileges-management-modal>")(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
authenticationInProgress() {
|
||||
return this.currentAuthenticationElement != null;
|
||||
}
|
||||
|
||||
presentPasswordModal(callback) {
|
||||
const scope = this.$rootScope.$new(true);
|
||||
scope.type = "password";
|
||||
scope.title = "Decryption Assistance";
|
||||
scope.message = `Unable to decrypt this item with your current keys.
|
||||
Please enter your account password at the time of this revision.`;
|
||||
scope.callback = callback;
|
||||
const el = this.$compile(
|
||||
`<input-modal type='type' message='message'
|
||||
title='title' callback='callback'></input-modal>`
|
||||
)(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
presentRevisionPreviewModal(uuid, content) {
|
||||
const scope = this.$rootScope.$new(true);
|
||||
scope.uuid = uuid;
|
||||
scope.content = content;
|
||||
const el = this.$compile(
|
||||
`<revision-preview-modal uuid='uuid' content='content'
|
||||
class='sk-modal'></revision-preview-modal>`
|
||||
)(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
export { AlertService } from './alertService';
|
||||
export { ArchiveManager } from './archiveManager';
|
||||
export { DesktopManager } from './desktopManager';
|
||||
export { GodService } from './godService';
|
||||
export { KeyboardManager } from './keyboardManager';
|
||||
export { LockManager } from './lockManager';
|
||||
export { NativeExtManager } from './nativeExtManager';
|
||||
export { PreferencesManager } from './preferencesManager';
|
||||
export { StatusManager } from './statusManager';
|
||||
export { ThemeManager } from './themeManager';
|
||||
export { AppState } from './state';
|
||||
|
||||
@@ -22,8 +22,19 @@ const KeyboardKeyEvents = {
|
||||
export class KeyboardManager {
|
||||
constructor() {
|
||||
this.observers = [];
|
||||
window.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||
window.addEventListener('keyup', this.handleKeyUp.bind(this));
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
this.handleKeyUp = this.handleKeyUp.bind(this);
|
||||
window.addEventListener('keydown', this.handleKeyDown);
|
||||
window.addEventListener('keyup', this.handleKeyUp);
|
||||
}
|
||||
|
||||
/** @access public */
|
||||
deinit() {
|
||||
this.observers.length = 0;
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
window.removeEventListener('keyup', this.handleKeyUp);
|
||||
this.handleKeyDown = null;
|
||||
this.handleKeyUp = null;
|
||||
}
|
||||
|
||||
modifiersForEvent(event) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import { AppStateEvents } from '../state';
|
||||
import { AppStateEvents } from '@/services/state';
|
||||
|
||||
const MILLISECONDS_PER_SECOND = 1000;
|
||||
const FOCUS_POLL_INTERVAL = 1 * MILLISECONDS_PER_SECOND;
|
||||
@@ -12,16 +12,15 @@ const LOCK_INTERVAL_ONE_HOUR= 3600 * MILLISECONDS_PER_SECOND;
|
||||
const STORAGE_KEY_AUTOLOCK_INTERVAL = "AutoLockIntervalKey";
|
||||
|
||||
export class LockManager {
|
||||
/* @ngInject */
|
||||
constructor($rootScope, application, appState) {
|
||||
this.$rootScope = $rootScope;
|
||||
constructor(application) {
|
||||
this.application = application;
|
||||
this.appState = appState;
|
||||
this.observeVisibility();
|
||||
setImmediate(() => {
|
||||
this.observeVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
observeVisibility() {
|
||||
this.appState.addObserver((eventName, data) => {
|
||||
this.unsubState = this.application.getAppState().addObserver((eventName) => {
|
||||
if(eventName === AppStateEvents.WindowDidBlur) {
|
||||
this.documentVisibilityChanged(false);
|
||||
} else if(eventName === AppStateEvents.WindowDidFocus) {
|
||||
@@ -33,6 +32,13 @@ export class LockManager {
|
||||
}
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.unsubState();
|
||||
if (this.pollFocusInterval) {
|
||||
clearInterval(this.pollFocusInterval);
|
||||
}
|
||||
}
|
||||
|
||||
async setAutoLockInterval(interval) {
|
||||
return this.application.setValue(STORAGE_KEY_AUTOLOCK_INTERVAL, interval);
|
||||
}
|
||||
@@ -53,7 +59,7 @@ export class LockManager {
|
||||
* not triggered on a typical window blur event but rather on tab changes.
|
||||
*/
|
||||
beginWebFocusPolling() {
|
||||
this.pollFocusTimeout = setInterval(() => {
|
||||
this.pollFocusInterval = setInterval(() => {
|
||||
const hasFocus = document.hasFocus();
|
||||
if(hasFocus && this.lastFocusState === 'hidden') {
|
||||
this.documentVisibilityChanged(true);
|
||||
|
||||
@@ -13,7 +13,6 @@ export class NativeExtManager extends ApplicationService {
|
||||
/* @ngInject */
|
||||
constructor(application) {
|
||||
super(application);
|
||||
this.application = application;
|
||||
this.extManagerId = 'org.standardnotes.extensions-manager';
|
||||
this.batchManagerId = 'org.standardnotes.batch-manager';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
ApplicationEvents,
|
||||
SNPredicate,
|
||||
ContentTypes,
|
||||
CreateMaxPayloadFromAnyObject,
|
||||
@@ -24,15 +23,7 @@ export const PrefKeys = {
|
||||
};
|
||||
|
||||
export class PreferencesManager extends ApplicationService {
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
appState,
|
||||
application
|
||||
) {
|
||||
super(application);
|
||||
this.appState = appState;
|
||||
}
|
||||
|
||||
|
||||
/** @override */
|
||||
onAppLaunch() {
|
||||
super.onAppLaunch();
|
||||
@@ -65,7 +56,7 @@ export class PreferencesManager extends ApplicationService {
|
||||
}
|
||||
|
||||
preferencesDidChange() {
|
||||
this.appState.setUserPreferences(this.userPreferences);
|
||||
this.application.getAppState().setUserPreferences(this.userPreferences);
|
||||
}
|
||||
|
||||
syncUserPreferences() {
|
||||
|
||||
204
app/assets/javascripts/services/state.js
Normal file
204
app/assets/javascripts/services/state.js
Normal file
@@ -0,0 +1,204 @@
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import pull from 'lodash/pull';
|
||||
import { ProtectedActions, ApplicationEvents } from 'snjs';
|
||||
|
||||
export const AppStateEvents = {
|
||||
TagChanged: 1,
|
||||
NoteChanged: 2,
|
||||
PreferencesChanged: 3,
|
||||
PanelResized: 4,
|
||||
EditorFocused: 5,
|
||||
BeganBackupDownload: 6,
|
||||
EndedBackupDownload: 7,
|
||||
DesktopExtsReady: 8,
|
||||
WindowDidFocus: 9,
|
||||
WindowDidBlur: 10,
|
||||
};
|
||||
|
||||
export const EventSources = {
|
||||
UserInteraction: 1,
|
||||
Script: 2
|
||||
};
|
||||
|
||||
export class AppState {
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$rootScope,
|
||||
$timeout,
|
||||
application
|
||||
) {
|
||||
this.$timeout = $timeout;
|
||||
this.$rootScope = $rootScope;
|
||||
this.application = application;
|
||||
this.observers = [];
|
||||
this.locked = true;
|
||||
this.registerVisibilityObservers();
|
||||
this.addAppEventObserver();
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.unsubApp();
|
||||
this.unsubApp = null;
|
||||
this.observers.length = 0;
|
||||
if(this.rootScopeCleanup1) {
|
||||
this.rootScopeCleanup1();
|
||||
this.rootScopeCleanup2();
|
||||
this.rootScopeCleanup1 = null;
|
||||
this.rootScopeCleanup2 = null;
|
||||
}
|
||||
document.removeEventListener('visibilitychange', this.onVisibilityChange);
|
||||
this.onVisibilityChange = null;
|
||||
}
|
||||
|
||||
addAppEventObserver() {
|
||||
this.unsubApp = this.application.addEventObserver(async (eventName) => {
|
||||
if (eventName === ApplicationEvents.Started) {
|
||||
this.locked = true;
|
||||
} else if (eventName === ApplicationEvents.Launched) {
|
||||
this.locked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isLocked() {
|
||||
return this.locked;
|
||||
}
|
||||
|
||||
registerVisibilityObservers() {
|
||||
if (isDesktopApplication()) {
|
||||
this.rootScopeCleanup1 = this.$rootScope.$on('window-lost-focus', () => {
|
||||
this.notifyEvent(AppStateEvents.WindowDidBlur);
|
||||
});
|
||||
this.rootScopeCleanup2 = this.$rootScope.$on('window-gained-focus', () => {
|
||||
this.notifyEvent(AppStateEvents.WindowDidFocus);
|
||||
});
|
||||
} else {
|
||||
/* Tab visibility listener, web only */
|
||||
this.onVisibilityChange = this.onVisibilityChange.bind(this);
|
||||
document.addEventListener('visibilitychange', this.onVisibilityChange);
|
||||
}
|
||||
}
|
||||
|
||||
onVisibilityChange() {
|
||||
const visible = document.visibilityState === "visible";
|
||||
const event = visible
|
||||
? AppStateEvents.WindowDidFocus
|
||||
: AppStateEvents.WindowDidBlur;
|
||||
this.notifyEvent(event);
|
||||
}
|
||||
|
||||
/** @returns A function that unregisters this observer */
|
||||
addObserver(callback) {
|
||||
this.observers.push(callback);
|
||||
return () => {
|
||||
pull(this.observers, callback);
|
||||
};
|
||||
}
|
||||
|
||||
async notifyEvent(eventName, data) {
|
||||
/**
|
||||
* Timeout is particullary important so we can give all initial
|
||||
* controllers a chance to construct before propogting any events *
|
||||
*/
|
||||
return new Promise((resolve) => {
|
||||
this.$timeout(async () => {
|
||||
for (const callback of this.observers) {
|
||||
await callback(eventName, data);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedTag(tag) {
|
||||
if (this.selectedTag === tag) {
|
||||
return;
|
||||
}
|
||||
const previousTag = this.selectedTag;
|
||||
this.selectedTag = tag;
|
||||
this.notifyEvent(
|
||||
AppStateEvents.TagChanged,
|
||||
{
|
||||
tag: tag,
|
||||
previousTag: previousTag
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async setSelectedNote(note) {
|
||||
const run = async () => {
|
||||
const previousNote = this.selectedNote;
|
||||
this.selectedNote = note;
|
||||
await this.notifyEvent(
|
||||
AppStateEvents.NoteChanged,
|
||||
{ previousNote: previousNote }
|
||||
);
|
||||
};
|
||||
if (note && note.content.protected &&
|
||||
await this.application.application.privilegesService.actionRequiresPrivilege(
|
||||
ProtectedActions.ViewProtectedNotes
|
||||
)) {
|
||||
this.application.presentPrivilegesModal(
|
||||
ProtectedActions.ViewProtectedNotes,
|
||||
run
|
||||
);
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedTag() {
|
||||
return this.selectedTag;
|
||||
}
|
||||
|
||||
getSelectedNote() {
|
||||
return this.selectedNote;
|
||||
}
|
||||
|
||||
setUserPreferences(preferences) {
|
||||
this.userPreferences = preferences;
|
||||
this.notifyEvent(
|
||||
AppStateEvents.PreferencesChanged
|
||||
);
|
||||
}
|
||||
|
||||
panelDidResize({ name, collapsed }) {
|
||||
this.notifyEvent(
|
||||
AppStateEvents.PanelResized,
|
||||
{
|
||||
panel: name,
|
||||
collapsed: collapsed
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
editorDidFocus(eventSource) {
|
||||
this.notifyEvent(
|
||||
AppStateEvents.EditorFocused,
|
||||
{ eventSource: eventSource }
|
||||
);
|
||||
}
|
||||
|
||||
beganBackupDownload() {
|
||||
this.notifyEvent(
|
||||
AppStateEvents.BeganBackupDownload
|
||||
);
|
||||
}
|
||||
|
||||
endedBackupDownload({ success }) {
|
||||
this.notifyEvent(
|
||||
AppStateEvents.EndedBackupDownload,
|
||||
{ success: success }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the desktop appplication extension server is ready.
|
||||
*/
|
||||
desktopExtensionsReady() {
|
||||
this.notifyEvent(
|
||||
AppStateEvents.DesktopExtsReady
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,33 +5,35 @@ import {
|
||||
EncryptionIntents,
|
||||
ApplicationService,
|
||||
} from 'snjs';
|
||||
import { AppStateEvents } from '@/state';
|
||||
import { AppStateEvents } from '@/services/state';
|
||||
|
||||
const CACHED_THEMES_KEY = 'cachedThemes';
|
||||
|
||||
export class ThemeManager extends ApplicationService {
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
application,
|
||||
appState,
|
||||
desktopManager,
|
||||
) {
|
||||
constructor(application) {
|
||||
super(application);
|
||||
this.appState = appState;
|
||||
this.desktopManager = desktopManager;
|
||||
this.activeThemes = [];
|
||||
this.unsubState = appState.addObserver((eventName, data) => {
|
||||
if (eventName === AppStateEvents.DesktopExtsReady) {
|
||||
this.activateCachedThemes();
|
||||
}
|
||||
setImmediate(() => {
|
||||
this.unsubState = this.application.getAppState().addObserver((eventName, data) => {
|
||||
if (eventName === AppStateEvents.DesktopExtsReady) {
|
||||
this.activateCachedThemes();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.unsubState();
|
||||
this.unsubState = null;
|
||||
this.activeThemes.length = 0;
|
||||
super.deinit();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
onAppStart() {
|
||||
super.onAppStart();
|
||||
this.registerObservers();
|
||||
if (!this.desktopManager.isDesktop) {
|
||||
if (!this.application.getDesktopService().isDesktop) {
|
||||
this.activateCachedThemes();
|
||||
}
|
||||
}
|
||||
@@ -52,7 +54,7 @@ export class ThemeManager extends ApplicationService {
|
||||
}
|
||||
|
||||
registerObservers() {
|
||||
this.desktopManager.registerUpdateObserver((component) => {
|
||||
this.application.getDesktopService().registerUpdateObserver((component) => {
|
||||
if (component.active && component.isTheme()) {
|
||||
this.deactivateTheme(component);
|
||||
setTimeout(() => {
|
||||
|
||||
Reference in New Issue
Block a user