diff --git a/app/src/api.ts b/app/src/api.ts deleted file mode 100644 index b41ab60f..00000000 --- a/app/src/api.ts +++ /dev/null @@ -1,20 +0,0 @@ -export { AppService } from 'services/app' -export { PluginsService } from 'services/plugins' -export { Tab } from 'models/tab' - -export interface IPlugin { - -} - -export interface IToolbarButton { - icon: string - title: string - weight?: number - click: () => void -} - -export interface IToolbarButtonProvider { - provide (): IToolbarButton[] -} - -export const ToolbarButtonProviderType = 'app:toolbar-button-provider' diff --git a/app/src/api/index.ts b/app/src/api/index.ts new file mode 100644 index 00000000..5b838f6b --- /dev/null +++ b/app/src/api/index.ts @@ -0,0 +1,6 @@ +export { Tab } from './tab' +export { TabRecoveryProviderType, ITabRecoveryProvider } from './tabRecovery' +export { ToolbarButtonProviderType, IToolbarButton, IToolbarButtonProvider } from './toolbarButtonProvider' + +export { AppService } from 'services/app' +export { PluginsService } from 'services/plugins' diff --git a/app/src/models/tab.ts b/app/src/api/tab.ts similarity index 91% rename from app/src/models/tab.ts rename to app/src/api/tab.ts index fa20e374..8f1781ba 100644 --- a/app/src/models/tab.ts +++ b/app/src/api/tab.ts @@ -23,4 +23,8 @@ export class Tab { getComponentType (): ComponentType { return null } + + getRecoveryToken (): any { + return null + } } diff --git a/app/src/api/tabRecovery.ts b/app/src/api/tabRecovery.ts new file mode 100644 index 00000000..1e245017 --- /dev/null +++ b/app/src/api/tabRecovery.ts @@ -0,0 +1,7 @@ +import { Tab } from './tab' + +export interface ITabRecoveryProvider { + recover (recoveryToken: any): Tab +} + +export const TabRecoveryProviderType = 'app:TabRecoveryProviderType' diff --git a/app/src/api/toolbarButtonProvider.ts b/app/src/api/toolbarButtonProvider.ts new file mode 100644 index 00000000..4a0e3e82 --- /dev/null +++ b/app/src/api/toolbarButtonProvider.ts @@ -0,0 +1,12 @@ +export interface IToolbarButton { + icon: string + title: string + weight?: number + click: () => void +} + +export interface IToolbarButtonProvider { + provide (): IToolbarButton[] +} + +export const ToolbarButtonProviderType = 'app:ToolbarButtonProviderType' diff --git a/app/src/components/appRoot.ts b/app/src/components/appRoot.ts index af94eae1..ee94c6eb 100644 --- a/app/src/components/appRoot.ts +++ b/app/src/components/appRoot.ts @@ -123,6 +123,8 @@ export class AppRootComponent { } this.docking.dock() }) + + this.app.restoreTabs() } getToolbarButtons (aboveZero: boolean): IToolbarButton[] { diff --git a/app/src/components/baseTab.ts b/app/src/components/baseTab.ts index fae14097..8becc96f 100644 --- a/app/src/components/baseTab.ts +++ b/app/src/components/baseTab.ts @@ -1,4 +1,4 @@ -import { Tab } from 'models/tab' +import { Tab } from 'api/tab' export class BaseTabComponent { protected model: T diff --git a/app/src/components/tabBody.ts b/app/src/components/tabBody.ts index 5db3e42d..8207245d 100644 --- a/app/src/components/tabBody.ts +++ b/app/src/components/tabBody.ts @@ -1,5 +1,5 @@ import { Component, Input, ViewContainerRef, ViewChild, HostBinding, ComponentFactoryResolver, ComponentRef } from '@angular/core' -import { Tab } from 'models/tab' +import { Tab } from 'api/tab' import { BaseTabComponent } from 'components/baseTab' @Component({ diff --git a/app/src/components/tabHeader.ts b/app/src/components/tabHeader.ts index 160edd70..69c3f63b 100644 --- a/app/src/components/tabHeader.ts +++ b/app/src/components/tabHeader.ts @@ -1,5 +1,5 @@ import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/core' -import { Tab } from 'models/tab' +import { Tab } from 'api/tab' import './tabHeader.scss' diff --git a/app/src/services/app.ts b/app/src/services/app.ts index 1996edf6..806fb56a 100644 --- a/app/src/services/app.ts +++ b/app/src/services/app.ts @@ -1,5 +1,8 @@ -import { EventEmitter, Injectable } from '@angular/core' -import { Tab } from 'models/tab' +import { Injectable } from '@angular/core' +import { Logger, LogService } from 'services/log' +import { Tab } from 'api/tab' +import { PluginsService } from 'services/plugins' +import { ITabRecoveryProvider, TabRecoveryProviderType } from 'api/tabRecovery' @Injectable() @@ -7,14 +10,19 @@ export class AppService { tabs: Tab[] = [] activeTab: Tab lastTabIndex = 0 + logger: Logger - constructor () { - + constructor ( + private plugins: PluginsService, + log: LogService, + ) { + this.logger = log.create('app') } openTab (tab: Tab): void { this.tabs.push(tab) this.selectTab(tab) + this.saveTabs() } selectTab (tab) { @@ -62,5 +70,33 @@ export class AppService { if (tab == this.activeTab) { this.selectTab(this.tabs[newIndex]) } + this.saveTabs() + } + + saveTabs () { + window.localStorage.tabsRecovery = JSON.stringify( + this.tabs + .map((tab) => tab.getRecoveryToken()) + .filter((token) => !!token) + ) + } + + restoreTabs () { + if (window.localStorage.tabsRecovery) { + let providers = this.plugins.getAll(TabRecoveryProviderType) + JSON.parse(window.localStorage.tabsRecovery).forEach((token) => { + for (let provider of providers) { + try { + let tab = provider.recover(token) + if (tab) { + this.openTab(tab) + return + } + } catch (_) { } + this.logger.warn('Cannot restore tab from the token:', token) + } + }) + this.saveTabs() + } } } diff --git a/app/src/services/log.ts b/app/src/services/log.ts index 44a4600c..a03d127e 100644 --- a/app/src/services/log.ts +++ b/app/src/services/log.ts @@ -6,9 +6,8 @@ export class Logger { private name: string, ) {} - log(level: string, ...args: any[]) { - args.splice(0, 0, this.name + ':') - console[level](...args) + log (level: string, ...args: any[]) { + console[level](`%c[${this.name}]`, 'color: #aaa', ...args) } debug(...args: any[]) { this.log('debug', ...args) } diff --git a/app/src/services/plugins.ts b/app/src/services/plugins.ts index 8f970671..9ed51900 100644 --- a/app/src/services/plugins.ts +++ b/app/src/services/plugins.ts @@ -1,9 +1,8 @@ -import { IPlugin } from 'api' import { Injectable } from '@angular/core' interface IPluginEntry { - plugin: IPlugin + plugin: any weight: number } @@ -15,14 +14,14 @@ export class PluginsService { ) { } - register (type: string, plugin: IPlugin, weight = 0): void { + register (type: string, plugin: any, weight = 0): void { if (!this.plugins[type]) { this.plugins[type] = [] } this.plugins[type].push({ plugin, weight }) } - getAll (type: string): T[] { + getAll (type: string): T[] { let plugins = this.plugins[type] || [] plugins = plugins.sort((a: IPluginEntry, b: IPluginEntry) => { if (a.weight < b.weight) { diff --git a/app/src/settings/index.ts b/app/src/settings/index.ts index 5994afe9..a8604df9 100644 --- a/app/src/settings/index.ts +++ b/app/src/settings/index.ts @@ -9,9 +9,10 @@ import { HotkeyHintComponent } from './components/hotkeyHint' import { HotkeyInputModalComponent } from './components/hotkeyInputModal' import { SettingsPaneComponent } from './components/settingsPane' -import { PluginsService, ToolbarButtonProviderType } from 'api' +import { PluginsService, ToolbarButtonProviderType, TabRecoveryProviderType } from 'api' import { ButtonProvider } from './buttonProvider' +import { RecoveryProvider } from './recoveryProvider' @NgModule({ @@ -22,6 +23,7 @@ import { ButtonProvider } from './buttonProvider' ], providers: [ ButtonProvider, + RecoveryProvider, ], entryComponents: [ HotkeyInputModalComponent, @@ -36,8 +38,13 @@ import { ButtonProvider } from './buttonProvider' ], }) class SettingsModule { - constructor (plugins: PluginsService, buttonProvider: ButtonProvider) { + constructor ( + plugins: PluginsService, + buttonProvider: ButtonProvider, + recoveryProvider: RecoveryProvider, + ) { plugins.register(ToolbarButtonProviderType, buttonProvider, 1) + plugins.register(TabRecoveryProviderType, recoveryProvider) } } diff --git a/app/src/settings/recoveryProvider.ts b/app/src/settings/recoveryProvider.ts new file mode 100644 index 00000000..6d1d321a --- /dev/null +++ b/app/src/settings/recoveryProvider.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core' +import { Tab, ITabRecoveryProvider } from 'api' +import { SettingsTab } from './tab' + + +@Injectable() +export class RecoveryProvider implements ITabRecoveryProvider { + recover (recoveryToken: any): Tab { + if (recoveryToken.type == 'app:settings') { + return new SettingsTab() + } + return null + } +} diff --git a/app/src/settings/tab.ts b/app/src/settings/tab.ts index 1066a5eb..090c69b1 100644 --- a/app/src/settings/tab.ts +++ b/app/src/settings/tab.ts @@ -1,4 +1,4 @@ -import { Tab, ComponentType } from '../models/tab' +import { Tab, ComponentType } from 'api/tab' import { SettingsPaneComponent } from './components/settingsPane' export class SettingsTab extends Tab { @@ -11,4 +11,10 @@ export class SettingsTab extends Tab { getComponentType (): ComponentType { return SettingsPaneComponent } + + getRecoveryToken (): any { + return { + type: 'app:settings', + } + } } diff --git a/app/src/terminal/index.ts b/app/src/terminal/index.ts index aa6bfcaf..b3a3e328 100644 --- a/app/src/terminal/index.ts +++ b/app/src/terminal/index.ts @@ -2,11 +2,12 @@ import { BrowserModule } from '@angular/platform-browser' import { NgModule } from '@angular/core' import { FormsModule } from '@angular/forms' -import { PluginsService, ToolbarButtonProviderType } from 'api' +import { PluginsService, ToolbarButtonProviderType, TabRecoveryProviderType } from 'api' import { TerminalTabComponent } from './components/terminalTab' import { SessionsService } from './services/sessions' import { ButtonProvider } from './buttonProvider' +import { RecoveryProvider } from './recoveryProvider' @NgModule({ @@ -17,6 +18,7 @@ import { ButtonProvider } from './buttonProvider' providers: [ ButtonProvider, SessionsService, + RecoveryProvider, ], entryComponents: [ TerminalTabComponent, @@ -26,8 +28,13 @@ import { ButtonProvider } from './buttonProvider' ], }) class TerminalModule { - constructor (plugins: PluginsService, buttonProvider: ButtonProvider) { + constructor ( + plugins: PluginsService, + buttonProvider: ButtonProvider, + recoveryProvider: RecoveryProvider, + ) { plugins.register(ToolbarButtonProviderType, buttonProvider) + plugins.register(TabRecoveryProviderType, recoveryProvider) } } diff --git a/app/src/terminal/recoveryProvider.ts b/app/src/terminal/recoveryProvider.ts new file mode 100644 index 00000000..bccb4247 --- /dev/null +++ b/app/src/terminal/recoveryProvider.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core' +import { Tab, ITabRecoveryProvider } from 'api' +import { TerminalTab } from './tab' +import { SessionsService } from './services/sessions' + + +@Injectable() +export class RecoveryProvider implements ITabRecoveryProvider { + constructor (private sessions: SessionsService) { } + + recover (recoveryToken: any): Tab { + if (recoveryToken.type == 'app:terminal') { + const options = this.sessions.recoveryProvider.getRecoverySession(recoveryToken.recoveryId) + let session = this.sessions.createSession(options) + session.recoveryId = recoveryToken.recoveryId + return new TerminalTab(session) + } + return null + } +} diff --git a/app/src/terminal/tab.ts b/app/src/terminal/tab.ts index a51a7e32..6eb4840b 100644 --- a/app/src/terminal/tab.ts +++ b/app/src/terminal/tab.ts @@ -1,9 +1,11 @@ -import { Tab, ComponentType } from '../models/tab' +import { Tab, ComponentType } from 'api/tab' import { TerminalTabComponent } from './components/terminalTab' import { Session } from './services/sessions' export class TerminalTab extends Tab { + static recoveryId = 'app:terminal' + constructor (public session: Session) { super() } @@ -11,4 +13,11 @@ export class TerminalTab extends Tab { getComponentType (): ComponentType { return TerminalTabComponent } + + getRecoveryToken (): any { + return { + type: 'app:terminal', + recoveryId: this.session.recoveryId, + } + } }