From e152d80f6dc2dadd8cd1bcf486a269a081f58e06 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Mon, 18 May 2020 15:23:06 -0400 Subject: [PATCH 1/6] 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" From 32bf2c95dad282c5414ec7e7060c6860f5c43ec2 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 19 May 2020 16:42:21 -0400 Subject: [PATCH 2/6] Initialize all members in the constructor --- .../ui_components/outline-server-view.js | 75 +++++++++---------- 1 file changed, 36 insertions(+), 39 deletions(-) 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 f6f93ca6..f9641d32 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 @@ -581,60 +581,35 @@ export class ServerView extends DirMixin(PolymerElement) { serverName: String, serverHostname: String, serverVersion: String, - isHostnameEditable: {type: Boolean, value: false}, + isHostnameEditable: {type: Boolean}, serverManagementApiUrl: String, serverPortForNewAccessKeys: Number, - isAccessKeyPortEditable: {type: Boolean, value: false}, + isAccessKeyPortEditable: {type: Boolean}, serverCreationDate: String, serverLocation: String, - accessKeyDataLimit: {type: Object, value: null}, + accessKeyDataLimit: {type: Object}, isAccessKeyDataLimitEnabled: {type: Boolean}, - supportsAccessKeyDataLimit: - {type: Boolean, value: false}, // Whether the server supports data limits. - dataLimitsAvailabilityDate: - {type: String}, // Date by which the feature stops being an experiment. + supportsAccessKeyDataLimit: {type: Boolean}, + dataLimitsAvailabilityDate: {type: String}, isServerManaged: Boolean, isServerReachable: Boolean, - // Callback for retrying to display an unreachable server. retryDisplayingServer: Function, - // myConnection has the same fields as each item in accessKeyRows. It may - // be unset in some old versions of Outline that allowed deleting this - // row. myConnection: Object, totalInboundBytes: Number, - accessKeyRows: {type: Array, value: []}, + accessKeyRows: {type: Array}, hasNonAdminAccessKeys: Boolean, metricsEnabled: Boolean, - // Initialize monthlyOutboundTransferBytes and monthlyCost to 0, so they can - // be bound to hidden attributes. Initializing to undefined does not - // cause hidden$=... expressions to be evaluated and so elements may be - // shown incorrectly. See: - // https://stackoverflow.com/questions/33700125/polymer-1-0-hidden-attribute-negate-operator - // https://www.polymer-project.org/1.0/docs/devguide/data-binding.html - monthlyOutboundTransferBytes: {type: Number, value: 0}, - monthlyCost: {type: Number, value: 0}, - selectedTab: { - type: String, - value: 'connections', - }, + monthlyOutboundTransferBytes: {type: Number}, + monthlyCost: {type: Number}, + selectedTab: {type: String}, managedServerUtilzationPercentage: { type: Number, computed: '_computeManagedServerUtilzationPercentage(totalInboundBytes, monthlyOutboundTransferBytes)', }, - accessKeySortBy: { - type: String, - value: 'name', - }, - accessKeySortDirection: { - // 1 == ascending, -1 == descending - type: Number, - value: 1, - }, - localize: { - type: Function, - readonly: true, - }, + accessKeySortBy: {type: String}, + accessKeySortDirection: {type: Number}, + localize: {type: Function, readonly: true}, }; } @@ -662,20 +637,42 @@ export class ServerView extends DirMixin(PolymerElement) { /** @type {DataLimit} */ this.accessKeyDataLimit = null; this.isAccessKeyDataLimitEnabled = false; + /** Whether the server supports data limits. */ this.supportsAccessKeyDataLimit = false; + /** Date by which the feature stops being an experiment. */ this.dataLimitsAvailabilityDate = ''; this.isServerManaged = false; this.isServerReachable = false; - /** @type {Function} */ + /** + * Callback for retrying to display an unreachable server. + * @type {Function} + */ this.retryDisplayingServer = null; - // myConnection: Object, + /** + * myConnection has the same fields as each item in accessKeyRows. It may + * be unset in some old versions of Outline that allowed deleting this row + * @type {DisplayAccessKey} + */ + this.myConnection = null; this.totalInboundBytes = 0; /** @type {DisplayAccessKey[]} */ this.accessKeyRows = []; this.hasNonAdminAccessKeys = false; this.metricsEnabled = true; + // Initialize monthlyOutboundTransferBytes and monthlyCost to 0, so they can + // be bound to hidden attributes. Initializing to undefined does not + // cause hidden$=... expressions to be evaluated and so elements may be + // shown incorrectly. See: + // https://stackoverflow.com/questions/33700125/polymer-1-0-hidden-attribute-negate-operator + // https://www.polymer-project.org/1.0/docs/devguide/data-binding.html this.monthlyOutboundTransferBytes = 0; this.monthlyCost = 0; + this.selectedTab = 'connections'; + this.accessKeySortBy = 'name'; + /** The direction to sort: 1 == ascending, -1 == descending */ + this.accessKeySortDirection = 1; + /** @type {Function} */ + this.localize = null; } /** From 84a36d7edbdd413bb8cd592d669d81579f28aa1c Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 19 May 2020 16:56:53 -0400 Subject: [PATCH 3/6] Reshuffle types --- src/server_manager/web_app/app.spec.ts | 4 ++-- src/server_manager/web_app/app.ts | 17 ++++------------- src/server_manager/web_app/display_server.ts | 8 +++++++- .../web_app/ui_components/app-root.js | 11 ++--------- 4 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/server_manager/web_app/app.spec.ts b/src/server_manager/web_app/app.spec.ts index 036ea01e..8fde8201 100644 --- a/src/server_manager/web_app/app.spec.ts +++ b/src/server_manager/web_app/app.spec.ts @@ -21,8 +21,8 @@ import {Surveys} from '../model/survey'; import {App} from './app'; import {TokenManager} from './digitalocean_oauth'; -import {DisplayServerRepository, makeDisplayServer} from './display_server'; -import {AppRoot, DisplayServer} from './ui_components/app-root.js'; +import {DisplayServer, DisplayServerRepository, makeDisplayServer} from './display_server'; +import {AppRoot} from './ui_components/app-root.js'; import {ServerView} from './ui_components/outline-server-view.js'; const TOKEN_WITH_NO_SERVERS = 'no-server-token'; diff --git a/src/server_manager/web_app/app.ts b/src/server_manager/web_app/app.ts index e7714772..2d5816cc 100644 --- a/src/server_manager/web_app/app.ts +++ b/src/server_manager/web_app/app.ts @@ -24,11 +24,11 @@ import {Surveys} from '../model/survey'; import {TokenManager} from './digitalocean_oauth'; import * as digitalocean_server from './digitalocean_server'; -import {DisplayServerRepository, makeDisplayServer} from './display_server'; +import {DisplayServer, 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'; +import {AppRoot} from './ui_components/app-root.js'; +import {DisplayAccessKey, ServerView} from './ui_components/outline-server-view.js'; // The Outline DigitalOcean team's referral code: // https://www.digitalocean.com/help/referral-program/ @@ -41,15 +41,6 @@ const CHANGE_HOSTNAME_VERSION = '1.2.0'; export const DATA_LIMITS_AVAILABILITY_DATE = new Date('2020-06-02'); const MAX_ACCESS_KEY_DATA_LIMIT_BYTES = 50 * (10 ** 9); // 50GB -interface UiAccessKey { - id: string; - placeholderName: string; - name: string; - accessUrl: string; - transferredBytes: number; - relativeTraffic: number; -} - interface DisplayDataAmount { unit: 'MB'|'GB'; value: number; @@ -989,7 +980,7 @@ export class App { // Converts the access key from the remote service format to the // format used by outline-server-view. - private convertToUiAccessKey(remoteAccessKey: server.AccessKey): UiAccessKey { + private convertToUiAccessKey(remoteAccessKey: server.AccessKey): DisplayAccessKey { return { id: remoteAccessKey.id, placeholderName: `${this.appRoot.localize('key', 'keyId', remoteAccessKey.id)}`, diff --git a/src/server_manager/web_app/display_server.ts b/src/server_manager/web_app/display_server.ts index e3bbe15f..43258ca5 100644 --- a/src/server_manager/web_app/display_server.ts +++ b/src/server_manager/web_app/display_server.ts @@ -13,7 +13,13 @@ // limitations under the License. import * as server from '../model/server'; -import {DisplayServer} from './ui_components/app-root.js'; + +export interface DisplayServer { + id: string; + name: string; + isManaged: boolean; + isSynced?: boolean; +} // Returns a `DisplayServer` corresponding to `server`. export async function makeDisplayServer(server: server.Server) { 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 4ad8712b..64d5d355 100644 --- a/src/server_manager/web_app/ui_components/app-root.js +++ b/src/server_manager/web_app/ui_components/app-root.js @@ -43,19 +43,12 @@ 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 {DisplayServer} from '../display_server'; + import {ServerView} from './outline-server-view.js'; const TOS_ACK_LOCAL_STORAGE_KEY = 'tos-ack'; -/** - * 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() { From 0d0f7c1fa829a452f7be01a53f19ed3399277e17 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 19 May 2020 19:29:53 -0400 Subject: [PATCH 4/6] Move DisplayDataAmount to server view --- src/server_manager/web_app/app.ts | 7 +------ .../web_app/ui_components/outline-server-view.js | 8 ++++---- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/server_manager/web_app/app.ts b/src/server_manager/web_app/app.ts index 2d5816cc..e8d8b135 100644 --- a/src/server_manager/web_app/app.ts +++ b/src/server_manager/web_app/app.ts @@ -28,7 +28,7 @@ import {DisplayServer, DisplayServerRepository, makeDisplayServer} from './displ import {parseManualServerConfig} from './management_urls'; import {AppRoot} from './ui_components/app-root.js'; -import {DisplayAccessKey, ServerView} from './ui_components/outline-server-view.js'; +import {DisplayAccessKey, DisplayDataAmount, ServerView} from './ui_components/outline-server-view.js'; // The Outline DigitalOcean team's referral code: // https://www.digitalocean.com/help/referral-program/ @@ -41,11 +41,6 @@ const CHANGE_HOSTNAME_VERSION = '1.2.0'; export const DATA_LIMITS_AVAILABILITY_DATE = new Date('2020-06-02'); const MAX_ACCESS_KEY_DATA_LIMIT_BYTES = 50 * (10 ** 9); // 50GB -interface DisplayDataAmount { - unit: 'MB'|'GB'; - value: number; -} - function dataLimitToDisplayDataAmount(limit: server.DataLimit): DisplayDataAmount|null { if (!limit) { return null; 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 f9641d32..73b37ab2 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 @@ -67,9 +67,9 @@ function makePublicEvent(eventName, detail) { */ /** - * An access key data limit - * @typedef {Object} DataLimit - * @readonly @prop {number} bytes + * @typedef {Object} DisplayDataAmount + * @prop {'MB'|'GB'} unit + * @prop {number} value */ export class ServerView extends DirMixin(PolymerElement) { @@ -634,7 +634,7 @@ export class ServerView extends DirMixin(PolymerElement) { this.isAccessKeyPortEditable = false; this.serverCreationDate = ''; this.serverLocation = ''; - /** @type {DataLimit} */ + /** @type {DisplayDataAmount} */ this.accessKeyDataLimit = null; this.isAccessKeyDataLimitEnabled = false; /** Whether the server supports data limits. */ From 55e2ce4f06ea10765e547684e46c58b8d68cd20a Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 19 May 2020 19:35:50 -0400 Subject: [PATCH 5/6] Update function types --- .../web_app/ui_components/outline-server-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 73b37ab2..7369c446 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 @@ -645,7 +645,7 @@ export class ServerView extends DirMixin(PolymerElement) { this.isServerReachable = false; /** * Callback for retrying to display an unreachable server. - * @type {Function} + * @type {() => void)} */ this.retryDisplayingServer = null; /** @@ -671,7 +671,7 @@ export class ServerView extends DirMixin(PolymerElement) { this.accessKeySortBy = 'name'; /** The direction to sort: 1 == ascending, -1 == descending */ this.accessKeySortDirection = 1; - /** @type {Function} */ + /** @type {(msgId: string, ...params: string[]) => string} */ this.localize = null; } From bfb46477868feff464123d52315f9663327d8cb2 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Tue, 19 May 2020 19:58:07 -0400 Subject: [PATCH 6/6] Moar typesWhy not? --- src/server_manager/web_app/app.spec.ts | 4 +- .../web_app/ui_components/app-root.js | 79 ++++++++++--------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/src/server_manager/web_app/app.spec.ts b/src/server_manager/web_app/app.spec.ts index 8fde8201..9524da5b 100644 --- a/src/server_manager/web_app/app.spec.ts +++ b/src/server_manager/web_app/app.spec.ts @@ -293,9 +293,9 @@ class FakePolymerAppRoot extends AppRoot { showModalDialog() { this.backgroundScreen = this.currentScreen; this.setScreen(AppRootScreen.DIALOG); - const promise = new Promise(() => {}); + const promise = new Promise(() => 0); // Supress Promise not handled warning. - promise.then(() => {}); + promise.then(v => v); return promise; } 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 64d5d355..b8a79dc1 100644 --- a/src/server_manager/web_app/ui_components/app-root.js +++ b/src/server_manager/web_app/ui_components/app-root.js @@ -470,30 +470,15 @@ export class AppRoot extends mixinBehaviors static get properties() { return { // Properties language and useKeyIfMissing are used by Polymer.AppLocalizeBehavior. - language: { - type: String, - readonly: true, - }, - useKeyIfMissing: { - type: Boolean, - value: true, - }, - serverList: { - type: Array, - value: [], - }, - selectedServer: { - type: Object, // DisplayServer in display_server.ts - value: undefined, - }, + language: {type: String, readonly: true}, + useKeyIfMissing: {type: Boolean}, + serverList: {type: Array}, + selectedServer: {type: Object}, hasManualServers: { type: Boolean, computed: '_computeHasManualServers(serverList.*)', }, - adminEmail: { - type: String, - value: '', - }, + adminEmail: {type: String}, isSignedInToDigitalOcean: { type: Boolean, computed: '_computeIsSignedInToDigitalOcean(adminEmail)', @@ -508,14 +493,8 @@ export class AppRoot extends mixinBehaviors type: Boolean, computed: '_computeHasAcceptedTermsOfService(userAcceptedTos)', }, - currentPage: { - type: String, - value: 'intro', - }, - shouldShowSideBar: { - type: Boolean, - value: false, - }, + currentPage: {type: String}, + shouldShowSideBar: {type: Boolean}, sideBarMarginClass: { type: String, computed: '_computeSideBarMarginClass(shouldShowSideBar)', @@ -527,6 +506,16 @@ export class AppRoot extends mixinBehaviors super(); /** @type {DisplayServer} */ this.selectedServer = undefined; + this.language = ''; + this.useKeyIfMissing = true; + /** @type {DisplayServer[]} */ + this.serverList = []; + this.adminEmail = ''; + this.outlineVersion = ''; + this.currentPage = 'intro'; + this.shouldShowSideBar = false; + + this.addEventListener('RegionSelected', this.handleRegionSelected); this.addEventListener( 'SetUpGenericCloudProviderRequested', this.handleSetUpGenericCloudProviderRequested); @@ -583,6 +572,10 @@ export class AppRoot extends mixinBehaviors return this.$.manualEntry; } + /** + * @param {string} serverName + * @param {boolean} showCancelButton + */ showProgress(serverName, showCancelButton) { this.currentPage = 'serverProgressStep'; this.$.serverProgressStep.serverName = serverName; @@ -608,7 +601,7 @@ export class AppRoot extends mixinBehaviors return this.$.serverView.querySelector(`#serverView-${selectedServerId}`); } - handleRegionSelected(e) { + handleRegionSelected(/** @type {Event} */ e) { this.fire('SetUpServerRequested', { regionId: this.$.regionPicker.getSelectedRegionId(), }); @@ -626,7 +619,7 @@ export class AppRoot extends mixinBehaviors this.handleManualServerSelected('gcp'); } - handleManualServerSelected(cloudProvider) { + handleManualServerSelected(/** @type {'generic'|'aws'|'gcp'} */ cloudProvider) { this.$.manualEntry.clear(); this.$.manualEntry.cloudProvider = cloudProvider; this.currentPage = 'manualEntry'; @@ -636,11 +629,11 @@ export class AppRoot extends mixinBehaviors this.currentPage = 'intro'; } - showError(errorMsg) { + showError(/** @type {string} */ errorMsg) { this.showToast(errorMsg, Infinity); } - showNotification(message, durationMs = 3000) { + showNotification(/** @type {string} */ message, durationMs = 3000) { this.showToast(message, durationMs); } @@ -667,8 +660,11 @@ export class AppRoot extends mixinBehaviors this.$.toast.close(); } - // cb is a function which accepts a single boolean which is true iff - // the user chose to retry the failing operation. + /** + * @param {(retry: boolean) => void} cb a function which accepts a single boolean which is true + * iff + * the user chose to retry the failing operation. + */ showConnectivityDialog(cb) { const dialogTitle = this.localize('error-connectivity-title'); const dialogText = this.localize('error-connectivity'); @@ -688,6 +684,10 @@ export class AppRoot extends mixinBehaviors }); } + /** + * @param {string} errorTitle + * @param {string} errorText + */ showManualServerError(errorTitle, errorText) { this.showModalDialog(errorTitle, errorText, [this.localize('cancel'), this.localize('retry')]) .then(clickedButtonIndex => { @@ -750,7 +750,7 @@ export class AppRoot extends mixinBehaviors this.fire('SignOutRequested'); } - openManualInstallFeedback(prepopulatedMessage) { + openManualInstallFeedback(/** @type {string} */ prepopulatedMessage) { this.$.feedbackDialog.open(prepopulatedMessage, true); } @@ -758,7 +758,7 @@ export class AppRoot extends mixinBehaviors this.$.shareDialog.open(accessKey, s3Url); } - openGetConnectedDialog(inviteUrl) { + openGetConnectedDialog(/** @type {string} */ inviteUrl) { const dialog = this.$.getConnectedDialog; if (dialog.children.length > 1) { return; // The iframe is already loading. @@ -786,7 +786,12 @@ export class AppRoot extends mixinBehaviors this.$.metricsDialog.showMetricsOptInDialog(); } - // Returns a Promise which fulfills with the index of the button clicked. + /** + * @param {string} title + * @param {string} text + * @param {string[]} buttons + * @returns {Promise} a Promise which fulfills with the index of the button clicked. + */ showModalDialog(title, text, buttons) { return this.$.modalDialog.open(title, text, buttons); }