From 3456b4c58a41a5c5a22e081992ee405d8d4822b0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 11 Jan 2021 23:42:49 -0700 Subject: [PATCH] WIP concept for a "thin widget" wrapper --- src/vector/index.ts | 11 +---- src/vector/promise_utils.ts | 25 ++++++++++ src/vector/thin_widget/app.tsx | 44 +++++++++++++++++ src/vector/thin_widget/index.html | 11 +++++ src/vector/thin_widget/index.scss | 44 +++++++++++++++++ src/vector/thin_widget/index.ts | 78 +++++++++++++++++++++++++++++++ webpack.config.js | 11 ++++- 7 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 src/vector/promise_utils.ts create mode 100644 src/vector/thin_widget/app.tsx create mode 100644 src/vector/thin_widget/index.html create mode 100644 src/vector/thin_widget/index.scss create mode 100644 src/vector/thin_widget/index.ts diff --git a/src/vector/index.ts b/src/vector/index.ts index d9c63755f6..b43ac9583d 100644 --- a/src/vector/index.ts +++ b/src/vector/index.ts @@ -28,22 +28,13 @@ require('katex/dist/katex.css'); // These are things that can run before the skin loads - be careful not to reference the react-sdk though. import {parseQsFromFragment} from "./url_utils"; import './modernizr'; +import {settled} from "./promise_utils"; // load service worker if available on this platform if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js'); } -async function settled(...promises: Array>) { - for (const prom of promises) { - try { - await prom; - } catch (e) { - console.error(e); - } - } -} - function checkBrowserFeatures() { if (!window.Modernizr) { console.error("Cannot check features - Modernizr global is missing."); diff --git a/src/vector/promise_utils.ts b/src/vector/promise_utils.ts new file mode 100644 index 0000000000..4c9802cf41 --- /dev/null +++ b/src/vector/promise_utils.ts @@ -0,0 +1,25 @@ +/* +Copyright 2021 New Vector Ltd. + +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 async function settled(...promises: Array>) { + for (const prom of promises) { + try { + await prom; + } catch (e) { + console.error(e); + } + } +} diff --git a/src/vector/thin_widget/app.tsx b/src/vector/thin_widget/app.tsx new file mode 100644 index 0000000000..8d64ecce93 --- /dev/null +++ b/src/vector/thin_widget/app.tsx @@ -0,0 +1,44 @@ +/* +Copyright 2021 New Vector Ltd. + +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 React from 'react'; +// add React and ReactPerf to the global namespace, to make them easier to access via the console +// this incidentally means we can forget our React imports in JSX files without penalty. +window.React = React; + +import AppTile from "matrix-react-sdk/src/components/views/elements/AppTile"; + +export interface IStartOpts { + accessToken: string; + widgetId: string; + roomId?: string; +} + +export async function loadApp(opts: IStartOpts) { + // TODO: Actually use `opts` to populate the widget + return ; +} diff --git a/src/vector/thin_widget/index.html b/src/vector/thin_widget/index.html new file mode 100644 index 0000000000..c147bc53ce --- /dev/null +++ b/src/vector/thin_widget/index.html @@ -0,0 +1,11 @@ + + + + + Thin Widget + + + +
+ + diff --git a/src/vector/thin_widget/index.scss b/src/vector/thin_widget/index.scss new file mode 100644 index 0000000000..f59c3ee874 --- /dev/null +++ b/src/vector/thin_widget/index.scss @@ -0,0 +1,44 @@ +/* +Copyright 2021 New Vector Ltd. + +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. +*/ + +// TODO: Match the user's theme: https://github.com/vector-im/element-web/issues/12794 + +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 400; + src: url('~matrix-react-sdk/res/fonts/Nunito/Nunito-Regular.ttf') format('truetype'); +} + +$dark-fg: #edf3ff; +$dark-bg: #363c43; +$light-fg: #2e2f32; +$light-bg: #fff; +body { + font-family: Nunito, Arial, Helvetica, sans-serif; + background-color: $dark-bg; + color: $dark-fg; +} + +body.theme-light { + background-color: $light-bg; + color: $light-fg; +} + +body, html { + padding: 0; + margin: 0; +} diff --git a/src/vector/thin_widget/index.ts b/src/vector/thin_widget/index.ts new file mode 100644 index 0000000000..bf9b5d7131 --- /dev/null +++ b/src/vector/thin_widget/index.ts @@ -0,0 +1,78 @@ +/* +Copyright 2021 New Vector Ltd. + +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. +*/ + +// We have to trick webpack into loading our CSS for us. +require("./index.scss"); +import * as qs from 'querystring'; +import { settled } from "../promise_utils"; +import ReactDOM from 'react-dom'; + +// The widget's options are encoded into the fragment to avoid leaking info to the server. The widget +// spec on the other hand requires the widgetId and parentUrl to show up in the regular query string. +const widgetQuery = qs.parse(window.location.hash.substring(1)); +const qsParam = (name: string, optional = false): string => { + if (!optional && (!widgetQuery[name] || typeof (widgetQuery[name]) !== 'string')) { + throw new Error(`Expected singular ${name} in query string`); + } + return widgetQuery[name] as string; +}; + +const accessToken = qsParam("accessToken"); +const roomId = qsParam("roomId", true); +const widgetId = qsParam("widgetId"); // state_key or account data key + +(async function() { + const { + rageshakePromise, + preparePlatform, + loadOlm, // to handle timelines + loadLanguage, + loadTheme, + showError, + _t, + } = await import( + /* webpackChunkName: "thin-wrapper-init" */ + /* webpackPreload: true */ + "../init"); + + try { + // give rageshake a chance to load/fail, we don't actually assert rageshake loads, we allow it to fail if no IDB + console.log("Waiting for rageshake..."); + await settled(rageshakePromise); + + console.log("Running startup..."); + await loadOlm(); + preparePlatform(); + await loadTheme(); + await loadLanguage(); + + // Now we can start our custom code + console.log("Loading app..."); + const module = await import( + /* webpackChunkName: "thin-wrapper-app" */ + /* webpackPreload: true */ + "./app"); + window.matrixChat = ReactDOM.render(await module.loadApp({accessToken, roomId, widgetId}), + document.getElementById('matrixchat')); + } catch (err) { + console.error(err); + // Like the compatibility page, AWOOOOOGA at the user + // This uses the default brand since the app config is unavailable. + await showError(_t("Your Element is misconfigured"), [ + err.translatedMessage || _t("Unexpected error preparing the app. See console for details."), + ]); + } +})(); diff --git a/webpack.config.js b/webpack.config.js index 25613a379b..653c7f1243 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -40,6 +40,7 @@ module.exports = (env, argv) => { "mobileguide": "./src/vector/mobile_guide/index.js", "jitsi": "./src/vector/jitsi/index.ts", "usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js", + "thinwidget": "./src/vector/thin_widget/index.ts", // CSS themes "theme-legacy": "./node_modules/matrix-react-sdk/res/themes/legacy-light/css/legacy-light.scss", @@ -312,7 +313,7 @@ module.exports = (env, argv) => { // HtmlWebpackPlugin will screw up our formatting like the names // of the themes and which chunks we actually care about. inject: false, - excludeChunks: ['mobileguide', 'usercontent', 'jitsi'], + excludeChunks: ['mobileguide', 'usercontent', 'jitsi', 'thinwidget'], minify: argv.mode === 'production', vars: { og_image_url: og_image_url, @@ -327,6 +328,14 @@ module.exports = (env, argv) => { chunks: ['jitsi'], }), + // This is a small thin wrapper for widgets (popout; isolated stack) + new HtmlWebpackPlugin({ + template: './src/vector/thin_widget/index.html', + filename: 'thin_widget.html', + minify: argv.mode === 'production', + chunks: ['thinwidget'], + }), + // This is the mobile guide's entry point (separate for faster mobile loading) new HtmlWebpackPlugin({ template: './src/vector/mobile_guide/index.html',