diff --git a/app/assets/javascripts/controllers/applicationView.js b/app/assets/javascripts/controllers/applicationView.ts similarity index 55% rename from app/assets/javascripts/controllers/applicationView.js rename to app/assets/javascripts/controllers/applicationView.ts index 8247e91c3..121b111c1 100644 --- a/app/assets/javascripts/controllers/applicationView.js +++ b/app/assets/javascripts/controllers/applicationView.ts @@ -1,7 +1,8 @@ +import { PanelPuppet, WebDirective, PermissionsModalScope, ModalComponentScope } from './../types'; import { getPlatformString } from '@/utils'; import template from '%/application-view.pug'; import { AppStateEvent } from '@/services/state'; -import { ApplicationEvent } from 'snjs'; +import { ApplicationEvent, SNComponent } from 'snjs'; import angular from 'angular'; import { PANEL_NAME_NOTES, @@ -12,14 +13,27 @@ import { STRING_DEFAULT_FILE_ERROR } from '@/strings'; import { PureCtrl } from './abstract/pure_ctrl'; +import { PermissionDialog } from '@/../../../../snjs/dist/@types/services/component_manager'; 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 */ constructor( - $compile, - $location, - $rootScope, - $timeout + $compile: ng.ICompileService, + $location: ng.ILocationService, + $rootScope: ng.IRootScopeService, + $timeout: ng.ITimeoutService ) { super($timeout); this.$location = $location; @@ -35,17 +49,16 @@ class ApplicationViewCtrl extends PureCtrl { } deinit() { - this.$location = null; - this.$rootScope = null; - this.$compile = null; - this.application = null; - this.lockScreenPuppet = null; + this.$location = undefined; + this.$rootScope = undefined; + this.$compile = undefined; + this.application = undefined; window.removeEventListener('dragover', this.onDragOver, true); window.removeEventListener('drop', this.onDragDrop, true); - this.onDragDrop = null; - this.onDragOver = null; - this.openModalComponent = null; - this.presentPermissionsDialog = null; + (this.onDragDrop as any) = undefined; + (this.onDragOver as any) = undefined; + (this.openModalComponent as any) = undefined; + (this.presentPermissionsDialog as any) = undefined; super.deinit(); } @@ -55,41 +68,39 @@ class ApplicationViewCtrl extends PureCtrl { } async loadApplication() { - await this.application.prepareForLaunch({ - callbacks: { - receiveChallenge: async (challenge, orchestrator) => { - this.application.promptForChallenge(challenge, orchestrator); - } + await this.application!.prepareForLaunch({ + receiveChallenge: async (challenge, orchestrator) => { + this.application!.promptForChallenge(challenge, orchestrator); } }); - await this.application.launch(); + await this.application!.launch(); } - onAppStart() { + async onAppStart() { super.onAppStart(); this.overrideComponentManagerFunctions(); - this.application.componentManager.setDesktopManager( - this.application.getDesktopService() + this.application!.componentManager!.setDesktopManager( + this.application!.getDesktopService() ); this.setState({ ready: true, - needsUnlock: this.application.hasPasscode() + needsUnlock: this.application!.hasPasscode() }); } - onAppLaunch() { + async onAppLaunch() { super.onAppLaunch(); this.setState({ needsUnlock: false }); this.handleAutoSignInFromParams(); } onUpdateAvailable() { - this.$rootScope.$broadcast('new-update-available'); + this.$rootScope!.$broadcast('new-update-available'); }; /** @override */ - async onAppEvent(eventName) { + async onAppEvent(eventName: ApplicationEvent) { super.onAppEvent(eventName); if (eventName === ApplicationEvent.LocalDataIncrementalLoad) { this.updateLocalDataStatus(); @@ -102,31 +113,31 @@ class ApplicationViewCtrl extends PureCtrl { this.updateLocalDataStatus(); } else if (eventName === ApplicationEvent.WillSync) { if (!this.completedInitialSync) { - this.syncStatus = this.application.getStatusService().replaceStatusWithString( + this.syncStatus = this.application!.getStatusService().replaceStatusWithString( this.syncStatus, "Syncing..." ); } } else if (eventName === ApplicationEvent.CompletedSync) { if (!this.completedInitialSync) { - this.syncStatus = this.application.getStatusService().removeStatus(this.syncStatus); + this.syncStatus = this.application!.getStatusService().removeStatus(this.syncStatus); this.completedInitialSync = true; } } else if (eventName === ApplicationEvent.InvalidSyncSession) { this.showInvalidSessionAlert(); } else if (eventName === ApplicationEvent.LocalDatabaseReadError) { - this.application.alertService.alert({ - text: 'Unable to load local database. Please restart the app and try again.' - }); + this.application!.alertService!.alert( + 'Unable to load local database. Please restart the app and try again.' + ); } else if (eventName === ApplicationEvent.LocalDatabaseWriteError) { - this.application.alertService.alert({ - text: 'Unable to write to local database. Please restart the app and try again.' - }); + this.application!.alertService!.alert( + 'Unable to write to local database. Please restart the app and try again.' + ); } } /** @override */ - async onAppStateEvent(eventName, data) { + async onAppStateEvent(eventName: AppStateEvent, data?: any) { if (eventName === AppStateEvent.PanelResized) { if (data.panel === PANEL_NAME_NOTES) { this.notesCollapsed = data.collapsed; @@ -139,41 +150,41 @@ class ApplicationViewCtrl extends PureCtrl { if (this.tagsCollapsed) { appClass += " collapsed-tags"; } this.setState({ appClass }); } else if (eventName === AppStateEvent.WindowDidFocus) { - if (!(await this.application.isLocked())) { - this.application.sync(); + if (!(await this.application!.isLocked())) { + this.application!.sync(); } } } updateLocalDataStatus() { - const syncStatus = this.application.getSyncStatus(); + const syncStatus = this.application!.getSyncStatus(); const stats = syncStatus.getStats(); - const encryption = this.application.isEncryptionAvailable(); + const encryption = this.application!.isEncryptionAvailable(); if (stats.localDataDone) { - this.syncStatus = this.application.getStatusService().removeStatus(this.syncStatus); + 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 = this.application!.getStatusService().replaceStatusWithString( this.syncStatus, loadingStatus ); } updateSyncStatus() { - const syncStatus = this.application.getSyncStatus(); + const syncStatus = this.application!.getSyncStatus(); const stats = syncStatus.getStats(); if (syncStatus.hasError()) { - this.syncStatus = this.application.getStatusService().replaceStatusWithString( + this.syncStatus = this.application!.getStatusService().replaceStatusWithString( this.syncStatus, 'Unable to Sync' ); } else if (stats.downloadCount > 20) { const text = `Downloading ${stats.downloadCount} items. Keep app open.`; - this.syncStatus = this.application.getStatusService().replaceStatusWithString( + this.syncStatus = this.application!.getStatusService().replaceStatusWithString( this.syncStatus, text ); @@ -181,56 +192,63 @@ class ApplicationViewCtrl extends PureCtrl { } else if (this.showingDownloadStatus) { this.showingDownloadStatus = false; const text = "Download Complete."; - this.syncStatus = this.application.getStatusService().replaceStatusWithString( + this.syncStatus = this.application!.getStatusService().replaceStatusWithString( this.syncStatus, text ); setTimeout(() => { - this.syncStatus = this.application.getStatusService().removeStatus(this.syncStatus); + this.syncStatus = this.application!.getStatusService().removeStatus(this.syncStatus); }, 2000); } else if (stats.uploadTotalCount > 20) { - this.uploadSyncStatus = this.application.getStatusService().replaceStatusWithString( + 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 = this.application!.getStatusService().removeStatus( this.uploadSyncStatus ); } } - openModalComponent(component) { - const scope = this.$rootScope.$new(true); + openModalComponent(component: SNComponent) { + const scope = this.$rootScope!.$new(true) as ModalComponentScope; scope.component = component; - const el = this.$compile("")(scope); + const el = this.$compile!( + "" + )(scope as any); angular.element(document.body).append(el); } - presentPermissionsDialog(dialog) { - const scope = this.$rootScope.$new(true); + presentPermissionsDialog(dialog: PermissionDialog) { + const scope = this.$rootScope!.$new(true) as PermissionsModalScope; scope.permissionsString = dialog.permissionsString; scope.component = dialog.component; scope.callback = dialog.callback; - const el = this.$compile("")(scope); + const el = this.$compile!( + "" + )(scope as any); angular.element(document.body).append(el); } overrideComponentManagerFunctions() { - this.application.componentManager.openModalComponent = this.openModalComponent; - this.application.componentManager.presentPermissionsDialog = this.presentPermissionsDialog; + 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(); + if ( + !this.lastAlertShownDate || + (new Date().getTime() - this.lastAlertShownDate!.getTime()) / 1000 > SHOW_INTERVAL + ) { + this.lastAlertShownDate = new Date(); setTimeout(() => { - this.application.alertService.alert({ - text: STRING_SESSION_EXPIRED - }); + this.application!.alertService!.alert( + STRING_SESSION_EXPIRED + ); }, 500); } } @@ -245,49 +263,49 @@ class ApplicationViewCtrl extends PureCtrl { window.addEventListener('drop', this.onDragDrop, true); } - onDragOver(event) { - if (event.dataTransfer.files.length > 0) { + onDragOver(event: DragEvent) { + if (event.dataTransfer!.files.length > 0) { event.preventDefault(); } } - onDragDrop(event) { - if (event.dataTransfer.files.length > 0) { + onDragDrop(event: DragEvent) { + if (event.dataTransfer!.files.length > 0) { event.preventDefault(); - this.application.alertService.alert({ - text: STRING_DEFAULT_FILE_ERROR - }); + this.application!.alertService!.alert( + STRING_DEFAULT_FILE_ERROR + ); } } async handleAutoSignInFromParams() { - const params = this.$location.search(); + 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(); + const user = this.application!.getUser(); if (user) { - if (user.email === email && await this.application.getHost() === server) { + 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!.signOut(); } } - await this.application.setHost(server); - this.application.signIn({ - email: email, - password: password, - }); + await this.application!.setHost(server); + this.application!.signIn( + email, + password, + ); } } -export class ApplicationView { +export class ApplicationView extends WebDirective { constructor() { + super(); this.template = template; this.controller = ApplicationViewCtrl; this.replace = true; diff --git a/app/assets/javascripts/controllers/constants.js b/app/assets/javascripts/controllers/constants.ts similarity index 100% rename from app/assets/javascripts/controllers/constants.js rename to app/assets/javascripts/controllers/constants.ts diff --git a/app/assets/javascripts/controllers/footer.js b/app/assets/javascripts/controllers/footer.ts similarity index 54% rename from app/assets/javascripts/controllers/footer.js rename to app/assets/javascripts/controllers/footer.ts index 4eed8f45e..2149d9ff2 100644 --- a/app/assets/javascripts/controllers/footer.js +++ b/app/assets/javascripts/controllers/footer.ts @@ -1,9 +1,14 @@ +import { FooterStatus, WebDirective } from './../types'; import { dateToLocalizedString } from '@/utils'; import { ApplicationEvent, - TIMING_STRATEGY_FORCE_SPAWN_NEW, + SyncQueueStrategy, ProtectedAction, - ContentTypes + ContentType, + SNComponent, + SNTheme, + ComponentArea, + ComponentAction } from 'snjs'; import template from '%/footer.pug'; import { AppStateEvent, EventSource } from '@/services/state'; @@ -11,40 +16,71 @@ import { STRING_GENERIC_SYNC_ERROR, STRING_NEW_UPDATE_READY } 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 { + 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> = {} + /* @ngInject */ constructor( - $rootScope, - $timeout, + $rootScope: ng.IRootScopeService, + $timeout: ng.ITimeoutService, ) { super($timeout); this.$rootScope = $rootScope; - this.rooms = []; - this.themesWithIcons = []; - this.showSyncResolution = false; this.addRootScopeListeners(); + this.toggleSyncResolutionMenu = this.toggleSyncResolutionMenu.bind(this); + this.closeAccountMenu = this.closeAccountMenu.bind(this); } deinit() { this.rooms.length = 0; this.themesWithIcons.length = 0; this.unregisterComponent(); - this.unregisterComponent = null; + this.unregisterComponent = undefined; this.rootScopeListener1(); this.rootScopeListener2(); - this.rootScopeListener1 = null; - this.rootScopeListener2 = null; - this.closeAccountMenu = null; - this.toggleSyncResolutionMenu = null; + this.rootScopeListener1 = undefined; + this.rootScopeListener2 = undefined; + (this.closeAccountMenu as any) = undefined; + (this.toggleSyncResolutionMenu as any) = undefined; super.deinit(); } $onInit() { super.$onInit(); - this.application.getStatusService().addStatusObserver((string) => { + this.application!.getStatusService().addStatusObserver((string: string) => { this.$timeout(() => { this.arbitraryStatusMessage = string; }); @@ -58,18 +94,18 @@ class FooterCtrl extends PureCtrl { } reloadUpgradeStatus() { - this.application.checkForSecurityUpdate().then((available) => { + this.application!.checkForSecurityUpdate().then((available) => { this.setState({ dataUpgradeAvailable: available }); }); } - onAppLaunch() { + async onAppLaunch() { super.onAppLaunch(); this.reloadPasscodeStatus(); this.reloadUpgradeStatus(); - this.user = this.application.getUser(); + this.user = this.application!.getUser(); this.updateOfflineStatus(); this.findErrors(); this.streamItems(); @@ -77,7 +113,7 @@ class FooterCtrl extends PureCtrl { } async reloadPasscodeStatus() { - const hasPasscode = this.application.hasPasscode(); + const hasPasscode = this.application!.hasPasscode(); this.setState({ hasPasscode: hasPasscode }); @@ -95,30 +131,30 @@ class FooterCtrl extends PureCtrl { } /** @override */ - onAppStateEvent(eventName, data) { + onAppStateEvent(eventName: AppStateEvent, data: any) { if (eventName === AppStateEvent.EditorFocused) { if (data.eventSource === EventSource.UserInteraction) { this.closeAllRooms(); this.closeAccountMenu(); } } else if (eventName === AppStateEvent.BeganBackupDownload) { - this.backupStatus = this.application.getStatusService().addStatusFromString( + this.backupStatus = this.application!.getStatusService().addStatusFromString( "Saving local backup..." ); } else if (eventName === AppStateEvent.EndedBackupDownload) { if (data.success) { - this.backupStatus = this.application.getStatusService().replaceStatusWithString( - this.backupStatus, + this.backupStatus = this.application!.getStatusService().replaceStatusWithString( + this.backupStatus!, "Successfully saved backup." ); } else { - this.backupStatus = this.application.getStatusService().replaceStatusWithString( - this.backupStatus, + this.backupStatus = this.application!.getStatusService().replaceStatusWithString( + this.backupStatus!, "Unable to save local backup." ); } this.$timeout(() => { - this.backupStatus = this.application.getStatusService().removeStatus(this.backupStatus); + this.backupStatus = this.application!.getStatusService().removeStatus(this.backupStatus!); }, 2000); } } @@ -131,7 +167,7 @@ class FooterCtrl extends PureCtrl { /** @override */ - onAppEvent(eventName) { + onAppEvent(eventName: ApplicationEvent) { if (eventName === ApplicationEvent.KeyStatusChanged) { this.reloadUpgradeStatus(); } else if (eventName === ApplicationEvent.EnteredOutOfSync) { @@ -143,7 +179,7 @@ class FooterCtrl extends PureCtrl { outOfSync: false }); } else if (eventName === ApplicationEvent.CompletedSync) { - if (this.offline && this.application.getNoteCount() === 0) { + if (this.offline && this.application!.getNoteCount() === 0) { this.showAccountMenu = true; } this.syncUpdated(); @@ -156,52 +192,53 @@ class FooterCtrl extends PureCtrl { } streamItems() { - this.application.streamItems({ - contentType: ContentType.Component, - stream: async () => { - this.rooms = this.application.getItems({ - contentType: ContentType.Component - }).filter((candidate) => { - return candidate.area === 'rooms' && !candidate.deleted; + this.application!.streamItems( + ContentType.Component, + async () => { + const components = this.application!.getItems(ContentType.Component) as SNComponent[]; + this.rooms = components.filter((candidate) => { + return candidate.area === ComponentArea.Rooms && !candidate.deleted; }); if (this.queueExtReload) { this.queueExtReload = false; this.reloadExtendedData(); } } - }); + ); - this.application.streamItems({ - contentType: 'SN|Theme', - stream: async () => { - const themes = this.application.getDisplayableItems({ - contentType: ContentType.Theme - }).filter((candidate) => { + this.application!.streamItems( + ContentType.Theme, + async () => { + const themes = this.application!.getDisplayableItems(ContentType.Theme) as SNTheme[]; + const filteredThemes = themes.filter((candidate) => { return ( !candidate.deleted && - candidate.content.package_info && - candidate.content.package_info.dock_icon + candidate.package_info && + candidate.package_info.dock_icon ); }).sort((a, b) => { return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; }); - const differ = themes.length !== this.themesWithIcons.length; - this.themesWithIcons = themes; + const differ = filteredThemes.length !== this.themesWithIcons.length; + this.themesWithIcons = filteredThemes; if (differ) { this.reloadDockShortcuts(); } } - }); + ); } registerComponentHandler() { - this.unregisterComponent = this.application.componentManager.registerHandler({ - identifier: "roomBar", - areas: ["rooms", "modal"], - activationHandler: (component) => { }, + this.unregisterComponent = this.application!.componentManager!.registerHandler({ + identifier: 'room-bar', + areas: [ComponentArea.Rooms, ComponentArea.Modal], + activationHandler: () => { }, actionHandler: (component, action, data) => { - if (action === "set-size") { - component.setLastSize(data); + if (action === ComponentAction.SetSize) { + this.application!.changeItem(component.uuid, (m) => { + const mutator = m as ComponentMutator; + mutator.setLastSize(data); + }) } }, focusHandler: (component, focused) => { @@ -224,7 +261,8 @@ class FooterCtrl extends PureCtrl { * then closing it after a short delay. */ 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) { this.queueExtReload = true; @@ -240,15 +278,15 @@ class FooterCtrl extends PureCtrl { } updateOfflineStatus() { - this.offline = this.application.noAccount(); + this.offline = this.application!.noAccount(); } openSecurityUpdate() { - this.application.performProtocolUpgrade(); + this.application!.performProtocolUpgrade(); } findErrors() { - this.error = this.application.getSyncStatus().error; + this.hasError = this.application!.getSyncStatus().hasError(); } accountMenuPressed() { @@ -256,31 +294,31 @@ class FooterCtrl extends PureCtrl { this.closeAllRooms(); } - toggleSyncResolutionMenu = () => { + toggleSyncResolutionMenu() { this.showSyncResolution = !this.showSyncResolution; } - closeAccountMenu = () => { + closeAccountMenu() { this.showAccountMenu = false; } lockApp() { - this.application.lock(); + this.application!.lock(); } refreshData() { this.isRefreshing = true; - this.application.sync({ - timingStrategy: TIMING_STRATEGY_FORCE_SPAWN_NEW, + this.application!.sync({ + queueStrategy: SyncQueueStrategy.ForceSpawnNew, checkIntegrity: true }).then((response) => { this.$timeout(() => { this.isRefreshing = false; }, 200); if (response && response.error) { - this.application.alertService.alert({ - text: STRING_GENERIC_SYNC_ERROR - }); + this.application!.alertService!.alert( + STRING_GENERIC_SYNC_ERROR + ); } else { this.syncUpdated(); } @@ -288,7 +326,7 @@ class FooterCtrl extends PureCtrl { } syncUpdated() { - this.lastSyncDate = dateToLocalizedString(this.application.getLastSyncDate()); + this.lastSyncDate = dateToLocalizedString(this.application!.getLastSyncDate()!); } onNewUpdateAvailable() { @@ -297,16 +335,16 @@ class FooterCtrl extends PureCtrl { clickedNewUpdateAnnouncement() { this.newUpdateAvailable = false; - this.application.alertService.alert({ - text: STRING_NEW_UPDATE_READY - }); + this.application!.alertService!.alert( + STRING_NEW_UPDATE_READY + ); } reloadDockShortcuts() { const shortcuts = []; for (const theme of this.themesWithIcons) { - const name = theme.content.package_info.name; - const icon = theme.content.package_info.dock_icon; + const name = theme.package_info.name; + const icon = theme.package_info.dock_icon; if (!icon) { continue; } @@ -314,7 +352,7 @@ class FooterCtrl extends PureCtrl { name: name, component: theme, icon: icon - }); + } as DockShortcut); } this.dockShortcuts = shortcuts.sort((a, b) => { @@ -327,46 +365,49 @@ class FooterCtrl extends PureCtrl { return -1; } else if (bType === 'circle' && aType === 'svg') { return 1; + } else { + return 0; } }); } - initSvgForShortcut(shortcut) { + initSvgForShortcut(shortcut: DockShortcut) { const id = 'dock-svg-' + shortcut.component.uuid; - const element = document.getElementById(id); + const element = document.getElementById(id)!; 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'); element.appendChild(doc.documentElement); } - selectShortcut(shortcut) { - this.application.componentManager.toggleComponent(shortcut.component); + selectShortcut(shortcut: DockShortcut) { + this.application!.componentManager!.toggleComponent(shortcut.component); } - onRoomDismiss(room) { - room.showRoom = false; + onRoomDismiss(room: SNComponent) { + this.roomShowState[room.uuid] = false; } closeAllRooms() { for (const room of this.rooms) { - room.showRoom = false; + this.roomShowState[room.uuid] = false; } } - async selectRoom(room) { + async selectRoom(room: SNComponent) { const run = () => { this.$timeout(() => { - room.showRoom = !room.showRoom; + this.roomShowState[room.uuid] = !this.roomShowState[room.uuid]; }); }; - if (!room.showRoom) { - const requiresPrivilege = await this.application.privilegesService.actionRequiresPrivilege( - ProtectedAction.ManageExtensions - ); + if (!this.roomShowState[room.uuid]) { + const requiresPrivilege = await this.application!.privilegesService! + .actionRequiresPrivilege( + ProtectedAction.ManageExtensions + ); if (requiresPrivilege) { - this.application.presentPrivilegesModal( + this.application!.presentPrivilegesModal( ProtectedAction.ManageExtensions, run ); @@ -379,15 +420,16 @@ class FooterCtrl extends PureCtrl { } clickOutsideAccountMenu() { - if (this.application && this.application.authenticationInProgress()) { + if (this.application && this.application!.authenticationInProgress()) { return; } this.showAccountMenu = false; } } -export class Footer { +export class Footer extends WebDirective { constructor() { + super(); this.restrict = 'E'; this.template = template; this.controller = FooterCtrl; diff --git a/app/assets/javascripts/controllers/index.js b/app/assets/javascripts/controllers/index.ts similarity index 100% rename from app/assets/javascripts/controllers/index.js rename to app/assets/javascripts/controllers/index.ts diff --git a/app/assets/javascripts/controllers/lockScreen.depr.js b/app/assets/javascripts/controllers/lockScreen.depr.js deleted file mode 100644 index 15418e918..000000000 --- a/app/assets/javascripts/controllers/lockScreen.depr.js +++ /dev/null @@ -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: '=' - }; - } -} \ No newline at end of file diff --git a/app/assets/javascripts/controllers/root.js b/app/assets/javascripts/controllers/root.ts similarity index 56% rename from app/assets/javascripts/controllers/root.js rename to app/assets/javascripts/controllers/root.ts index 2daf57d0b..99f2b64fd 100644 --- a/app/assets/javascripts/controllers/root.js +++ b/app/assets/javascripts/controllers/root.ts @@ -1,8 +1,16 @@ +import { ApplicationManager } from './../applicationManager'; +import { WebDirective } from './../types'; import template from '%/root.pug'; +import { WebApplication } from '@/application'; class RootCtrl { + + private $timeout: ng.ITimeoutService + private applicationManager: ApplicationManager + public applications: WebApplication[] = [] + /* @ngInject */ - constructor($timeout, applicationManager) { + constructor($timeout: ng.ITimeoutService, applicationManager: ApplicationManager) { this.$timeout = $timeout; this.applicationManager = applicationManager; this.applicationManager.addApplicationChangeObserver(() => { @@ -17,8 +25,9 @@ class RootCtrl { } } -export class Root { +export class Root extends WebDirective { constructor() { + super(); this.template = template; this.controller = RootCtrl; this.replace = true; diff --git a/app/assets/javascripts/database.ts b/app/assets/javascripts/database.ts index 42f05e4d7..f321e41db 100644 --- a/app/assets/javascripts/database.ts +++ b/app/assets/javascripts/database.ts @@ -1,5 +1,6 @@ import { WebApplication } from './application'; import { SNAlertService } from "../../../../snjs/dist/@types"; +import { RawPayload } from '../../../../snjs/dist/@types/protocol/payloads/generator'; const DB_NAME = 'standardnotes'; const STORE_NAME = 'items'; @@ -105,7 +106,7 @@ export class Database { }); } - public async getAllPayloads() { + public async getAllPayloads(): Promise { const db = (await this.openDatabase())!; return new Promise((resolve) => { const objectStore = @@ -126,11 +127,11 @@ export class Database { }); } - public async savePayload(payload: any) { + public async savePayload(payload: any): Promise { return this.savePayloads([payload]); } - public async savePayloads(payloads: any[]) { + public async savePayloads(payloads: any[]): Promise { if (payloads.length === 0) { return; } @@ -157,8 +158,8 @@ export class Database { }); } - private putItems(objectStore: IDBObjectStore, items: any[]) { - return Promise.all(items.map((item) => { + private async putItems(objectStore: IDBObjectStore, items: any[]): Promise { + await Promise.all(items.map((item) => { return new Promise((resolve) => { const request = objectStore.put(item); request.onerror = resolve; @@ -167,19 +168,21 @@ export class Database { })); } - public async deletePayload(uuid: string) { + public async deletePayload(uuid: string): Promise { const db = (await this.openDatabase())!; return new Promise((resolve, reject) => { const request = db.transaction(STORE_NAME, READ_WRITE) .objectStore(STORE_NAME) .delete(uuid); - request.onsuccess = resolve; + request.onsuccess = () => { + resolve(); + }; request.onerror = reject; }); } - public async clearAllPayloads() { + public async clearAllPayloads(): Promise { const deleteRequest = window.indexedDB.deleteDatabase(DB_NAME); return new Promise((resolve, reject) => { deleteRequest.onerror = () => { @@ -200,7 +203,7 @@ export class Database { this.alertService!.alert(message); } - private showGenericError(error: {code: number, name: string}) { + private showGenericError(error: { code: number, name: string }) { const message = `Unable to save changes locally due to an unknown system issue. ` + `Issue Code: ${error.code} Issue Name: ${error.name}.`; diff --git a/app/assets/javascripts/services/statusManager.js b/app/assets/javascripts/services/statusManager.js deleted file mode 100644 index dc55cf6bf..000000000 --- a/app/assets/javascripts/services/statusManager.js +++ /dev/null @@ -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); - } -} diff --git a/app/assets/javascripts/services/statusManager.ts b/app/assets/javascripts/services/statusManager.ts new file mode 100644 index 000000000..2015a55d8 --- /dev/null +++ b/app/assets/javascripts/services/statusManager.ts @@ -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); + } + } +} diff --git a/app/assets/javascripts/types.ts b/app/assets/javascripts/types.ts index b9116fe6b..b28acd376 100644 --- a/app/assets/javascripts/types.ts +++ b/app/assets/javascripts/types.ts @@ -1,3 +1,4 @@ +import { SNComponent } from 'snjs'; export class WebDirective implements ng.IDirective { controller?: string | ng.Injectable; controllerAs?: string; @@ -18,6 +19,17 @@ export interface PasswordWizardScope extends Partial { application: any } +export interface PermissionsModalScope extends Partial { + application: any + component: SNComponent + permissionsString: string + callback: (approved: boolean) => void +} + +export interface ModalComponentScope extends Partial { + component: SNComponent +} + export type PanelPuppet = { onReady?: () => void ready?: boolean @@ -25,4 +37,8 @@ export type PanelPuppet = { setLeft?: (left: number) => void isCollapsed?: () => boolean flash?: () => void +} + +export type FooterStatus = { + string: string } \ No newline at end of file diff --git a/app/assets/templates/footer.pug b/app/assets/templates/footer.pug index 3c3e4f9ad..6d98fe486 100644 --- a/app/assets/templates/footer.pug +++ b/app/assets/templates/footer.pug @@ -8,10 +8,10 @@ ) .sk-app-bar-item-column .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-label.title(ng-class='{red: ctrl.error}') Account + .sk-label.title(ng-class='{red: ctrl.hasError}') Account account-menu( close-function='ctrl.closeAccountMenu()', ng-click='$event.stopPropagation()', @@ -31,7 +31,7 @@ .sk-label {{room.name}} component-modal( component='room', - ng-if='room.showRoom', + ng-if='ctrl.roomShowState[room.uuid]', on-dismiss='ctrl.onRoomDismiss()', application='ctrl.application' )