Add types to Javascript

This commit is contained in:
Vinicius Fortuna 2020-05-18 15:23:06 -04:00
parent c02fdec396
commit e152d80f6d
7 changed files with 113 additions and 39 deletions

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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,

View file

@ -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();

View file

@ -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) {

View file

@ -9,6 +9,7 @@
"outDir": "build/server_manager/web_app/js",
"sourceMap": true,
"experimentalDecorators":true,
"allowJs": true,
},
"include": [
"src/**/*.ts"