diff --git a/src/server_manager/ui_components/app-root.html b/src/server_manager/ui_components/app-root.html index eff677a96..57a72902e 100644 --- a/src/server_manager/ui_components/app-root.html +++ b/src/server_manager/ui_components/app-root.html @@ -514,7 +514,7 @@

[[localize('digitalocean-disconnect-account')]]

- [[localize('close')]] + [[localize('close')]]
@@ -812,30 +812,6 @@

[[localize('digitalocean-disconnect-account')]]

this.$.shareDialog.open(accessKey, s3Url); } - openGetConnectedDialog(inviteUrl) { - const dialog = this.$.getConnectedDialog; - if (dialog.children.length > 1) { - return; // The iframe is already loading. - } - // Reset the iframe's state, by replacing it with a newly constructed - // iframe. Unfortunately the location.reload API does not work in our case due to - // this Chrome error: - // "Blocked a frame with origin "outline://web_app" from accessing a cross-origin frame." - const iframe = document.createElement("iframe"); - iframe.onload = function() { - dialog.open(); - }; - iframe.src = inviteUrl; - dialog.insertBefore(iframe, dialog.children[0]); - } - - closeGetConnectedDialog() { - const dialog = this.$.getConnectedDialog; - dialog.close(); - const oldIframe = dialog.children[0]; - dialog.removeChild(oldIframe); - } - showMetricsDialogForNewServer() { this.$.metricsDialog.showMetricsOptInDialog(); } diff --git a/src/server_manager/ui_components/outline-share-dialog.html b/src/server_manager/ui_components/outline-share-dialog.html index abcb9e885..2bd75c1d8 100644 --- a/src/server_manager/ui_components/outline-share-dialog.html +++ b/src/server_manager/ui_components/outline-share-dialog.html @@ -120,7 +120,7 @@

[[localize('share-title')]]

- [[localize('share-invite-copy')]] + [[localize('share-invite-copy')]] [[localize('done')]]
@@ -129,27 +129,16 @@

[[localize('share-title')]]

diff --git a/src/server_manager/web_app/app.spec.ts b/src/server_manager/web_app/app.spec.ts index 7916f43c3..f606d18c6 100644 --- a/src/server_manager/web_app/app.spec.ts +++ b/src/server_manager/web_app/app.spec.ts @@ -257,13 +257,29 @@ enum AppRootScreen { DIALOG } +class FakeElement { + addEventListener(event: string, handler: Function) {} + querySelector(query: string) { + return new FakeElement(); + } +} + +class FakeShareDialogElement implements polymer.Base { + is = 'fake-share-dialog'; + $ = {copyButton: new FakeElement()}; +} + class FakePolymerAppRoot implements polymer.Base { events = new events.EventEmitter(); backgroundScreen = AppRootScreen.NONE; currentScreen = AppRootScreen.NONE; serverView = {setServerTransferredData: () => {}, serverId: '', initHelpBubbles: () => {}}; serverList: DisplayServer[] = []; - is: 'fake-polymer-app-root'; + is = 'fake-polymer-app-root'; + $ = { + shareDialog: new FakeShareDialogElement(), + getConnectedDialog: new FakeElement(), + }; private setScreen(screenId: AppRootScreen) { this.currentScreen = screenId; diff --git a/src/server_manager/web_app/app.ts b/src/server_manager/web_app/app.ts index 06dc6ff85..d6e10634f 100644 --- a/src/server_manager/web_app/app.ts +++ b/src/server_manager/web_app/app.ts @@ -25,7 +25,9 @@ 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 {GetConnectedApp} from './get_connected_app'; import {parseManualServerConfig} from './management_urls'; +import {ShareDialogApp} from './share_dialog_app'; // The Outline DigitalOcean team's referral code: // https://www.digitalocean.com/help/referral-program/ @@ -145,6 +147,9 @@ export class App { private digitalOceanTokenManager: TokenManager, private surveys: Surveys) { appRoot.setAttribute('outline-version', this.version); + const shareApp = new ShareDialogApp(appRoot.$.shareDialog); + const getConnectedApp = new GetConnectedApp(appRoot.$.getConnectedDialog); + appRoot.addEventListener('ConnectToDigitalOcean', (event: CustomEvent) => { this.connectToDigitalOcean(); }); @@ -271,12 +276,11 @@ export class App { }); appRoot.addEventListener('OpenShareDialogRequested', (event: CustomEvent) => { - const accessKey = event.detail.accessKey; - this.appRoot.openShareDialog(accessKey, this.getS3InviteUrl(accessKey)); + shareApp.start(event.detail.accessKey, this.getS3InviteUrl(event.detail.accessKey)); }); appRoot.addEventListener('OpenGetConnectedDialogRequested', (event: CustomEvent) => { - this.appRoot.openGetConnectedDialog(this.getS3InviteUrl(event.detail.accessKey, true)); + getConnectedApp.start(this.getS3InviteUrl(event.detail.accessKey, true)); }); appRoot.addEventListener('ShowServerRequested', (event: CustomEvent) => { diff --git a/src/server_manager/web_app/build_action.sh b/src/server_manager/web_app/build_action.sh index d465b7221..ed16902bb 100755 --- a/src/server_manager/web_app/build_action.sh +++ b/src/server_manager/web_app/build_action.sh @@ -43,7 +43,7 @@ tsc # Browserify node_modules/ (just a couple of key NPMs) and app. pushd $OUT_DIR > /dev/null mkdir -p browserified/server_manager/web_app -$NODE_MODULES_BIN_DIR/browserify --require byte-size --require clipboard-polyfill -o browserified/node_modules.js +$NODE_MODULES_BIN_DIR/browserify --require byte-size -o browserified/node_modules.js $NODE_MODULES_BIN_DIR/browserify js/server_manager/web_app/main.js -s main -o browserified/server_manager/web_app/main.js popd > /dev/null diff --git a/src/server_manager/web_app/get_connected_app.ts b/src/server_manager/web_app/get_connected_app.ts new file mode 100644 index 000000000..1d62d3238 --- /dev/null +++ b/src/server_manager/web_app/get_connected_app.ts @@ -0,0 +1,43 @@ +// Copyright 2020 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export class GetConnectedApp { + constructor(private dialog: polymer.Base) { + // Get connected is not a Polymer component, so we use `querySelector()` instead of `dialog.$`. + dialog.querySelector('#closeGetConnectedButton') + .addEventListener('tap', (event: CustomEvent) => { + dialog.close(); + if (dialog.children.length > 1) { + const oldIframe = dialog.children[0]; + dialog.removeChild(oldIframe); + } + }); + } + + start(inviteUrl: string) { + if (this.dialog.children.length > 1) { + return; // The iframe is already loading. + } + // Reset the iframe's state, by replacing it with a newly constructed + // iframe. Unfortunately the location.reload API does not work in our case due to + // this Chrome error: + // "Blocked a frame with origin "outline://web_app" from accessing a cross-origin frame." + const iframe = document.createElement('iframe'); + iframe.onload = () => { + this.dialog.open(); + }; + iframe.src = inviteUrl; + this.dialog.insertBefore(iframe, this.dialog.children[0]); + } +} diff --git a/src/server_manager/web_app/share_dialog_app.ts b/src/server_manager/web_app/share_dialog_app.ts new file mode 100644 index 000000000..91ea1e676 --- /dev/null +++ b/src/server_manager/web_app/share_dialog_app.ts @@ -0,0 +1,36 @@ +// Copyright 2020 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as clipboard from 'clipboard-polyfill'; + +export class ShareDialogApp { + constructor(private root: polymer.Base) { + this.root.$.copyButton.addEventListener('tap', (event: CustomEvent) => { + const dt = new clipboard.DT(); + dt.setData('text/plain', this.root.$.selectableText.innerText); + dt.setData('text/html', this.root.$.selectableText.innerHTML); + clipboard.write(dt); + this.root.$.copyText.hidden = false; + }); + } + + start(accessKey: string, s3InviteUrl: string) { + this.root.acessKey = accessKey; + this.root.s3Url = s3InviteUrl; + // TODO(fortuna): Instead of passing a pre-made outline-share-dialog, we should create and + // insert it here instead. This way we don't need to reset state, which is cleaner. + this.root.$.copyText.setAttribute('hidden', true); + this.root.$.dialog.open(); + } +}