From e152d80f6dc2dadd8cd1bcf486a269a081f58e06 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 18 May 2020 15:23:06 -0400 Subject: [PATCH] Add types to Javascript --- src/server_manager/web_app/app.spec.ts | 11 ++- src/server_manager/web_app/app.ts | 20 +++-- src/server_manager/web_app/display_server.ts | 8 +- src/server_manager/web_app/main.ts | 3 +- .../web_app/ui_components/app-root.js | 31 +++++++- .../ui_components/outline-server-view.js | 78 +++++++++++++++---- tsconfig.json | 1 + 7 files changed, 113 insertions(+), 39 deletions(-) diff --git a/src/server_manager/web_app/app.spec.ts b/src/server_manager/web_app/app.spec.ts index 63d90e8b..036ea01e 100644 --- a/src/server_manager/web_app/app.spec.ts +++ b/src/server_manager/web_app/app.spec.ts @@ -21,7 +21,9 @@ import {Surveys} from '../model/survey'; import {App} from './app'; import {TokenManager} from './digitalocean_oauth'; -import {DisplayServer, DisplayServerRepository, makeDisplayServer} from './display_server'; +import {DisplayServerRepository, makeDisplayServer} from './display_server'; +import {AppRoot, DisplayServer} from './ui_components/app-root.js'; +import {ServerView} from './ui_components/outline-server-view.js'; const TOKEN_WITH_NO_SERVERS = 'no-server-token'; const TOKEN_WITH_ONE_SERVER = 'one-server-token'; @@ -257,11 +259,12 @@ enum AppRootScreen { DIALOG } -class FakePolymerAppRoot implements polymer.Base { +class FakePolymerAppRoot extends AppRoot { events = new EventEmitter(); backgroundScreen = AppRootScreen.NONE; currentScreen = AppRootScreen.NONE; - serverView = {setServerTransferredData: () => {}, serverId: '', initHelpBubbles: () => {}}; + serverView = {setServerTransferredData: () => {}, serverId: '', initHelpBubbles: () => {}} as + unknown as ServerView; serverList: DisplayServer[] = []; is: 'fake-polymer-app-root'; @@ -304,7 +307,7 @@ class FakePolymerAppRoot implements polymer.Base { this.backgroundScreen = AppRootScreen.NONE; } - getServerView() { + getServerView(serverId: string): ServerView { return this.serverView; } diff --git a/src/server_manager/web_app/app.ts b/src/server_manager/web_app/app.ts index df8fc538..e7714772 100644 --- a/src/server_manager/web_app/app.ts +++ b/src/server_manager/web_app/app.ts @@ -24,9 +24,12 @@ import {Surveys} from '../model/survey'; import {TokenManager} from './digitalocean_oauth'; import * as digitalocean_server from './digitalocean_server'; -import {DisplayServer, DisplayServerRepository, makeDisplayServer} from './display_server'; +import {DisplayServerRepository, makeDisplayServer} from './display_server'; import {parseManualServerConfig} from './management_urls'; +import {AppRoot, DisplayServer} from './ui_components/app-root.js'; +import {ServerView} from './ui_components/outline-server-view.js'; + // The Outline DigitalOcean team's referral code: // https://www.digitalocean.com/help/referral-program/ const UNUSED_DIGITALOCEAN_REFERRAL_CODE = '5ddb4219b716'; @@ -99,7 +102,7 @@ async function computeDefaultAccessKeyDataLimit( } } -async function showHelpBubblesOnce(serverView: polymer.Base) { +async function showHelpBubblesOnce(serverView: ServerView) { if (!window.localStorage.getItem('addAccessKeyHelpBubble-dismissed')) { await serverView.showAddAccessKeyHelpBubble(); window.localStorage.setItem('addAccessKeyHelpBubble-dismissed', 'true'); @@ -137,7 +140,7 @@ export class App { private serverBeingCreated: server.ManagedServer; constructor( - private appRoot: polymer.Base, private readonly version: string, + private appRoot: AppRoot, private readonly version: string, private createDigitalOceanSession: DigitalOceanSessionFactory, private createDigitalOceanServerRepository: DigitalOceanServerRepositoryFactory, private manualServerRepository: server.ManualServerRepository, @@ -565,7 +568,7 @@ export class App { }); } else { // Display the unreachable server state within the server view. - const serverView = this.appRoot.getServerView(displayServer.id); + const serverView = this.appRoot.getServerView(displayServer.id) as ServerView; serverView.isServerReachable = false; serverView.isServerManaged = isManagedServer(server); serverView.serverName = displayServer.name; // Don't get the name from the remote server. @@ -892,7 +895,7 @@ export class App { this.showTransferStats(selectedServer, view); } - private showMetricsOptInWhenNeeded(selectedServer: server.Server, serverView: polymer.Base) { + private showMetricsOptInWhenNeeded(selectedServer: server.Server, serverView: ServerView) { const showMetricsOptInOnce = () => { // Sanity check to make sure the running server is still displayed, i.e. // it hasn't been deleted. @@ -922,7 +925,7 @@ export class App { } } - private async refreshTransferStats(selectedServer: server.Server, serverView: polymer.Base) { + private async refreshTransferStats(selectedServer: server.Server, serverView: ServerView) { try { const stats = await selectedServer.getDataUsage(); let totalBytes = 0; @@ -963,7 +966,7 @@ export class App { } } - private showTransferStats(selectedServer: server.Server, serverView: polymer.Base) { + private showTransferStats(selectedServer: server.Server, serverView: ServerView) { this.refreshTransferStats(selectedServer, serverView); // Get transfer stats once per minute for as long as server is selected. const statsRefreshRateMs = 60 * 1000; @@ -1224,7 +1227,8 @@ export class App { this.appRoot.showError(this.appRoot.localize('error-server-rename')); const oldName = this.selectedServer.getName(); view.serverName = oldName; - view.$.serverSettings.serverName = oldName; + // tslint:disable-next-line:no-any + (view.$.serverSettings as any).serverName = oldName; } } diff --git a/src/server_manager/web_app/display_server.ts b/src/server_manager/web_app/display_server.ts index 43258ca5..e3bbe15f 100644 --- a/src/server_manager/web_app/display_server.ts +++ b/src/server_manager/web_app/display_server.ts @@ -13,13 +13,7 @@ // limitations under the License. import * as server from '../model/server'; - -export interface DisplayServer { - id: string; - name: string; - isManaged: boolean; - isSynced?: boolean; -} +import {DisplayServer} from './ui_components/app-root.js'; // Returns a `DisplayServer` corresponding to `server`. export async function makeDisplayServer(server: server.Server) { diff --git a/src/server_manager/web_app/main.ts b/src/server_manager/web_app/main.ts index 18bc66ab..3a99a743 100644 --- a/src/server_manager/web_app/main.ts +++ b/src/server_manager/web_app/main.ts @@ -24,6 +24,7 @@ import * as digitalocean_server from './digitalocean_server'; import {DisplayServerRepository} from './display_server'; import {ManualServerRepository} from './manual_server'; import {DEFAULT_PROMPT_IMPRESSION_DELAY_MS, OutlineSurveys} from './survey'; +import {AppRoot} from './ui_components/app-root.js'; const SUPPORTED_LANGUAGES: {[key: string]: {id: string, dir: string}} = { 'am': {id: 'am', dir: 'ltr'}, @@ -101,7 +102,7 @@ document.addEventListener('WebComponentsReady', () => { document.documentElement.setAttribute('dir', languageDirection); // NOTE: this cast is safe and allows us to leverage Polymer typings since we haven't migrated to // Polymer 3, which adds typescript support. - const appRoot = document.getElementById('appRoot') as unknown as polymer.Base; + const appRoot = document.getElementById('appRoot') as unknown as AppRoot; appRoot.setLanguage(language.string(), languageDirection); new App( appRoot, version, digitalocean_api.createDigitalOceanSession, diff --git a/src/server_manager/web_app/ui_components/app-root.js b/src/server_manager/web_app/ui_components/app-root.js index a343134f..4ad8712b 100644 --- a/src/server_manager/web_app/ui_components/app-root.js +++ b/src/server_manager/web_app/ui_components/app-root.js @@ -36,7 +36,6 @@ import './outline-manual-server-entry.js'; import './outline-modal-dialog.js'; import './outline-region-picker-step.js'; import './outline-server-progress-step.js'; -import './outline-server-view.js'; import './outline-tos-view.js'; import {AppLocalizeBehavior} from '@polymer/app-localize-behavior/app-localize-behavior.js'; @@ -44,9 +43,20 @@ import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js'; import {html} from '@polymer/polymer/lib/utils/html-tag.js'; import {PolymerElement} from '@polymer/polymer/polymer-element.js'; +import {ServerView} from './outline-server-view.js'; + const TOS_ACK_LOCAL_STORAGE_KEY = 'tos-ack'; -class AppRoot extends mixinBehaviors +/** + * A server to be displayed + * @typedef {Object} DisplayServer + * @property {string} id - The id of the server + * @property {string} name - The display name of the server + * @property {boolean} isManaged - Whether the server host is managed by the Outline Manager + * @property {boolean=} isSynced - Whether the server information is updated + */ + +export class AppRoot extends mixinBehaviors ([AppLocalizeBehavior], PolymerElement) { static get template() { return html` @@ -522,6 +532,8 @@ class AppRoot extends mixinBehaviors constructor() { super(); + /** @type {DisplayServer} */ + this.selectedServer = undefined; this.addEventListener('RegionSelected', this.handleRegionSelected); this.addEventListener( 'SetUpGenericCloudProviderRequested', this.handleSetUpGenericCloudProviderRequested); @@ -530,6 +542,11 @@ class AppRoot extends mixinBehaviors this.addEventListener('ManualServerEntryCancelled', this.handleManualCancelled); } + /** + * Sets the language and direction for the application + * @param {string} newLanguage + * @param {string} langDir + */ setLanguage(newLanguage, langDir) { const messagesUrl = `./messages/${newLanguage}.json`; this.loadResources(messagesUrl, newLanguage); @@ -585,6 +602,11 @@ class AppRoot extends mixinBehaviors this.currentPage = 'serverView'; } + /** + * Gets the ServerView for the server given by its id + * @param {string} displayServerId + * @returns {ServerView} + */ getServerView(displayServerId) { if (!displayServerId) { return null; @@ -629,6 +651,11 @@ class AppRoot extends mixinBehaviors this.showToast(message, durationMs); } + /** + * Show a toast with a message + * @param {string} message + * @param {number} duration in seconds + */ showToast(message, duration) { const toast = this.$.toast; toast.close(); diff --git a/src/server_manager/web_app/ui_components/outline-server-view.js b/src/server_manager/web_app/ui_components/outline-server-view.js index c6fa895a..f6f93ca6 100644 --- a/src/server_manager/web_app/ui_components/outline-server-view.js +++ b/src/server_manager/web_app/ui_components/outline-server-view.js @@ -55,6 +55,23 @@ function makePublicEvent(eventName, detail) { return new CustomEvent(eventName, params); } +/** + * An access key to be displayed + * @typedef {Object} DisplayAccessKey + * @prop {string} id + * @prop {string} placeholderName + * @prop {string} name + * @prop {string} accessUrl + * @prop {number} transferredBytes + * @prop {number} relativeTraffic + */ + +/** + * An access key data limit + * @typedef {Object} DataLimit + * @readonly @prop {number} bytes + */ + export class ServerView extends DirMixin(PolymerElement) { static get template() { return html` @@ -629,23 +646,50 @@ export class ServerView extends DirMixin(PolymerElement) { ]; } - // Parameter `accessKey` has the format { - // id: string, - // placeholderName: string, - // name: string, - // accessUrl: string, - // transferredBytes: number; - // relativeTraffic: number; - // } - addAccessKey(accessKey) { - // TODO(fortuna): Restore loading animation. - // TODO(fortuna): Restore highlighting. - this.push('accessKeyRows', accessKey); - // Force render the access key list so that the input is present in the DOM - this.$.accessKeysContainer.querySelector('dom-repeat').render(); - const input = this.shadowRoot.querySelector(`#access-key-${accessKey.id}`); - input.select(); - } + constructor() { + super(); + this.serverId = ''; + this.serverName = ''; + this.serverHostname = ''; + this.serverVersion = ''; + this.isHostnameEditable = false; + this.serverManagementApiUrl = ''; + /** @type {number} */ + this.serverPortForNewAccessKeys = null; + this.isAccessKeyPortEditable = false; + this.serverCreationDate = ''; + this.serverLocation = ''; + /** @type {DataLimit} */ + this.accessKeyDataLimit = null; + this.isAccessKeyDataLimitEnabled = false; + this.supportsAccessKeyDataLimit = false; + this.dataLimitsAvailabilityDate = ''; + this.isServerManaged = false; + this.isServerReachable = false; + /** @type {Function} */ + this.retryDisplayingServer = null; + // myConnection: Object, + this.totalInboundBytes = 0; + /** @type {DisplayAccessKey[]} */ + this.accessKeyRows = []; + this.hasNonAdminAccessKeys = false; + this.metricsEnabled = true; + this.monthlyOutboundTransferBytes = 0; + this.monthlyCost = 0; + } + + /** + * @param {DisplayAccessKey} accessKey + */ + addAccessKey(accessKey) { + // TODO(fortuna): Restore loading animation. + // TODO(fortuna): Restore highlighting. + this.push('accessKeyRows', accessKey); + // Force render the access key list so that the input is present in the DOM + this.$.accessKeysContainer.querySelector('dom-repeat').render(); + const input = this.shadowRoot.querySelector(`#access-key-${accessKey.id}`); + input.select(); + } removeAccessKey(accessKeyId) { for (let ui in this.accessKeyRows) { diff --git a/tsconfig.json b/tsconfig.json index 8ffbc06c..e5ec6a74 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "outDir": "build/server_manager/web_app/js", "sourceMap": true, "experimentalDecorators":true, + "allowJs": true, }, "include": [ "src/**/*.ts"