ApplicationManager and better memory management

This commit is contained in:
Mo Bitar
2020-03-23 19:59:55 -05:00
parent 7dc3dab90b
commit ee7cb1fce6
55 changed files with 36786 additions and 3046 deletions

View File

@@ -1,34 +1,38 @@
import { ApplicationService } from 'snjs';
import { ApplicationEvents } from 'snjs';
export class PureCtrl extends ApplicationService {
export class PureCtrl {
/* @ngInject */
constructor(
$scope,
$timeout,
application,
appState
) {
if (!$scope || !$timeout || !application || !appState) {
throw 'Invalid PureCtrl construction.';
constructor($timeout) {
if(!$timeout) {
throw Error('$timeout must not be null');
}
super(application);
this.$scope = $scope;
this.$timeout = $timeout;
this.appState = appState;
this.props = {};
this.state = {};
/* Allow caller constructor to finish setting instance variables */
setImmediate(() => {
this.state = this.getInitialState();
});
$scope.$on('$destroy', () => {
this.unsubState();
this.deinit();
});
}
$onInit() {
this.addAppEventObserver();
this.addAppStateObserver();
}
$onInit() {
this.addAppStateObserver();
deinit() {
this.unsubApp();
this.unsubState();
this.unsubApp = null;
this.unsubState = null;
this.application = null;
if (this.stateTimeout) {
this.$timeout.cancel(this.stateTimeout);
}
}
$onDestroy() {
this.deinit();
}
/** @private */
@@ -43,8 +47,11 @@ export class PureCtrl extends ApplicationService {
}
async setState(state) {
if(!this.$timeout) {
return;
}
return new Promise((resolve) => {
this.$timeout(() => {
this.stateTimeout = this.$timeout(() => {
this.state = Object.freeze(Object.assign({}, this.state, state));
resolve();
});
@@ -59,7 +66,7 @@ export class PureCtrl extends ApplicationService {
}
addAppStateObserver() {
this.unsubState = this.appState.addObserver((eventName, data) => {
this.unsubState = this.application.getAppState().addObserver((eventName, data) => {
this.onAppStateEvent(eventName, data);
});
}
@@ -68,9 +75,46 @@ export class PureCtrl extends ApplicationService {
/** Optional override */
}
addAppEventObserver() {
if (this.application.isStarted()) {
this.onAppStart();
}
if (this.application.isLaunched()) {
this.onAppLaunch();
}
this.unsubApp = this.application.addEventObserver(async (eventName) => {
this.onAppEvent(eventName);
if (eventName === ApplicationEvents.Started) {
await this.onAppStart();
} else if (eventName === ApplicationEvents.Launched) {
await this.onAppLaunch();
} else if (eventName === ApplicationEvents.CompletedSync) {
this.onAppSync();
} else if (eventName === ApplicationEvents.KeyStatusChanged) {
this.onAppKeyChange();
}
});
}
onAppEvent(eventName) {
/** Optional override */
}
/** @override */
async onAppStart() {
await this.resetState();
return super.onAppStart();
}
async onAppLaunch() {
/** Optional override */
}
async onAppKeyChange() {
/** Optional override */
}
onAppSync() {
/** Optional override */
}
}

View File

@@ -0,0 +1,281 @@
import { getPlatformString } from '@/utils';
import template from '%/application-view.pug';
import { AppStateEvents } from '@/services/state';
import { ApplicationEvents } from 'snjs';
import angular from 'angular';
import {
PANEL_NAME_NOTES,
PANEL_NAME_TAGS
} from '@/controllers/constants';
import {
STRING_SESSION_EXPIRED,
STRING_DEFAULT_FILE_ERROR
} from '@/strings';
import { PureCtrl } from './abstract/pure_ctrl';
class ApplicationViewCtrl extends PureCtrl {
/* @ngInject */
constructor(
$compile,
$location,
$rootScope,
$timeout
) {
super($timeout);
this.$location = $location;
this.$rootScope = $rootScope;
this.$compile = $compile;
this.platformString = getPlatformString();
this.state = { appClass: '' };
this.onDragDrop = this.onDragDrop.bind(this);
this.onDragOver = this.onDragOver.bind(this);
this.openModalComponent = this.openModalComponent.bind(this);
this.presentPermissionsDialog = this.presentPermissionsDialog.bind(this);
this.addDragDropHandlers();
}
deinit() {
this.$location = null;
this.$rootScope = null;
this.$compile = null;
this.application = null;
this.lockScreenPuppet = null;
window.removeEventListener('dragover', this.onDragOver, true);
window.removeEventListener('drop', this.onDragDrop, true);
this.onDragDrop = null;
this.onDragOver = null;
this.openModalComponent = null;
this.presentPermissionsDialog = null;
super.deinit();
}
$onInit() {
super.$onInit();
this.loadApplication();
}
async loadApplication() {
await this.application.prepareForLaunch({
callbacks: {
receiveChallenge: async (challenge, orchestrator) => {
this.application.promptForChallenge(challenge, orchestrator);
}
}
});
await this.application.launch();
}
onAppStart() {
super.onAppStart();
this.overrideComponentManagerFunctions();
this.application.componentManager.setDesktopManager(this.application.getDesktopService());
this.setState({
ready: true,
needsUnlock: this.application.hasPasscode()
});
}
onAppLaunch() {
super.onAppLaunch();
this.setState({ needsUnlock: false });
this.handleAutoSignInFromParams();
}
onUpdateAvailable() {
this.$rootScope.$broadcast('new-update-available');
};
/** @override */
async onAppEvent(eventName) {
super.onAppEvent(eventName);
if (eventName === ApplicationEvents.LocalDataIncrementalLoad) {
this.updateLocalDataStatus();
} else if (eventName === ApplicationEvents.SyncStatusChanged) {
this.updateSyncStatus();
} else if (eventName === ApplicationEvents.LocalDataLoaded) {
this.updateLocalDataStatus();
} else if (eventName === ApplicationEvents.WillSync) {
if (!this.completedInitialSync) {
this.syncStatus = this.application.getStatusService().replaceStatusWithString(
this.syncStatus,
"Syncing..."
);
}
} else if (eventName === ApplicationEvents.CompletedSync) {
if (!this.completedInitialSync) {
this.syncStatus = this.application.getStatusService().removeStatus(this.syncStatus);
this.completedInitialSync = true;
}
} else if (eventName === ApplicationEvents.InvalidSyncSession) {
this.showInvalidSessionAlert();
}
}
/** @override */
async onAppStateEvent(eventName, data) {
if (eventName === AppStateEvents.PanelResized) {
if (data.panel === PANEL_NAME_NOTES) {
this.notesCollapsed = data.collapsed;
}
if (data.panel === PANEL_NAME_TAGS) {
this.tagsCollapsed = data.collapsed;
}
let appClass = "";
if (this.notesCollapsed) { appClass += "collapsed-notes"; }
if (this.tagsCollapsed) { appClass += " collapsed-tags"; }
this.setState({ appClass });
} else if (eventName === AppStateEvents.WindowDidFocus) {
if (!(await this.application.isLocked())) {
this.application.sync();
}
}
}
updateLocalDataStatus() {
const syncStatus = this.application.getSyncStatus();
const stats = syncStatus.getStats();
const encryption = this.application.isEncryptionAvailable();
if (stats.localDataDone) {
this.syncStatus = this.application.getStatusService().removeStatus(this.syncStatus);
return;
}
const notesString = `${stats.localDataCurrent}/${stats.localDataTotal} items...`;
const loadingStatus = encryption
? `Decrypting ${notesString}`
: `Loading ${notesString}`;
this.syncStatus = this.application.getStatusService().replaceStatusWithString(
this.syncStatus,
loadingStatus
);
}
updateSyncStatus() {
const syncStatus = this.application.getSyncStatus();
const stats = syncStatus.getStats();
if (stats.downloadCount > 20) {
const text = `Downloading ${stats.downloadCount} items. Keep app open.`;
this.syncStatus = this.application.getStatusService().replaceStatusWithString(
this.syncStatus,
text
);
this.showingDownloadStatus = true;
} else if (this.showingDownloadStatus) {
this.showingDownloadStatus = false;
const text = "Download Complete.";
this.syncStatus = this.application.getStatusService().replaceStatusWithString(
this.syncStatus,
text
);
setTimeout(() => {
this.syncStatus = this.application.getStatusService().removeStatus(this.syncStatus);
}, 2000);
} else if (stats.uploadTotalCount > 20) {
this.uploadSyncStatus = this.application.getStatusService().replaceStatusWithString(
this.uploadSyncStatus,
`Syncing ${stats.uploadCompletionCount}/${stats.uploadTotalCount} items...`
);
} else if (this.uploadSyncStatus) {
this.uploadSyncStatus = this.application.getStatusService().removeStatus(
this.uploadSyncStatus
);
}
}
openModalComponent(component) {
const scope = this.$rootScope.$new(true);
scope.component = component;
const el = this.$compile("<component-modal component='component' class='sk-modal'></component-modal>")(scope);
angular.element(document.body).append(el);
}
presentPermissionsDialog(dialog) {
const scope = this.$rootScope.$new(true);
scope.permissionsString = dialog.permissionsString;
scope.component = dialog.component;
scope.callback = dialog.callback;
const el = this.$compile("<permissions-modal component='component' permissions-string='permissionsString' callback='callback' class='sk-modal'></permissions-modal>")(scope);
angular.element(document.body).append(el);
}
overrideComponentManagerFunctions() {
this.application.componentManager.openModalComponent = this.openModalComponent;
this.application.componentManager.presentPermissionsDialog = this.presentPermissionsDialog;
}
showInvalidSessionAlert() {
/** Don't show repeatedly; at most 30 seconds in between */
const SHOW_INTERVAL = 30;
const lastShownSeconds = (new Date() - this.lastShownDate) / 1000;
if (!this.lastShownDate || lastShownSeconds > SHOW_INTERVAL) {
this.lastShownDate = new Date();
setTimeout(() => {
this.alertService.alert({
text: STRING_SESSION_EXPIRED
});
}, 500);
}
}
addDragDropHandlers() {
/**
* Disable dragging and dropping of files (but allow text) into main SN interface.
* both 'dragover' and 'drop' are required to prevent dropping of files.
* This will not prevent extensions from receiving drop events.
*/
window.addEventListener('dragover', this.onDragOver, true);
window.addEventListener('drop', this.onDragDrop, true);
}
onDragOver(event) {
if (event.dataTransfer.files.length > 0) {
event.preventDefault();
}
}
onDragDrop(event) {
if (event.dataTransfer.files.length > 0) {
event.preventDefault();
this.application.alertService.alert({
text: STRING_DEFAULT_FILE_ERROR
});
}
}
async handleAutoSignInFromParams() {
const params = this.$location.search();
const server = params.server;
const email = params.email;
const password = params.pw;
if (!server || !email || !password) return;
const user = this.application.getUser();
if (user) {
if (user.email === email && await this.application.getHost() === server) {
/** Already signed in, return */
return;
} else {
/** Sign out */
await this.application.signOut();
await this.application.restart();
}
}
await this.application.setHost(server);
this.application.signIn({
email: email,
password: password,
});
}
}
export class ApplicationView {
constructor() {
this.template = template;
this.controller = ApplicationViewCtrl;
this.replace = true;
this.controllerAs = 'self';
this.bindToController = {
application: '='
};
}
}

View File

@@ -10,7 +10,7 @@ import { isDesktopApplication } from '@/utils';
import { KeyboardModifiers, KeyboardKeys } from '@/services/keyboardManager';
import template from '%/editor.pug';
import { PureCtrl } from '@Controllers';
import { AppStateEvents, EventSources } from '@/state';
import { AppStateEvents, EventSources } from '@/services/state';
import {
STRING_DELETED_NOTE,
STRING_INVALID_NOTE,
@@ -48,35 +48,34 @@ const Fonts = {
class EditorCtrl extends PureCtrl {
/* @ngInject */
constructor(
$scope,
$timeout,
$rootScope,
application,
appState,
desktopManager,
keyboardManager,
preferencesManager,
) {
super($scope, $timeout, application, appState);
this.$rootScope = $rootScope;
this.desktopManager = desktopManager;
this.keyboardManager = keyboardManager;
this.preferencesManager = preferencesManager;
constructor($timeout) {
super($timeout);
this.leftPanelPuppet = {
onReady: () => this.reloadPreferences()
};
this.rightPanelPuppet = {
onReady: () => this.reloadPreferences()
};
this.addSyncStatusObserver();
this.registerKeyboardShortcuts();
/** Used by .pug template */
this.prefKeyMonospace = PrefKeys.EditorMonospaceEnabled;
this.prefKeySpellcheck = PrefKeys.EditorSpellcheck;
this.prefKeyMarginResizers = PrefKeys.EditorResizersEnabled;
}
deinit() {
this.removeTabObserver();
this.leftPanelPuppet = null;
this.rightPanelPuppet = null;
this.onEditorLoad = null;
super.deinit();
}
$onInit() {
super.$onInit();
this.addSyncStatusObserver();
this.registerKeyboardShortcuts();
}
/** @override */
getInitialState() {
return {
@@ -100,7 +99,7 @@ class EditorCtrl extends PureCtrl {
onAppStateEvent(eventName, data) {
if (eventName === AppStateEvents.NoteChanged) {
this.handleNoteSelectionChange(
this.appState.getSelectedNote(),
this.application.getAppState().getSelectedNote(),
data.previousNote
);
} else if (eventName === AppStateEvents.PreferencesChanged) {
@@ -209,7 +208,7 @@ class EditorCtrl extends PureCtrl {
async handleNoteSelectionChange(note, previousNote) {
this.setState({
note: this.appState.getSelectedNote(),
note: this.application.getAppState().getSelectedNote(),
showExtensions: false,
showOptionsMenu: false,
altKeyDown: false,
@@ -504,7 +503,7 @@ class EditorCtrl extends PureCtrl {
}
onContentFocus() {
this.appState.editorDidFocus(this.lastEditorFocusEventSource);
this.application.getAppState().editorDidFocus(this.lastEditorFocusEventSource);
this.lastEditorFocusEventSource = null;
}
@@ -552,7 +551,7 @@ class EditorCtrl extends PureCtrl {
dontUpdatePreviews: true
});
}
this.appState.setSelectedNote(null);
this.application.getAppState().setSelectedNote(null);
this.setMenuState('showOptionsMenu', false);
}
});
@@ -561,7 +560,7 @@ class EditorCtrl extends PureCtrl {
ProtectedActions.DeleteNote
);
if (requiresPrivilege) {
this.godService.presentPrivilegesModal(
this.application.presentPrivilegesModal(
ProtectedActions.DeleteNote,
() => {
run();
@@ -592,7 +591,7 @@ class EditorCtrl extends PureCtrl {
bypassDebouncer: true,
dontUpdatePreviews: true
});
this.appState.setSelectedNote(null);
this.application.getAppState().setSelectedNote(null);
}
deleteNotePermanantely() {
@@ -649,7 +648,7 @@ class EditorCtrl extends PureCtrl {
ProtectedActions.ViewProtectedNotes
).then((configured) => {
if (!configured) {
this.godService.presentPrivilegesManagementModal();
this.application.presentPrivilegesManagementModal();
}
});
}
@@ -743,13 +742,13 @@ class EditorCtrl extends PureCtrl {
onPanelResizeFinish = (width, left, isMaxWidth) => {
if (isMaxWidth) {
this.preferencesManager.setUserPrefValue(
this.application.getPrefsService().setUserPrefValue(
PrefKeys.EditorWidth,
null
);
} else {
if (width !== undefined && width !== null) {
this.preferencesManager.setUserPrefValue(
this.application.getPrefsService().setUserPrefValue(
PrefKeys.EditorWidth,
width
);
@@ -757,25 +756,25 @@ class EditorCtrl extends PureCtrl {
}
}
if (left !== undefined && left !== null) {
this.preferencesManager.setUserPrefValue(
this.application.getPrefsService().setUserPrefValue(
PrefKeys.EditorLeft,
left
);
this.rightPanelPuppet.setLeft(left);
}
this.preferencesManager.syncUserPreferences();
this.application.getPrefsService().syncUserPreferences();
}
reloadPreferences() {
const monospaceEnabled = this.preferencesManager.getValue(
const monospaceEnabled = this.application.getPrefsService().getValue(
PrefKeys.EditorMonospaceEnabled,
true
);
const spellcheck = this.preferencesManager.getValue(
const spellcheck = this.application.getPrefsService().getValue(
PrefKeys.EditorSpellcheck,
true
);
const marginResizersEnabled = this.preferencesManager.getValue(
const marginResizersEnabled = this.application.getPrefsService().getValue(
PrefKeys.EditorResizersEnabled,
true
);
@@ -797,7 +796,7 @@ class EditorCtrl extends PureCtrl {
this.leftPanelPuppet.ready &&
this.rightPanelPuppet.ready
) {
const width = this.preferencesManager.getValue(
const width = this.application.getPrefsService().getValue(
PrefKeys.EditorWidth,
null
);
@@ -805,7 +804,7 @@ class EditorCtrl extends PureCtrl {
this.leftPanelPuppet.setWidth(width);
this.rightPanelPuppet.setWidth(width);
}
const left = this.preferencesManager.getValue(
const left = this.application.getPrefsService().getValue(
PrefKeys.EditorLeft,
null
);
@@ -836,7 +835,7 @@ class EditorCtrl extends PureCtrl {
async toggleKey(key) {
this[key] = !this[key];
this.preferencesManager.setUserPrefValue(
this.application.getPrefsService().setUserPrefValue(
key,
this[key],
true
@@ -863,7 +862,7 @@ class EditorCtrl extends PureCtrl {
/** @components */
onEditorLoad = (editor) => {
this.desktopManager.redoSearch();
this.application.getDesktopService().redoSearch();
}
registerComponentHandler() {
@@ -1047,7 +1046,7 @@ class EditorCtrl extends PureCtrl {
}
registerKeyboardShortcuts() {
this.altKeyObserver = this.keyboardManager.addKeyObserver({
this.altKeyObserver = this.application.getKeyboardService().addKeyObserver({
modifiers: [
KeyboardModifiers.Alt
],
@@ -1063,7 +1062,7 @@ class EditorCtrl extends PureCtrl {
}
});
this.trashKeyObserver = this.keyboardManager.addKeyObserver({
this.trashKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: KeyboardKeys.Backspace,
notElementIds: [
ElementIds.NoteTextEditor,
@@ -1075,7 +1074,7 @@ class EditorCtrl extends PureCtrl {
},
});
this.deleteKeyObserver = this.keyboardManager.addKeyObserver({
this.deleteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: KeyboardKeys.Backspace,
modifiers: [
KeyboardModifiers.Meta,
@@ -1090,10 +1089,9 @@ class EditorCtrl extends PureCtrl {
}
onSystemEditorLoad() {
if (this.loadedTabListener) {
if (this.tabObserver) {
return;
}
this.loadedTabListener = true;
/**
* Insert 4 spaces when a tab key is pressed,
* only used when inside of the text editor.
@@ -1103,7 +1101,7 @@ class EditorCtrl extends PureCtrl {
const editor = document.getElementById(
ElementIds.NoteTextEditor
);
this.tabObserver = this.keyboardManager.addKeyObserver({
this.tabObserver = this.application.getKeyboardService().addKeyObserver({
element: editor,
key: KeyboardKeys.Tab,
onKeyDown: (event) => {
@@ -1144,19 +1142,29 @@ class EditorCtrl extends PureCtrl {
* Handles when the editor is destroyed,
* (and not when our controller is destroyed.)
*/
angular.element(editor).on('$destroy', () => {
if (this.tabObserver) {
this.keyboardManager.removeKeyObserver(this.tabObserver);
this.loadedTabListener = false;
}
angular.element(editor).one('$destroy', () => {
this.removeTabObserver();
});
};
}
removeTabObserver() {
if (!this.application) {
return;
}
const keyboardService = this.application.getKeyboardService();
if (this.tabObserver && keyboardService) {
keyboardService.removeKeyObserver(this.tabObserver);
this.tabObserver = null;
}
}
}
export class EditorPanel {
constructor() {
this.restrict = 'E';
this.scope = {};
this.scope = {
application: '='
};
this.template = template;
this.replace = true;
this.controller = EditorCtrl;

View File

@@ -6,7 +6,7 @@ import {
ContentTypes
} from 'snjs';
import template from '%/footer.pug';
import { AppStateEvents, EventSources } from '@/state';
import { AppStateEvents, EventSources } from '@/services/state';
import {
STRING_GENERIC_SYNC_ERROR,
STRING_NEW_UPDATE_READY
@@ -17,25 +17,32 @@ class FooterCtrl extends PureCtrl {
/* @ngInject */
constructor(
$scope,
$rootScope,
$timeout,
application,
appState,
nativeExtManager,
statusManager,
godService
) {
super($scope, $timeout, application, appState);
super($timeout);
this.$rootScope = $rootScope;
this.nativeExtManager = nativeExtManager;
this.statusManager = statusManager;
this.godService = godService;
this.rooms = [];
this.themesWithIcons = [];
this.showSyncResolution = false;
this.addRootScopeListeners();
this.statusManager.addStatusObserver((string) => {
}
deinit() {
this.rooms.length = 0;
this.themesWithIcons.length = 0;
this.rootScopeListener1();
this.rootScopeListener2();
this.rootScopeListener1 = null;
this.rootScopeListener2 = null;
this.closeAccountMenu = null;
this.toggleSyncResolutionMenu = null;
super.deinit();
}
$onInit() {
super.$onInit();
this.application.getStatusService().addStatusObserver((string) => {
this.$timeout(() => {
this.arbitraryStatusMessage = string;
});
@@ -49,7 +56,7 @@ class FooterCtrl extends PureCtrl {
}
reloadUpgradeStatus() {
this.godService.checkForSecurityUpdate().then((available) => {
this.application.checkForSecurityUpdate().then((available) => {
this.setState({
dataUpgradeAvailable: available
});
@@ -72,10 +79,10 @@ class FooterCtrl extends PureCtrl {
}
addRootScopeListeners() {
this.$rootScope.$on("reload-ext-data", () => {
this.rootScopeListener1 = this.$rootScope.$on("reload-ext-data", () => {
this.reloadExtendedData();
});
this.$rootScope.$on("new-update-available", () => {
this.rootScopeListener2 = this.$rootScope.$on("new-update-available", () => {
this.$timeout(() => {
this.onNewUpdateAvailable();
});
@@ -90,23 +97,23 @@ class FooterCtrl extends PureCtrl {
this.closeAccountMenu();
}
} else if (eventName === AppStateEvents.BeganBackupDownload) {
this.backupStatus = this.statusManager.addStatusFromString(
this.backupStatus = this.application.getStatusService().addStatusFromString(
"Saving local backup..."
);
} else if (eventName === AppStateEvents.EndedBackupDownload) {
if (data.success) {
this.backupStatus = this.statusManager.replaceStatusWithString(
this.backupStatus = this.application.getStatusService().replaceStatusWithString(
this.backupStatus,
"Successfully saved backup."
);
} else {
this.backupStatus = this.statusManager.replaceStatusWithString(
this.backupStatus = this.application.getStatusService().replaceStatusWithString(
this.backupStatus,
"Unable to save local backup."
);
}
this.$timeout(() => {
this.backupStatus = this.statusManager.removeStatus(this.backupStatus);
this.backupStatus = this.application.getStatusService().removeStatus(this.backupStatus);
}, 2000);
}
}
@@ -205,7 +212,7 @@ class FooterCtrl extends PureCtrl {
* then closing it after a short delay.
*/
const extWindow = this.rooms.find((room) => {
return room.package_info.identifier === this.nativeExtManager.extManagerId;
return room.package_info.identifier === this.application.getNativeExtService().extManagerId;
});
if (!extWindow) {
this.queueExtReload = true;
@@ -225,7 +232,7 @@ class FooterCtrl extends PureCtrl {
}
openSecurityUpdate() {
this.godService.performProtocolUpgrade();
this.application.performProtocolUpgrade();
}
findErrors() {
@@ -347,7 +354,7 @@ class FooterCtrl extends PureCtrl {
ProtectedActions.ManageExtensions
);
if (requiresPrivilege) {
this.godService.presentPrivilegesModal(
this.application.presentPrivilegesModal(
ProtectedActions.ManageExtensions,
run
);
@@ -360,7 +367,7 @@ class FooterCtrl extends PureCtrl {
}
clickOutsideAccountMenu() {
if (this.godService.authenticationInProgress()) {
if (this.application && this.application.authenticationInProgress()) {
return;
}
this.showAccountMenu = false;
@@ -370,11 +377,12 @@ class FooterCtrl extends PureCtrl {
export class Footer {
constructor() {
this.restrict = 'E';
this.scope = {};
this.template = template;
this.controller = FooterCtrl;
this.replace = true;
this.controllerAs = 'ctrl';
this.bindToController = true;
this.bindToController = {
application: '='
};
}
}

View File

@@ -4,4 +4,4 @@ export { Footer } from './footer';
export { NotesPanel } from './notes/notes';
export { TagsPanel } from './tags';
export { Root } from './root';
export { LockScreen } from './lockScreen';
export { ApplicationView } from './applicationView';

View File

@@ -1,5 +1,5 @@
import template from '%/lock-screen.pug';
import { AppStateEvents } from '@/state';
import { AppStateEvents } from '@/services/state';
import { PureCtrl } from './abstract/pure_ctrl';
const ELEMENT_ID_PASSCODE_INPUT = 'passcode-input';
@@ -7,12 +7,9 @@ const ELEMENT_ID_PASSCODE_INPUT = 'passcode-input';
class LockScreenCtrl extends PureCtrl {
/* @ngInject */
constructor(
$scope,
$timeout,
application,
appState
) {
super($scope, $timeout, application, appState);
super($timeout);
this.formData = {};
}

View File

@@ -2,7 +2,7 @@ import angular from 'angular';
import template from '%/notes.pug';
import { ApplicationEvents, ContentTypes, removeFromArray } from 'snjs';
import { PureCtrl } from '@Controllers';
import { AppStateEvents } from '@/state';
import { AppStateEvents } from '@/services/state';
import { KeyboardModifiers, KeyboardKeys } from '@/services/keyboardManager';
import {
PrefKeys
@@ -31,24 +31,10 @@ class NotesCtrl extends PureCtrl {
/* @ngInject */
constructor(
$scope,
$timeout,
$rootScope,
application,
appState,
desktopManager,
keyboardManager,
preferencesManager,
) {
super($scope, $timeout, application, appState);
this.$rootScope = $rootScope;
this.application = application;
this.appState = appState;
this.desktopManager = desktopManager;
this.keyboardManager = keyboardManager;
this.preferencesManager = preferencesManager;
super($timeout);
this.resetPagination();
this.registerKeyboardShortcuts();
}
$onInit() {
@@ -59,11 +45,24 @@ class NotesCtrl extends PureCtrl {
this.panelPuppet = {
onReady: () => this.reloadPreferences()
};
window.onresize = (event) => {
this.resetPagination({
keepCurrentIfLarger: true
});
};
this.onWindowResize = this.onWindowResize.bind(this);
window.addEventListener('resize', this.onWindowResize, true);
this.registerKeyboardShortcuts();
}
onWindowResize() {
this.resetPagination({
keepCurrentIfLarger: true
});
}
deinit() {
this.panelPuppet.onReady = null;
this.panelPuppet = null;
window.removeEventListener('resize', this.onWindowResize, true);
this.onWindowResize = null;
this.onPanelResize = null;
super.deinit();
}
getInitialState() {
@@ -91,9 +90,9 @@ class NotesCtrl extends PureCtrl {
/** @override */
onAppStateEvent(eventName, data) {
if (eventName === AppStateEvents.TagChanged) {
this.handleTagChange(this.appState.getSelectedTag(), data.previousTag);
this.handleTagChange(this.application.getAppState().getSelectedTag(), data.previousTag);
} else if (eventName === AppStateEvents.NoteChanged) {
this.handleNoteSelection(this.appState.getSelectedNote());
this.handleNoteSelection(this.application.getAppState().getSelectedNote());
} else if (eventName === AppStateEvents.PreferencesChanged) {
this.reloadPreferences();
this.reloadNotes();
@@ -129,8 +128,8 @@ class NotesCtrl extends PureCtrl {
* @access private
*/
async createPlaceholderNote() {
const selectedTag = this.appState.getSelectedTag();
if(selectedTag.isSmartTag() && !selectedTag.content.isAllTag) {
const selectedTag = this.application.getAppState().getSelectedTag();
if (selectedTag.isSmartTag() && !selectedTag.content.isAllTag) {
return;
}
return this.createNewNote();
@@ -163,11 +162,11 @@ class NotesCtrl extends PureCtrl {
}
async selectNote(note) {
this.appState.setSelectedNote(note);
this.application.getAppState().setSelectedNote(note);
}
async createNewNote() {
const selectedTag = this.appState.getSelectedTag();
const selectedTag = this.application.getAppState().getSelectedTag();
if (!selectedTag) {
throw 'Attempting to create note with no selected tag';
}
@@ -209,11 +208,11 @@ class NotesCtrl extends PureCtrl {
await this.selectNote(null);
}
await this.setState({ tag: tag });
this.resetScrollPosition();
this.setShowMenuFalse();
await this.setNoteFilterText('');
this.desktopManager.searchText();
this.application.getDesktopService().searchText();
this.resetPagination();
/* Capture db load state before beginning reloadNotes, since this status may change during reload */
@@ -307,14 +306,14 @@ class NotesCtrl extends PureCtrl {
this.application.saveItem({ item: note });
}
if (this.isFiltering()) {
this.desktopManager.searchText(this.state.noteFilter.text);
this.application.getDesktopService().searchText(this.state.noteFilter.text);
}
}
reloadPreferences() {
const viewOptions = {};
const prevSortValue = this.state.sortBy;
let sortBy = this.preferencesManager.getValue(
let sortBy = this.application.getPrefsService().getValue(
PrefKeys.SortNotesBy,
SORT_KEY_CREATED_AT
);
@@ -323,27 +322,27 @@ class NotesCtrl extends PureCtrl {
sortBy = SORT_KEY_CLIENT_UPDATED_AT;
}
viewOptions.sortBy = sortBy;
viewOptions.sortReverse = this.preferencesManager.getValue(
viewOptions.sortReverse = this.application.getPrefsService().getValue(
PrefKeys.SortNotesReverse,
false
);
viewOptions.showArchived = this.preferencesManager.getValue(
viewOptions.showArchived = this.application.getPrefsService().getValue(
PrefKeys.NotesShowArchived,
false
);
viewOptions.hidePinned = this.preferencesManager.getValue(
viewOptions.hidePinned = this.application.getPrefsService().getValue(
PrefKeys.NotesHidePinned,
false
);
viewOptions.hideNotePreview = this.preferencesManager.getValue(
viewOptions.hideNotePreview = this.application.getPrefsService().getValue(
PrefKeys.NotesHideNotePreview,
false
);
viewOptions.hideDate = this.preferencesManager.getValue(
viewOptions.hideDate = this.application.getPrefsService().getValue(
PrefKeys.NotesHideDate,
false
);
viewOptions.hideTags = this.preferencesManager.getValue(
viewOptions.hideTags = this.application.getPrefsService().getValue(
PrefKeys.NotesHideTags,
false
);
@@ -353,13 +352,13 @@ class NotesCtrl extends PureCtrl {
if (prevSortValue && prevSortValue !== sortBy) {
this.selectFirstNote();
}
const width = this.preferencesManager.getValue(
const width = this.application.getPrefsService().getValue(
PrefKeys.NotesPanelWidth
);
if (width && this.panelPuppet.ready) {
this.panelPuppet.setWidth(width);
if (this.panelPuppet.isCollapsed()) {
this.appState.panelDidResize({
this.application.getAppState().panelDidResize({
name: PANEL_NAME_NOTES,
collapsed: this.panelPuppet.isCollapsed()
});
@@ -368,12 +367,12 @@ class NotesCtrl extends PureCtrl {
}
onPanelResize = (newWidth, lastLeft, isAtMaxWidth, isCollapsed) => {
this.preferencesManager.setUserPrefValue(
this.application.getPrefsService().setUserPrefValue(
PrefKeys.NotesPanelWidth,
newWidth
);
this.preferencesManager.syncUserPreferences();
this.appState.panelDidResize({
this.application.getPrefsService().syncUserPreferences();
this.application.getAppState().panelDidResize({
name: PANEL_NAME_NOTES,
collapsed: isCollapsed
});
@@ -383,7 +382,7 @@ class NotesCtrl extends PureCtrl {
this.notesToDisplay += this.pageSize;
this.reloadNotes();
if (this.searchSubmitted) {
this.desktopManager.searchText(this.state.noteFilter.text);
this.application.getDesktopService().searchText(this.state.noteFilter.text);
}
}
@@ -584,7 +583,7 @@ class NotesCtrl extends PureCtrl {
* enter before highlighting desktop search results.
*/
this.searchSubmitted = true;
this.desktopManager.searchText(this.state.noteFilter.text);
this.application.getDesktopService().searchText(this.state.noteFilter.text);
}
selectedMenuItem() {
@@ -592,8 +591,8 @@ class NotesCtrl extends PureCtrl {
}
togglePrefKey(key) {
this.preferencesManager.setUserPrefValue(key, !this.state[key]);
this.preferencesManager.syncUserPreferences();
this.application.getPrefsService().setUserPrefValue(key, !this.state[key]);
this.application.getPrefsService().syncUserPreferences();
}
selectedSortByCreated() {
@@ -610,19 +609,19 @@ class NotesCtrl extends PureCtrl {
toggleReverseSort() {
this.selectedMenuItem();
this.preferencesManager.setUserPrefValue(
this.application.getPrefsService().setUserPrefValue(
PrefKeys.SortNotesReverse,
!this.state.sortReverse
);
this.preferencesManager.syncUserPreferences();
this.application.getPrefsService().syncUserPreferences();
}
setSortBy(type) {
this.preferencesManager.setUserPrefValue(
this.application.getPrefsService().setUserPrefValue(
PrefKeys.SortNotesBy,
type
);
this.preferencesManager.syncUserPreferences();
this.application.getPrefsService().syncUserPreferences();
}
shouldShowTagsForNote(note) {
@@ -652,7 +651,7 @@ class NotesCtrl extends PureCtrl {
* use Control modifier as well. These rules don't apply to desktop, but
* probably better to be consistent.
*/
this.newNoteKeyObserver = this.keyboardManager.addKeyObserver({
this.newNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: 'n',
modifiers: [
KeyboardModifiers.Meta,
@@ -664,7 +663,7 @@ class NotesCtrl extends PureCtrl {
}
});
this.nextNoteKeyObserver = this.keyboardManager.addKeyObserver({
this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: KeyboardKeys.Down,
elements: [
document.body,
@@ -679,7 +678,7 @@ class NotesCtrl extends PureCtrl {
}
});
this.nextNoteKeyObserver = this.keyboardManager.addKeyObserver({
this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: KeyboardKeys.Up,
element: document.body,
onKeyDown: (event) => {
@@ -687,7 +686,7 @@ class NotesCtrl extends PureCtrl {
}
});
this.searchKeyObserver = this.keyboardManager.addKeyObserver({
this.searchKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: "f",
modifiers: [
KeyboardModifiers.Meta,
@@ -703,11 +702,13 @@ class NotesCtrl extends PureCtrl {
export class NotesPanel {
constructor() {
this.scope = {};
this.template = template;
this.replace = true;
this.controller = NotesCtrl;
this.controllerAs = 'self';
this.bindToController = true;
this.scope = {
application: '='
};
}
}

View File

@@ -1,258 +1,16 @@
import { getPlatformString } from '@/utils';
import template from '%/root.pug';
import { AppStateEvents } from '@/state';
import { ApplicationEvents } from 'snjs';
import angular from 'angular';
import {
PANEL_NAME_NOTES,
PANEL_NAME_TAGS
} from '@/controllers/constants';
import {
STRING_SESSION_EXPIRED,
STRING_DEFAULT_FILE_ERROR,
} from '@/strings';
import { PureCtrl } from './abstract/pure_ctrl';
class RootCtrl extends PureCtrl {
class RootCtrl {
/* @ngInject */
constructor(
$compile,
$location,
$scope,
$rootScope,
$timeout,
application,
appState,
desktopManager,
godService,
lockManager,
preferencesManager /** Unused below, required to load globally */,
themeManager,
statusManager,
) {
super($scope, $timeout, application, appState);
this.$location = $location;
this.$rootScope = $rootScope;
this.$compile = $compile;
this.desktopManager = desktopManager;
this.godService = godService;
this.lockManager = lockManager;
this.statusManager = statusManager;
this.themeManager = themeManager;
this.platformString = getPlatformString();
this.state = { appClass: '' };
this.loadApplication();
this.addDragDropHandlers();
this.lockScreenPuppet = {
focusInput: () => { }
};
}
async loadApplication() {
await this.application.prepareForLaunch({
callbacks: {
receiveChallenge: async (challenge, orchestrator) => {
this.godService.promptForChallenge(challenge, orchestrator);
}
}
});
await this.application.launch();
}
onAppStart() {
super.onAppStart();
this.overrideComponentManagerFunctions();
this.application.componentManager.setDesktopManager(this.desktopManager);
this.setState({
ready: true,
needsUnlock: this.application.hasPasscode()
constructor(applicationManager) {
this.applicationManager = applicationManager;
this.applicationManager.addApplicationChangeObserver(() => {
this.reload();
});
}
onAppLaunch() {
super.onAppLaunch();
this.setState({ needsUnlock: false });
this.handleAutoSignInFromParams();
}
onUpdateAvailable() {
this.$rootScope.$broadcast('new-update-available');
};
/** @override */
async onAppEvent(eventName) {
super.onAppEvent(eventName);
if (eventName === ApplicationEvents.LocalDataIncrementalLoad) {
this.updateLocalDataStatus();
} else if (eventName === ApplicationEvents.SyncStatusChanged) {
this.updateSyncStatus();
} else if (eventName === ApplicationEvents.LocalDataLoaded) {
this.updateLocalDataStatus();
} else if (eventName === ApplicationEvents.WillSync) {
if (!this.completedInitialSync) {
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
"Syncing..."
);
}
} else if (eventName === ApplicationEvents.CompletedSync) {
if (!this.completedInitialSync) {
this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
this.completedInitialSync = true;
}
} else if (eventName === ApplicationEvents.InvalidSyncSession) {
this.showInvalidSessionAlert();
}
}
/** @override */
async onAppStateEvent(eventName, data) {
if (eventName === AppStateEvents.PanelResized) {
if (data.panel === PANEL_NAME_NOTES) {
this.notesCollapsed = data.collapsed;
}
if (data.panel === PANEL_NAME_TAGS) {
this.tagsCollapsed = data.collapsed;
}
let appClass = "";
if (this.notesCollapsed) { appClass += "collapsed-notes"; }
if (this.tagsCollapsed) { appClass += " collapsed-tags"; }
this.setState({ appClass });
} else if (eventName === AppStateEvents.WindowDidFocus) {
if (!(await this.application.isLocked())) {
this.application.sync();
}
}
}
updateLocalDataStatus() {
const syncStatus = this.application.getSyncStatus();
const stats = syncStatus.getStats();
const encryption = this.application.isEncryptionAvailable();
if (stats.localDataDone) {
this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
return;
}
const notesString = `${stats.localDataCurrent}/${stats.localDataTotal} items...`;
const loadingStatus = encryption
? `Decrypting ${notesString}`
: `Loading ${notesString}`;
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
loadingStatus
);
}
updateSyncStatus() {
const syncStatus = this.application.getSyncStatus();
const stats = syncStatus.getStats();
if (stats.downloadCount > 20) {
const text = `Downloading ${stats.downloadCount} items. Keep app open.`;
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
text
);
this.showingDownloadStatus = true;
} else if (this.showingDownloadStatus) {
this.showingDownloadStatus = false;
const text = "Download Complete.";
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
text
);
setTimeout(() => {
this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
}, 2000);
} else if (stats.uploadTotalCount > 20) {
this.uploadSyncStatus = this.statusManager.replaceStatusWithString(
this.uploadSyncStatus,
`Syncing ${stats.uploadCompletionCount}/${stats.uploadTotalCount} items...`
);
} else if (this.uploadSyncStatus) {
this.uploadSyncStatus = this.statusManager.removeStatus(
this.uploadSyncStatus
);
}
}
overrideComponentManagerFunctions() {
function openModalComponent(component) {
const scope = this.$rootScope.$new(true);
scope.component = component;
const el = this.$compile("<component-modal component='component' class='sk-modal'></component-modal>")(scope);
angular.element(document.body).append(el);
}
function presentPermissionsDialog(dialog) {
const scope = this.$rootScope.$new(true);
scope.permissionsString = dialog.permissionsString;
scope.component = dialog.component;
scope.callback = dialog.callback;
const el = this.$compile("<permissions-modal component='component' permissions-string='permissionsString' callback='callback' class='sk-modal'></permissions-modal>")(scope);
angular.element(document.body).append(el);
}
this.application.componentManager.openModalComponent = openModalComponent.bind(this);
this.application.componentManager.presentPermissionsDialog = presentPermissionsDialog.bind(this);
}
showInvalidSessionAlert() {
/** Don't show repeatedly; at most 30 seconds in between */
const SHOW_INTERVAL = 30;
const lastShownSeconds = (new Date() - this.lastShownDate) / 1000;
if (!this.lastShownDate || lastShownSeconds > SHOW_INTERVAL) {
this.lastShownDate = new Date();
setTimeout(() => {
this.alertService.alert({
text: STRING_SESSION_EXPIRED
});
}, 500);
}
}
addDragDropHandlers() {
/**
* Disable dragging and dropping of files (but allow text) into main SN interface.
* both 'dragover' and 'drop' are required to prevent dropping of files.
* This will not prevent extensions from receiving drop events.
*/
window.addEventListener('dragover', (event) => {
if (event.dataTransfer.files.length > 0) {
event.preventDefault();
}
}, false);
window.addEventListener('drop', (event) => {
if (event.dataTransfer.files.length > 0) {
event.preventDefault();
this.application.alertService.alert({
text: STRING_DEFAULT_FILE_ERROR
});
}
}, false);
}
async handleAutoSignInFromParams() {
const params = this.$location.search();
const server = params.server;
const email = params.email;
const password = params.pw;
if (!server || !email || !password) return;
const user = this.application.getUser();
if (user) {
if (user.email === email && await this.application.getHost() === server) {
/** Already signed in, return */
return;
} else {
/** Sign out */
await this.application.signOut();
await this.application.restart();
}
}
await this.application.setHost(server);
this.application.signIn({
email: email,
password: password,
});
reload() {
this.applications = this.applicationManager.getApplications();
}
}

View File

@@ -1,6 +1,6 @@
import { SNNote, SNSmartTag, ContentTypes, ApplicationEvents } from 'snjs';
import template from '%/tags.pug';
import { AppStateEvents } from '@/state';
import { AppStateEvents } from '@/services/state';
import { PANEL_NAME_TAGS } from '@/controllers/constants';
import { PrefKeys } from '@/services/preferencesManager';
import { STRING_DELETE_TAG } from '@/strings';
@@ -9,16 +9,9 @@ import { PureCtrl } from '@Controllers';
class TagsPanelCtrl extends PureCtrl {
/* @ngInject */
constructor(
$scope,
$rootScope,
$timeout,
application,
appState,
preferencesManager
) {
super($scope, $timeout, application, appState);
this.$rootScope = $rootScope;
this.preferencesManager = preferencesManager;
super($timeout);
this.panelPuppet = {
onReady: () => this.loadPreferences()
};
@@ -92,7 +85,7 @@ class TagsPanelCtrl extends PureCtrl {
this.loadPreferences();
} else if (eventName === AppStateEvents.TagChanged) {
this.setState({
selectedTag: this.appState.getSelectedTag()
selectedTag: this.application.getAppState().getSelectedTag()
});
}
}
@@ -136,11 +129,11 @@ class TagsPanelCtrl extends PureCtrl {
if (!this.panelPuppet.ready) {
return;
}
const width = this.preferencesManager.getValue(PrefKeys.TagsPanelWidth);
const width = this.application.getPrefsService().getValue(PrefKeys.TagsPanelWidth);
if (width) {
this.panelPuppet.setWidth(width);
if (this.panelPuppet.isCollapsed()) {
this.appState.panelDidResize({
this.application.getAppState().panelDidResize({
name: PANEL_NAME_TAGS,
collapsed: this.panelPuppet.isCollapsed()
});
@@ -149,12 +142,12 @@ class TagsPanelCtrl extends PureCtrl {
}
onPanelResize = (newWidth, lastLeft, isAtMaxWidth, isCollapsed) => {
this.preferencesManager.setUserPrefValue(
this.application.getPrefsService().setUserPrefValue(
PrefKeys.TagsPanelWidth,
newWidth,
true
);
this.appState.panelDidResize({
this.application.getAppState().panelDidResize({
name: PANEL_NAME_TAGS,
collapsed: isCollapsed
});
@@ -202,7 +195,7 @@ class TagsPanelCtrl extends PureCtrl {
tag.content.conflict_of = null;
this.application.saveItem({ item: tag });
}
this.appState.setSelectedTag(tag);
this.application.getAppState().setSelectedTag(tag);
}
async clickedAddNewTag() {
@@ -299,7 +292,9 @@ class TagsPanelCtrl extends PureCtrl {
export class TagsPanel {
constructor() {
this.restrict = 'E';
this.scope = {};
this.scope = {
application: '='
};
this.template = template;
this.replace = true;
this.controller = TagsPanelCtrl;