Footer TS

This commit is contained in:
Mo Bitar
2020-04-12 12:41:49 -05:00
parent 3542cba002
commit 956c9a1814
11 changed files with 331 additions and 325 deletions

View File

@@ -1,7 +1,8 @@
import { PanelPuppet, WebDirective, PermissionsModalScope, ModalComponentScope } from './../types';
import { getPlatformString } from '@/utils'; import { getPlatformString } from '@/utils';
import template from '%/application-view.pug'; import template from '%/application-view.pug';
import { AppStateEvent } from '@/services/state'; import { AppStateEvent } from '@/services/state';
import { ApplicationEvent } from 'snjs'; import { ApplicationEvent, SNComponent } from 'snjs';
import angular from 'angular'; import angular from 'angular';
import { import {
PANEL_NAME_NOTES, PANEL_NAME_NOTES,
@@ -12,14 +13,27 @@ import {
STRING_DEFAULT_FILE_ERROR STRING_DEFAULT_FILE_ERROR
} from '@/strings'; } from '@/strings';
import { PureCtrl } from './abstract/pure_ctrl'; import { PureCtrl } from './abstract/pure_ctrl';
import { PermissionDialog } from '@/../../../../snjs/dist/@types/services/component_manager';
class ApplicationViewCtrl extends PureCtrl { class ApplicationViewCtrl extends PureCtrl {
private $compile?: ng.ICompileService
private $location?: ng.ILocationService
private $rootScope?: ng.IRootScopeService
public platformString: string
private completedInitialSync = false
private syncStatus: any
private notesCollapsed = false
private tagsCollapsed = false
private showingDownloadStatus = false
private uploadSyncStatus: any
private lastAlertShownDate?: Date
/* @ngInject */ /* @ngInject */
constructor( constructor(
$compile, $compile: ng.ICompileService,
$location, $location: ng.ILocationService,
$rootScope, $rootScope: ng.IRootScopeService,
$timeout $timeout: ng.ITimeoutService
) { ) {
super($timeout); super($timeout);
this.$location = $location; this.$location = $location;
@@ -35,17 +49,16 @@ class ApplicationViewCtrl extends PureCtrl {
} }
deinit() { deinit() {
this.$location = null; this.$location = undefined;
this.$rootScope = null; this.$rootScope = undefined;
this.$compile = null; this.$compile = undefined;
this.application = null; this.application = undefined;
this.lockScreenPuppet = null;
window.removeEventListener('dragover', this.onDragOver, true); window.removeEventListener('dragover', this.onDragOver, true);
window.removeEventListener('drop', this.onDragDrop, true); window.removeEventListener('drop', this.onDragDrop, true);
this.onDragDrop = null; (this.onDragDrop as any) = undefined;
this.onDragOver = null; (this.onDragOver as any) = undefined;
this.openModalComponent = null; (this.openModalComponent as any) = undefined;
this.presentPermissionsDialog = null; (this.presentPermissionsDialog as any) = undefined;
super.deinit(); super.deinit();
} }
@@ -55,41 +68,39 @@ class ApplicationViewCtrl extends PureCtrl {
} }
async loadApplication() { async loadApplication() {
await this.application.prepareForLaunch({ await this.application!.prepareForLaunch({
callbacks: {
receiveChallenge: async (challenge, orchestrator) => { receiveChallenge: async (challenge, orchestrator) => {
this.application.promptForChallenge(challenge, orchestrator); this.application!.promptForChallenge(challenge, orchestrator);
}
} }
}); });
await this.application.launch(); await this.application!.launch();
} }
onAppStart() { async onAppStart() {
super.onAppStart(); super.onAppStart();
this.overrideComponentManagerFunctions(); this.overrideComponentManagerFunctions();
this.application.componentManager.setDesktopManager( this.application!.componentManager!.setDesktopManager(
this.application.getDesktopService() this.application!.getDesktopService()
); );
this.setState({ this.setState({
ready: true, ready: true,
needsUnlock: this.application.hasPasscode() needsUnlock: this.application!.hasPasscode()
}); });
} }
onAppLaunch() { async onAppLaunch() {
super.onAppLaunch(); super.onAppLaunch();
this.setState({ needsUnlock: false }); this.setState({ needsUnlock: false });
this.handleAutoSignInFromParams(); this.handleAutoSignInFromParams();
} }
onUpdateAvailable() { onUpdateAvailable() {
this.$rootScope.$broadcast('new-update-available'); this.$rootScope!.$broadcast('new-update-available');
}; };
/** @override */ /** @override */
async onAppEvent(eventName) { async onAppEvent(eventName: ApplicationEvent) {
super.onAppEvent(eventName); super.onAppEvent(eventName);
if (eventName === ApplicationEvent.LocalDataIncrementalLoad) { if (eventName === ApplicationEvent.LocalDataIncrementalLoad) {
this.updateLocalDataStatus(); this.updateLocalDataStatus();
@@ -102,31 +113,31 @@ class ApplicationViewCtrl extends PureCtrl {
this.updateLocalDataStatus(); this.updateLocalDataStatus();
} else if (eventName === ApplicationEvent.WillSync) { } else if (eventName === ApplicationEvent.WillSync) {
if (!this.completedInitialSync) { if (!this.completedInitialSync) {
this.syncStatus = this.application.getStatusService().replaceStatusWithString( this.syncStatus = this.application!.getStatusService().replaceStatusWithString(
this.syncStatus, this.syncStatus,
"Syncing..." "Syncing..."
); );
} }
} else if (eventName === ApplicationEvent.CompletedSync) { } else if (eventName === ApplicationEvent.CompletedSync) {
if (!this.completedInitialSync) { if (!this.completedInitialSync) {
this.syncStatus = this.application.getStatusService().removeStatus(this.syncStatus); this.syncStatus = this.application!.getStatusService().removeStatus(this.syncStatus);
this.completedInitialSync = true; this.completedInitialSync = true;
} }
} else if (eventName === ApplicationEvent.InvalidSyncSession) { } else if (eventName === ApplicationEvent.InvalidSyncSession) {
this.showInvalidSessionAlert(); this.showInvalidSessionAlert();
} else if (eventName === ApplicationEvent.LocalDatabaseReadError) { } else if (eventName === ApplicationEvent.LocalDatabaseReadError) {
this.application.alertService.alert({ this.application!.alertService!.alert(
text: 'Unable to load local database. Please restart the app and try again.' 'Unable to load local database. Please restart the app and try again.'
}); );
} else if (eventName === ApplicationEvent.LocalDatabaseWriteError) { } else if (eventName === ApplicationEvent.LocalDatabaseWriteError) {
this.application.alertService.alert({ this.application!.alertService!.alert(
text: 'Unable to write to local database. Please restart the app and try again.' 'Unable to write to local database. Please restart the app and try again.'
}); );
} }
} }
/** @override */ /** @override */
async onAppStateEvent(eventName, data) { async onAppStateEvent(eventName: AppStateEvent, data?: any) {
if (eventName === AppStateEvent.PanelResized) { if (eventName === AppStateEvent.PanelResized) {
if (data.panel === PANEL_NAME_NOTES) { if (data.panel === PANEL_NAME_NOTES) {
this.notesCollapsed = data.collapsed; this.notesCollapsed = data.collapsed;
@@ -139,41 +150,41 @@ class ApplicationViewCtrl extends PureCtrl {
if (this.tagsCollapsed) { appClass += " collapsed-tags"; } if (this.tagsCollapsed) { appClass += " collapsed-tags"; }
this.setState({ appClass }); this.setState({ appClass });
} else if (eventName === AppStateEvent.WindowDidFocus) { } else if (eventName === AppStateEvent.WindowDidFocus) {
if (!(await this.application.isLocked())) { if (!(await this.application!.isLocked())) {
this.application.sync(); this.application!.sync();
} }
} }
} }
updateLocalDataStatus() { updateLocalDataStatus() {
const syncStatus = this.application.getSyncStatus(); const syncStatus = this.application!.getSyncStatus();
const stats = syncStatus.getStats(); const stats = syncStatus.getStats();
const encryption = this.application.isEncryptionAvailable(); const encryption = this.application!.isEncryptionAvailable();
if (stats.localDataDone) { if (stats.localDataDone) {
this.syncStatus = this.application.getStatusService().removeStatus(this.syncStatus); this.syncStatus = this.application!.getStatusService().removeStatus(this.syncStatus);
return; return;
} }
const notesString = `${stats.localDataCurrent}/${stats.localDataTotal} items...`; const notesString = `${stats.localDataCurrent}/${stats.localDataTotal} items...`;
const loadingStatus = encryption const loadingStatus = encryption
? `Decrypting ${notesString}` ? `Decrypting ${notesString}`
: `Loading ${notesString}`; : `Loading ${notesString}`;
this.syncStatus = this.application.getStatusService().replaceStatusWithString( this.syncStatus = this.application!.getStatusService().replaceStatusWithString(
this.syncStatus, this.syncStatus,
loadingStatus loadingStatus
); );
} }
updateSyncStatus() { updateSyncStatus() {
const syncStatus = this.application.getSyncStatus(); const syncStatus = this.application!.getSyncStatus();
const stats = syncStatus.getStats(); const stats = syncStatus.getStats();
if (syncStatus.hasError()) { if (syncStatus.hasError()) {
this.syncStatus = this.application.getStatusService().replaceStatusWithString( this.syncStatus = this.application!.getStatusService().replaceStatusWithString(
this.syncStatus, this.syncStatus,
'Unable to Sync' 'Unable to Sync'
); );
} else if (stats.downloadCount > 20) { } else if (stats.downloadCount > 20) {
const text = `Downloading ${stats.downloadCount} items. Keep app open.`; const text = `Downloading ${stats.downloadCount} items. Keep app open.`;
this.syncStatus = this.application.getStatusService().replaceStatusWithString( this.syncStatus = this.application!.getStatusService().replaceStatusWithString(
this.syncStatus, this.syncStatus,
text text
); );
@@ -181,56 +192,63 @@ class ApplicationViewCtrl extends PureCtrl {
} else if (this.showingDownloadStatus) { } else if (this.showingDownloadStatus) {
this.showingDownloadStatus = false; this.showingDownloadStatus = false;
const text = "Download Complete."; const text = "Download Complete.";
this.syncStatus = this.application.getStatusService().replaceStatusWithString( this.syncStatus = this.application!.getStatusService().replaceStatusWithString(
this.syncStatus, this.syncStatus,
text text
); );
setTimeout(() => { setTimeout(() => {
this.syncStatus = this.application.getStatusService().removeStatus(this.syncStatus); this.syncStatus = this.application!.getStatusService().removeStatus(this.syncStatus);
}, 2000); }, 2000);
} else if (stats.uploadTotalCount > 20) { } else if (stats.uploadTotalCount > 20) {
this.uploadSyncStatus = this.application.getStatusService().replaceStatusWithString( this.uploadSyncStatus = this.application!.getStatusService().replaceStatusWithString(
this.uploadSyncStatus, this.uploadSyncStatus,
`Syncing ${stats.uploadCompletionCount}/${stats.uploadTotalCount} items...` `Syncing ${stats.uploadCompletionCount}/${stats.uploadTotalCount} items...`
); );
} else if (this.uploadSyncStatus) { } else if (this.uploadSyncStatus) {
this.uploadSyncStatus = this.application.getStatusService().removeStatus( this.uploadSyncStatus = this.application!.getStatusService().removeStatus(
this.uploadSyncStatus this.uploadSyncStatus
); );
} }
} }
openModalComponent(component) { openModalComponent(component: SNComponent) {
const scope = this.$rootScope.$new(true); const scope = this.$rootScope!.$new(true) as ModalComponentScope;
scope.component = component; scope.component = component;
const el = this.$compile("<component-modal component='component' class='sk-modal'></component-modal>")(scope); const el = this.$compile!(
"<component-modal component='component' class='sk-modal'></component-modal>"
)(scope as any);
angular.element(document.body).append(el); angular.element(document.body).append(el);
} }
presentPermissionsDialog(dialog) { presentPermissionsDialog(dialog: PermissionDialog) {
const scope = this.$rootScope.$new(true); const scope = this.$rootScope!.$new(true) as PermissionsModalScope;
scope.permissionsString = dialog.permissionsString; scope.permissionsString = dialog.permissionsString;
scope.component = dialog.component; scope.component = dialog.component;
scope.callback = dialog.callback; scope.callback = dialog.callback;
const el = this.$compile("<permissions-modal component='component' permissions-string='permissionsString' callback='callback' class='sk-modal'></permissions-modal>")(scope); const el = this.$compile!(
"<permissions-modal component='component' permissions-string='permissionsString'"
+ " callback='callback' class='sk-modal'></permissions-modal>"
)(scope as any);
angular.element(document.body).append(el); angular.element(document.body).append(el);
} }
overrideComponentManagerFunctions() { overrideComponentManagerFunctions() {
this.application.componentManager.openModalComponent = this.openModalComponent; this.application!.componentManager!.openModalComponent = this.openModalComponent;
this.application.componentManager.presentPermissionsDialog = this.presentPermissionsDialog; this.application!.componentManager!.presentPermissionsDialog = this.presentPermissionsDialog;
} }
showInvalidSessionAlert() { showInvalidSessionAlert() {
/** Don't show repeatedly; at most 30 seconds in between */ /** Don't show repeatedly; at most 30 seconds in between */
const SHOW_INTERVAL = 30; const SHOW_INTERVAL = 30;
const lastShownSeconds = (new Date() - this.lastShownDate) / 1000; if (
if (!this.lastShownDate || lastShownSeconds > SHOW_INTERVAL) { !this.lastAlertShownDate ||
this.lastShownDate = new Date(); (new Date().getTime() - this.lastAlertShownDate!.getTime()) / 1000 > SHOW_INTERVAL
) {
this.lastAlertShownDate = new Date();
setTimeout(() => { setTimeout(() => {
this.application.alertService.alert({ this.application!.alertService!.alert(
text: STRING_SESSION_EXPIRED STRING_SESSION_EXPIRED
}); );
}, 500); }, 500);
} }
} }
@@ -245,49 +263,49 @@ class ApplicationViewCtrl extends PureCtrl {
window.addEventListener('drop', this.onDragDrop, true); window.addEventListener('drop', this.onDragDrop, true);
} }
onDragOver(event) { onDragOver(event: DragEvent) {
if (event.dataTransfer.files.length > 0) { if (event.dataTransfer!.files.length > 0) {
event.preventDefault(); event.preventDefault();
} }
} }
onDragDrop(event) { onDragDrop(event: DragEvent) {
if (event.dataTransfer.files.length > 0) { if (event.dataTransfer!.files.length > 0) {
event.preventDefault(); event.preventDefault();
this.application.alertService.alert({ this.application!.alertService!.alert(
text: STRING_DEFAULT_FILE_ERROR STRING_DEFAULT_FILE_ERROR
}); );
} }
} }
async handleAutoSignInFromParams() { async handleAutoSignInFromParams() {
const params = this.$location.search(); const params = this.$location!.search();
const server = params.server; const server = params.server;
const email = params.email; const email = params.email;
const password = params.pw; const password = params.pw;
if (!server || !email || !password) return; if (!server || !email || !password) return;
const user = this.application.getUser(); const user = this.application!.getUser();
if (user) { if (user) {
if (user.email === email && await this.application.getHost() === server) { if (user.email === email && await this.application!.getHost() === server) {
/** Already signed in, return */ /** Already signed in, return */
return; return;
} else { } else {
/** Sign out */ /** Sign out */
await this.application.signOut(); await this.application!.signOut();
await this.application.restart();
} }
} }
await this.application.setHost(server); await this.application!.setHost(server);
this.application.signIn({ this.application!.signIn(
email: email, email,
password: password, password,
}); );
} }
} }
export class ApplicationView { export class ApplicationView extends WebDirective {
constructor() { constructor() {
super();
this.template = template; this.template = template;
this.controller = ApplicationViewCtrl; this.controller = ApplicationViewCtrl;
this.replace = true; this.replace = true;

View File

@@ -1,9 +1,14 @@
import { FooterStatus, WebDirective } from './../types';
import { dateToLocalizedString } from '@/utils'; import { dateToLocalizedString } from '@/utils';
import { import {
ApplicationEvent, ApplicationEvent,
TIMING_STRATEGY_FORCE_SPAWN_NEW, SyncQueueStrategy,
ProtectedAction, ProtectedAction,
ContentTypes ContentType,
SNComponent,
SNTheme,
ComponentArea,
ComponentAction
} from 'snjs'; } from 'snjs';
import template from '%/footer.pug'; import template from '%/footer.pug';
import { AppStateEvent, EventSource } from '@/services/state'; import { AppStateEvent, EventSource } from '@/services/state';
@@ -11,40 +16,71 @@ import {
STRING_GENERIC_SYNC_ERROR, STRING_GENERIC_SYNC_ERROR,
STRING_NEW_UPDATE_READY STRING_NEW_UPDATE_READY
} from '@/strings'; } from '@/strings';
import { PureCtrl } from '@Controllers'; import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
import { ComponentMutator } from '@/../../../../snjs/dist/@types/models';
type DockShortcut = {
name: string,
component: SNComponent,
icon: {
type: string
background_color: string
border_color: string
}
}
class FooterCtrl extends PureCtrl { class FooterCtrl extends PureCtrl {
private $rootScope: ng.IRootScopeService
private rooms: SNComponent[] = []
private themesWithIcons: SNTheme[] = []
private showSyncResolution = false
private unregisterComponent: any
private rootScopeListener1: any
private rootScopeListener2: any
public arbitraryStatusMessage?: string
public user?: any
private backupStatus?: FooterStatus
private offline = true
private showAccountMenu = false
private queueExtReload = false
private reloadInProgress = false
public hasError = false
public isRefreshing = false
public lastSyncDate?: string
public newUpdateAvailable = false
public dockShortcuts: DockShortcut[] = []
public roomShowState: Partial<Record<string, boolean>> = {}
/* @ngInject */ /* @ngInject */
constructor( constructor(
$rootScope, $rootScope: ng.IRootScopeService,
$timeout, $timeout: ng.ITimeoutService,
) { ) {
super($timeout); super($timeout);
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.rooms = [];
this.themesWithIcons = [];
this.showSyncResolution = false;
this.addRootScopeListeners(); this.addRootScopeListeners();
this.toggleSyncResolutionMenu = this.toggleSyncResolutionMenu.bind(this);
this.closeAccountMenu = this.closeAccountMenu.bind(this);
} }
deinit() { deinit() {
this.rooms.length = 0; this.rooms.length = 0;
this.themesWithIcons.length = 0; this.themesWithIcons.length = 0;
this.unregisterComponent(); this.unregisterComponent();
this.unregisterComponent = null; this.unregisterComponent = undefined;
this.rootScopeListener1(); this.rootScopeListener1();
this.rootScopeListener2(); this.rootScopeListener2();
this.rootScopeListener1 = null; this.rootScopeListener1 = undefined;
this.rootScopeListener2 = null; this.rootScopeListener2 = undefined;
this.closeAccountMenu = null; (this.closeAccountMenu as any) = undefined;
this.toggleSyncResolutionMenu = null; (this.toggleSyncResolutionMenu as any) = undefined;
super.deinit(); super.deinit();
} }
$onInit() { $onInit() {
super.$onInit(); super.$onInit();
this.application.getStatusService().addStatusObserver((string) => { this.application!.getStatusService().addStatusObserver((string: string) => {
this.$timeout(() => { this.$timeout(() => {
this.arbitraryStatusMessage = string; this.arbitraryStatusMessage = string;
}); });
@@ -58,18 +94,18 @@ class FooterCtrl extends PureCtrl {
} }
reloadUpgradeStatus() { reloadUpgradeStatus() {
this.application.checkForSecurityUpdate().then((available) => { this.application!.checkForSecurityUpdate().then((available) => {
this.setState({ this.setState({
dataUpgradeAvailable: available dataUpgradeAvailable: available
}); });
}); });
} }
onAppLaunch() { async onAppLaunch() {
super.onAppLaunch(); super.onAppLaunch();
this.reloadPasscodeStatus(); this.reloadPasscodeStatus();
this.reloadUpgradeStatus(); this.reloadUpgradeStatus();
this.user = this.application.getUser(); this.user = this.application!.getUser();
this.updateOfflineStatus(); this.updateOfflineStatus();
this.findErrors(); this.findErrors();
this.streamItems(); this.streamItems();
@@ -77,7 +113,7 @@ class FooterCtrl extends PureCtrl {
} }
async reloadPasscodeStatus() { async reloadPasscodeStatus() {
const hasPasscode = this.application.hasPasscode(); const hasPasscode = this.application!.hasPasscode();
this.setState({ this.setState({
hasPasscode: hasPasscode hasPasscode: hasPasscode
}); });
@@ -95,30 +131,30 @@ class FooterCtrl extends PureCtrl {
} }
/** @override */ /** @override */
onAppStateEvent(eventName, data) { onAppStateEvent(eventName: AppStateEvent, data: any) {
if (eventName === AppStateEvent.EditorFocused) { if (eventName === AppStateEvent.EditorFocused) {
if (data.eventSource === EventSource.UserInteraction) { if (data.eventSource === EventSource.UserInteraction) {
this.closeAllRooms(); this.closeAllRooms();
this.closeAccountMenu(); this.closeAccountMenu();
} }
} else if (eventName === AppStateEvent.BeganBackupDownload) { } else if (eventName === AppStateEvent.BeganBackupDownload) {
this.backupStatus = this.application.getStatusService().addStatusFromString( this.backupStatus = this.application!.getStatusService().addStatusFromString(
"Saving local backup..." "Saving local backup..."
); );
} else if (eventName === AppStateEvent.EndedBackupDownload) { } else if (eventName === AppStateEvent.EndedBackupDownload) {
if (data.success) { if (data.success) {
this.backupStatus = this.application.getStatusService().replaceStatusWithString( this.backupStatus = this.application!.getStatusService().replaceStatusWithString(
this.backupStatus, this.backupStatus!,
"Successfully saved backup." "Successfully saved backup."
); );
} else { } else {
this.backupStatus = this.application.getStatusService().replaceStatusWithString( this.backupStatus = this.application!.getStatusService().replaceStatusWithString(
this.backupStatus, this.backupStatus!,
"Unable to save local backup." "Unable to save local backup."
); );
} }
this.$timeout(() => { this.$timeout(() => {
this.backupStatus = this.application.getStatusService().removeStatus(this.backupStatus); this.backupStatus = this.application!.getStatusService().removeStatus(this.backupStatus!);
}, 2000); }, 2000);
} }
} }
@@ -131,7 +167,7 @@ class FooterCtrl extends PureCtrl {
/** @override */ /** @override */
onAppEvent(eventName) { onAppEvent(eventName: ApplicationEvent) {
if (eventName === ApplicationEvent.KeyStatusChanged) { if (eventName === ApplicationEvent.KeyStatusChanged) {
this.reloadUpgradeStatus(); this.reloadUpgradeStatus();
} else if (eventName === ApplicationEvent.EnteredOutOfSync) { } else if (eventName === ApplicationEvent.EnteredOutOfSync) {
@@ -143,7 +179,7 @@ class FooterCtrl extends PureCtrl {
outOfSync: false outOfSync: false
}); });
} else if (eventName === ApplicationEvent.CompletedSync) { } else if (eventName === ApplicationEvent.CompletedSync) {
if (this.offline && this.application.getNoteCount() === 0) { if (this.offline && this.application!.getNoteCount() === 0) {
this.showAccountMenu = true; this.showAccountMenu = true;
} }
this.syncUpdated(); this.syncUpdated();
@@ -156,52 +192,53 @@ class FooterCtrl extends PureCtrl {
} }
streamItems() { streamItems() {
this.application.streamItems({ this.application!.streamItems(
contentType: ContentType.Component, ContentType.Component,
stream: async () => { async () => {
this.rooms = this.application.getItems({ const components = this.application!.getItems(ContentType.Component) as SNComponent[];
contentType: ContentType.Component this.rooms = components.filter((candidate) => {
}).filter((candidate) => { return candidate.area === ComponentArea.Rooms && !candidate.deleted;
return candidate.area === 'rooms' && !candidate.deleted;
}); });
if (this.queueExtReload) { if (this.queueExtReload) {
this.queueExtReload = false; this.queueExtReload = false;
this.reloadExtendedData(); this.reloadExtendedData();
} }
} }
}); );
this.application.streamItems({ this.application!.streamItems(
contentType: 'SN|Theme', ContentType.Theme,
stream: async () => { async () => {
const themes = this.application.getDisplayableItems({ const themes = this.application!.getDisplayableItems(ContentType.Theme) as SNTheme[];
contentType: ContentType.Theme const filteredThemes = themes.filter((candidate) => {
}).filter((candidate) => {
return ( return (
!candidate.deleted && !candidate.deleted &&
candidate.content.package_info && candidate.package_info &&
candidate.content.package_info.dock_icon candidate.package_info.dock_icon
); );
}).sort((a, b) => { }).sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
}); });
const differ = themes.length !== this.themesWithIcons.length; const differ = filteredThemes.length !== this.themesWithIcons.length;
this.themesWithIcons = themes; this.themesWithIcons = filteredThemes;
if (differ) { if (differ) {
this.reloadDockShortcuts(); this.reloadDockShortcuts();
} }
} }
}); );
} }
registerComponentHandler() { registerComponentHandler() {
this.unregisterComponent = this.application.componentManager.registerHandler({ this.unregisterComponent = this.application!.componentManager!.registerHandler({
identifier: "roomBar", identifier: 'room-bar',
areas: ["rooms", "modal"], areas: [ComponentArea.Rooms, ComponentArea.Modal],
activationHandler: (component) => { }, activationHandler: () => { },
actionHandler: (component, action, data) => { actionHandler: (component, action, data) => {
if (action === "set-size") { if (action === ComponentAction.SetSize) {
component.setLastSize(data); this.application!.changeItem(component.uuid, (m) => {
const mutator = m as ComponentMutator;
mutator.setLastSize(data);
})
} }
}, },
focusHandler: (component, focused) => { focusHandler: (component, focused) => {
@@ -224,7 +261,8 @@ class FooterCtrl extends PureCtrl {
* then closing it after a short delay. * then closing it after a short delay.
*/ */
const extWindow = this.rooms.find((room) => { const extWindow = this.rooms.find((room) => {
return room.package_info.identifier === this.application.getNativeExtService().extManagerId; return room.package_info.identifier === this.application!
.getNativeExtService().extManagerId;
}); });
if (!extWindow) { if (!extWindow) {
this.queueExtReload = true; this.queueExtReload = true;
@@ -240,15 +278,15 @@ class FooterCtrl extends PureCtrl {
} }
updateOfflineStatus() { updateOfflineStatus() {
this.offline = this.application.noAccount(); this.offline = this.application!.noAccount();
} }
openSecurityUpdate() { openSecurityUpdate() {
this.application.performProtocolUpgrade(); this.application!.performProtocolUpgrade();
} }
findErrors() { findErrors() {
this.error = this.application.getSyncStatus().error; this.hasError = this.application!.getSyncStatus().hasError();
} }
accountMenuPressed() { accountMenuPressed() {
@@ -256,31 +294,31 @@ class FooterCtrl extends PureCtrl {
this.closeAllRooms(); this.closeAllRooms();
} }
toggleSyncResolutionMenu = () => { toggleSyncResolutionMenu() {
this.showSyncResolution = !this.showSyncResolution; this.showSyncResolution = !this.showSyncResolution;
} }
closeAccountMenu = () => { closeAccountMenu() {
this.showAccountMenu = false; this.showAccountMenu = false;
} }
lockApp() { lockApp() {
this.application.lock(); this.application!.lock();
} }
refreshData() { refreshData() {
this.isRefreshing = true; this.isRefreshing = true;
this.application.sync({ this.application!.sync({
timingStrategy: TIMING_STRATEGY_FORCE_SPAWN_NEW, queueStrategy: SyncQueueStrategy.ForceSpawnNew,
checkIntegrity: true checkIntegrity: true
}).then((response) => { }).then((response) => {
this.$timeout(() => { this.$timeout(() => {
this.isRefreshing = false; this.isRefreshing = false;
}, 200); }, 200);
if (response && response.error) { if (response && response.error) {
this.application.alertService.alert({ this.application!.alertService!.alert(
text: STRING_GENERIC_SYNC_ERROR STRING_GENERIC_SYNC_ERROR
}); );
} else { } else {
this.syncUpdated(); this.syncUpdated();
} }
@@ -288,7 +326,7 @@ class FooterCtrl extends PureCtrl {
} }
syncUpdated() { syncUpdated() {
this.lastSyncDate = dateToLocalizedString(this.application.getLastSyncDate()); this.lastSyncDate = dateToLocalizedString(this.application!.getLastSyncDate()!);
} }
onNewUpdateAvailable() { onNewUpdateAvailable() {
@@ -297,16 +335,16 @@ class FooterCtrl extends PureCtrl {
clickedNewUpdateAnnouncement() { clickedNewUpdateAnnouncement() {
this.newUpdateAvailable = false; this.newUpdateAvailable = false;
this.application.alertService.alert({ this.application!.alertService!.alert(
text: STRING_NEW_UPDATE_READY STRING_NEW_UPDATE_READY
}); );
} }
reloadDockShortcuts() { reloadDockShortcuts() {
const shortcuts = []; const shortcuts = [];
for (const theme of this.themesWithIcons) { for (const theme of this.themesWithIcons) {
const name = theme.content.package_info.name; const name = theme.package_info.name;
const icon = theme.content.package_info.dock_icon; const icon = theme.package_info.dock_icon;
if (!icon) { if (!icon) {
continue; continue;
} }
@@ -314,7 +352,7 @@ class FooterCtrl extends PureCtrl {
name: name, name: name,
component: theme, component: theme,
icon: icon icon: icon
}); } as DockShortcut);
} }
this.dockShortcuts = shortcuts.sort((a, b) => { this.dockShortcuts = shortcuts.sort((a, b) => {
@@ -327,46 +365,49 @@ class FooterCtrl extends PureCtrl {
return -1; return -1;
} else if (bType === 'circle' && aType === 'svg') { } else if (bType === 'circle' && aType === 'svg') {
return 1; return 1;
} else {
return 0;
} }
}); });
} }
initSvgForShortcut(shortcut) { initSvgForShortcut(shortcut: DockShortcut) {
const id = 'dock-svg-' + shortcut.component.uuid; const id = 'dock-svg-' + shortcut.component.uuid;
const element = document.getElementById(id); const element = document.getElementById(id)!;
const parser = new DOMParser(); const parser = new DOMParser();
const svg = shortcut.component.content.package_info.dock_icon.source; const svg = shortcut.component.package_info.dock_icon.source;
const doc = parser.parseFromString(svg, 'image/svg+xml'); const doc = parser.parseFromString(svg, 'image/svg+xml');
element.appendChild(doc.documentElement); element.appendChild(doc.documentElement);
} }
selectShortcut(shortcut) { selectShortcut(shortcut: DockShortcut) {
this.application.componentManager.toggleComponent(shortcut.component); this.application!.componentManager!.toggleComponent(shortcut.component);
} }
onRoomDismiss(room) { onRoomDismiss(room: SNComponent) {
room.showRoom = false; this.roomShowState[room.uuid] = false;
} }
closeAllRooms() { closeAllRooms() {
for (const room of this.rooms) { for (const room of this.rooms) {
room.showRoom = false; this.roomShowState[room.uuid] = false;
} }
} }
async selectRoom(room) { async selectRoom(room: SNComponent) {
const run = () => { const run = () => {
this.$timeout(() => { this.$timeout(() => {
room.showRoom = !room.showRoom; this.roomShowState[room.uuid] = !this.roomShowState[room.uuid];
}); });
}; };
if (!room.showRoom) { if (!this.roomShowState[room.uuid]) {
const requiresPrivilege = await this.application.privilegesService.actionRequiresPrivilege( const requiresPrivilege = await this.application!.privilegesService!
.actionRequiresPrivilege(
ProtectedAction.ManageExtensions ProtectedAction.ManageExtensions
); );
if (requiresPrivilege) { if (requiresPrivilege) {
this.application.presentPrivilegesModal( this.application!.presentPrivilegesModal(
ProtectedAction.ManageExtensions, ProtectedAction.ManageExtensions,
run run
); );
@@ -379,15 +420,16 @@ class FooterCtrl extends PureCtrl {
} }
clickOutsideAccountMenu() { clickOutsideAccountMenu() {
if (this.application && this.application.authenticationInProgress()) { if (this.application && this.application!.authenticationInProgress()) {
return; return;
} }
this.showAccountMenu = false; this.showAccountMenu = false;
} }
} }
export class Footer { export class Footer extends WebDirective {
constructor() { constructor() {
super();
this.restrict = 'E'; this.restrict = 'E';
this.template = template; this.template = template;
this.controller = FooterCtrl; this.controller = FooterCtrl;

View File

@@ -1,78 +0,0 @@
import template from '%/lock-screen.pug';
import { AppStateEvent } from '@/services/state';
import { PureCtrl } from './abstract/pure_ctrl';
const ELEMENT_ID_PASSCODE_INPUT = 'passcode-input';
class LockScreenCtrl extends PureCtrl {
/* @ngInject */
constructor(
$timeout,
) {
super($timeout);
this.formData = {};
}
$onInit() {
super.$onInit();
this.puppet.focusInput = () => {
this.passcodeInput.focus();
};
}
get passcodeInput() {
return document.getElementById(
ELEMENT_ID_PASSCODE_INPUT
);
}
/** @override */
async onAppStateEvent(eventName, data) {
if (eventName === AppStateEvent.WindowDidFocus) {
const input = this.passcodeInput;
if (input) {
input.focus();
}
}
}
async submitPasscodeForm() {
if (
!this.formData.passcode ||
this.formData.passcode.length === 0
) {
return;
}
this.passcodeInput.blur();
this.onValue()(this.formData.passcode);
}
forgotPasscode() {
this.formData.showRecovery = true;
}
beginDeleteData() {
this.application.alertService.confirm({
text: "Are you sure you want to clear all local data?",
destructive: true,
onConfirm: async () => {
await this.application.signOut();
await this.application.restart();
}
});
}
}
export class LockScreen {
constructor() {
this.restrict = 'E';
this.template = template;
this.controller = LockScreenCtrl;
this.controllerAs = 'ctrl';
this.bindToController = true;
this.scope = {
onValue: '&',
puppet: '='
};
}
}

View File

@@ -1,8 +1,16 @@
import { ApplicationManager } from './../applicationManager';
import { WebDirective } from './../types';
import template from '%/root.pug'; import template from '%/root.pug';
import { WebApplication } from '@/application';
class RootCtrl { class RootCtrl {
private $timeout: ng.ITimeoutService
private applicationManager: ApplicationManager
public applications: WebApplication[] = []
/* @ngInject */ /* @ngInject */
constructor($timeout, applicationManager) { constructor($timeout: ng.ITimeoutService, applicationManager: ApplicationManager) {
this.$timeout = $timeout; this.$timeout = $timeout;
this.applicationManager = applicationManager; this.applicationManager = applicationManager;
this.applicationManager.addApplicationChangeObserver(() => { this.applicationManager.addApplicationChangeObserver(() => {
@@ -17,8 +25,9 @@ class RootCtrl {
} }
} }
export class Root { export class Root extends WebDirective {
constructor() { constructor() {
super();
this.template = template; this.template = template;
this.controller = RootCtrl; this.controller = RootCtrl;
this.replace = true; this.replace = true;

View File

@@ -1,5 +1,6 @@
import { WebApplication } from './application'; import { WebApplication } from './application';
import { SNAlertService } from "../../../../snjs/dist/@types"; import { SNAlertService } from "../../../../snjs/dist/@types";
import { RawPayload } from '../../../../snjs/dist/@types/protocol/payloads/generator';
const DB_NAME = 'standardnotes'; const DB_NAME = 'standardnotes';
const STORE_NAME = 'items'; const STORE_NAME = 'items';
@@ -105,7 +106,7 @@ export class Database {
}); });
} }
public async getAllPayloads() { public async getAllPayloads(): Promise<any[]> {
const db = (await this.openDatabase())!; const db = (await this.openDatabase())!;
return new Promise((resolve) => { return new Promise((resolve) => {
const objectStore = const objectStore =
@@ -126,11 +127,11 @@ export class Database {
}); });
} }
public async savePayload(payload: any) { public async savePayload(payload: any): Promise<void> {
return this.savePayloads([payload]); return this.savePayloads([payload]);
} }
public async savePayloads(payloads: any[]) { public async savePayloads(payloads: any[]): Promise<void> {
if (payloads.length === 0) { if (payloads.length === 0) {
return; return;
} }
@@ -157,8 +158,8 @@ export class Database {
}); });
} }
private putItems(objectStore: IDBObjectStore, items: any[]) { private async putItems(objectStore: IDBObjectStore, items: any[]): Promise<void> {
return Promise.all(items.map((item) => { await Promise.all(items.map((item) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const request = objectStore.put(item); const request = objectStore.put(item);
request.onerror = resolve; request.onerror = resolve;
@@ -167,19 +168,21 @@ export class Database {
})); }));
} }
public async deletePayload(uuid: string) { public async deletePayload(uuid: string): Promise<void> {
const db = (await this.openDatabase())!; const db = (await this.openDatabase())!;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const request = const request =
db.transaction(STORE_NAME, READ_WRITE) db.transaction(STORE_NAME, READ_WRITE)
.objectStore(STORE_NAME) .objectStore(STORE_NAME)
.delete(uuid); .delete(uuid);
request.onsuccess = resolve; request.onsuccess = () => {
resolve();
};
request.onerror = reject; request.onerror = reject;
}); });
} }
public async clearAllPayloads() { public async clearAllPayloads(): Promise<void> {
const deleteRequest = window.indexedDB.deleteDatabase(DB_NAME); const deleteRequest = window.indexedDB.deleteDatabase(DB_NAME);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
deleteRequest.onerror = () => { deleteRequest.onerror = () => {
@@ -200,7 +203,7 @@ export class Database {
this.alertService!.alert(message); this.alertService!.alert(message);
} }
private showGenericError(error: {code: number, name: string}) { private showGenericError(error: { code: number, name: string }) {
const message = const message =
`Unable to save changes locally due to an unknown system issue. ` + `Unable to save changes locally due to an unknown system issue. ` +
`Issue Code: ${error.code} Issue Name: ${error.name}.`; `Issue Code: ${error.code} Issue Name: ${error.name}.`;

View File

@@ -1,64 +0,0 @@
import _ from 'lodash';
export class StatusManager {
constructor() {
this.statuses = [];
this.observers = [];
}
statusFromString(string) {
return {string: string};
}
replaceStatusWithString(status, string) {
this.removeStatus(status);
return this.addStatusFromString(string);
}
addStatusFromString(string) {
return this.addStatus(this.statusFromString(string));
}
addStatus(status) {
if(typeof status !== "object") {
console.error("Attempting to set non-object status", status);
return;
}
this.statuses.push(status);
this.notifyObservers();
return status;
}
removeStatus(status) {
_.pull(this.statuses, status);
this.notifyObservers();
return null;
}
getStatusString() {
let result = "";
this.statuses.forEach((status, index) => {
if(index > 0) {
result += " ";
}
result += status.string;
});
return result;
}
notifyObservers() {
for(const observer of this.observers) {
observer(this.getStatusString());
}
}
addStatusObserver(callback) {
this.observers.push(callback);
}
removeStatusObserver(callback) {
_.pull(this.statuses, callback);
}
}

View File

@@ -0,0 +1,60 @@
import { removeFromArray } from 'snjs';
import { FooterStatus } from './../types';
type StatusCallback = (string: string) => void
export class StatusManager {
private statuses: FooterStatus[] = []
private observers: StatusCallback[] = []
statusFromString(string: string) {
return {string: string};
}
replaceStatusWithString(status: FooterStatus, string: string) {
this.removeStatus(status);
return this.addStatusFromString(string);
}
addStatusFromString(string: string) {
return this.addStatus(this.statusFromString(string));
}
addStatus(status: FooterStatus) {
this.statuses.push(status);
this.notifyObservers();
return status;
}
removeStatus(status: FooterStatus) {
removeFromArray(this.statuses, status);
this.notifyObservers();
return undefined;
}
getStatusString() {
let result = '';
this.statuses.forEach((status, index) => {
if(index > 0) {
result += ' ';
}
result += status.string;
});
return result;
}
notifyObservers() {
for(const observer of this.observers) {
observer(this.getStatusString());
}
}
addStatusObserver(callback: StatusCallback) {
this.observers.push(callback);
return () => {
removeFromArray(this.observers, callback);
}
}
}

View File

@@ -1,3 +1,4 @@
import { SNComponent } from 'snjs';
export class WebDirective implements ng.IDirective { export class WebDirective implements ng.IDirective {
controller?: string | ng.Injectable<ng.IControllerConstructor>; controller?: string | ng.Injectable<ng.IControllerConstructor>;
controllerAs?: string; controllerAs?: string;
@@ -18,6 +19,17 @@ export interface PasswordWizardScope extends Partial<ng.IScope> {
application: any application: any
} }
export interface PermissionsModalScope extends Partial<ng.IScope> {
application: any
component: SNComponent
permissionsString: string
callback: (approved: boolean) => void
}
export interface ModalComponentScope extends Partial<ng.IScope> {
component: SNComponent
}
export type PanelPuppet = { export type PanelPuppet = {
onReady?: () => void onReady?: () => void
ready?: boolean ready?: boolean
@@ -26,3 +38,7 @@ export type PanelPuppet = {
isCollapsed?: () => boolean isCollapsed?: () => boolean
flash?: () => void flash?: () => void
} }
export type FooterStatus = {
string: string
}

View File

@@ -8,10 +8,10 @@
) )
.sk-app-bar-item-column .sk-app-bar-item-column
.sk-circle.small( .sk-circle.small(
ng-class="ctrl.error ? 'danger' : (ctrl.user ? 'info' : 'neutral')" ng-class="ctrl.hasError ? 'danger' : (ctrl.user ? 'info' : 'neutral')"
) )
.sk-app-bar-item-column .sk-app-bar-item-column
.sk-label.title(ng-class='{red: ctrl.error}') Account .sk-label.title(ng-class='{red: ctrl.hasError}') Account
account-menu( account-menu(
close-function='ctrl.closeAccountMenu()', close-function='ctrl.closeAccountMenu()',
ng-click='$event.stopPropagation()', ng-click='$event.stopPropagation()',
@@ -31,7 +31,7 @@
.sk-label {{room.name}} .sk-label {{room.name}}
component-modal( component-modal(
component='room', component='room',
ng-if='room.showRoom', ng-if='ctrl.roomShowState[room.uuid]',
on-dismiss='ctrl.onRoomDismiss()', on-dismiss='ctrl.onRoomDismiss()',
application='ctrl.application' application='ctrl.application'
) )