Compare commits

...
Sign in to create a new pull request.

6 commits

Author SHA1 Message Date
Travis Ralston
bdd34120f0 Render the widget fully from room state 2021-02-14 22:16:43 -07:00
Travis Ralston
0c6c0f32dd Debugging 2021-02-14 21:23:27 -07:00
Travis Ralston
447db3df62 Fix theme support 2021-02-14 21:23:23 -07:00
Travis Ralston
24a695e9a3 Fix merge 2021-02-14 21:16:03 -07:00
Travis Ralston
8c4b8f281c Merge branch 'develop' into travis/thin-widget-wrapper 2021-02-14 21:15:34 -07:00
Travis Ralston
3456b4c58a WIP concept for a "thin widget" wrapper 2021-01-11 23:42:49 -07:00
7 changed files with 266 additions and 17 deletions

View file

@ -28,16 +28,7 @@ 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';
async function settled(...promises: Array<Promise<any>>) {
for (const prom of promises) {
try {
await prom;
} catch (e) {
console.error(e);
}
}
}
import {settled} from "./promise_utils";
function checkBrowserFeatures() {
if (!window.Modernizr) {

View file

@ -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<Promise<any>>) {
for (const prom of promises) {
try {
await prom;
} catch (e) {
console.error(e);
}
}
}

View file

@ -0,0 +1,45 @@
/*
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';
import AppTile from "matrix-react-sdk/src/components/views/elements/AppTile";
import { IWidget } from "matrix-widget-api";
import MatrixClientContext from "matrix-react-sdk/src/contexts/MatrixClientContext";
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
// 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;
export interface IStartOpts {
widgetId: string;
roomId?: string;
}
export async function loadApp(widget: IWidget) {
return (
<MatrixClientContext.Provider value={MatrixClientPeg.get()}>
<div id="mx_ThinWrapper_container">
<AppTile
app={widget}
fullWidth={true}
userId={MatrixClientPeg.get().getUserId()}
userWidget={false}
/>
</div>
</MatrixClientContext.Provider>
);
}

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Thin Widget</title>
<meta name="theme-color" content="#ffffff">
</head>
<body>
<noscript>Sorry, this requires JavaScript to be enabled.</noscript> <!-- TODO: Translate this? -->
<section id="matrixchat" style="height: 100%; overflow: auto;" class="notranslate"></section>
</body>
</html>

View file

@ -0,0 +1,58 @@
/*
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;
}
#mx_ThinWrapper_container {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
.mx_AppTileFullWidth {
width: unset !important;
height: calc(100% - 10px); // 5px top + bottom borders on the AppTile
margin: 0;
}
}

View file

@ -0,0 +1,105 @@
/*
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';
import { StopGapWidgetDriver, WidgetRenderMode } from "matrix-react-sdk/src/stores/widgets/StopGapWidgetDriver";
import WidgetUtils from "matrix-react-sdk/src/utils/WidgetUtils";
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
// 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(2));
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 homeserverUrl = qsParam("hsUrl");
const roomId = qsParam("roomId", true);
const widgetId = qsParam("widgetId"); // state_key or account data key
// TODO: clear href so people don't accidentally copy/paste it
//window.location.hash = '';
(async function() {
const {
rageshakePromise,
preparePlatform,
loadSkin,
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...");
StopGapWidgetDriver.RENDER_MODE = WidgetRenderMode.ThinWrapper;
await loadSkin();
await loadOlm();
preparePlatform();
await MatrixClientPeg.shim(homeserverUrl, accessToken);
await loadTheme();
await loadLanguage();
console.log("Locating widget...");
const stateEvent = await MatrixClientPeg.get()._http.authedRequest(
undefined, "GET",
`/rooms/${encodeURIComponent(roomId)}/state/im.vector.modular.widgets/${encodeURIComponent(widgetId)}`,
undefined, undefined, {},
);
if (!stateEvent?.url) {
throw new Error("Invalid widget");
}
const app = WidgetUtils.makeAppConfig(
widgetId,
stateEvent,
MatrixClientPeg.get().getUserId(), // assume we are the sender
roomId,
widgetId);
// 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(app),
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."),
]);
}
})();

View file

@ -31,6 +31,15 @@ module.exports = (env, argv) => {
const reactSdkSrcDir = path.resolve(require.resolve("matrix-react-sdk/package.json"), '..', 'src');
const jsSdkSrcDir = path.resolve(require.resolve("matrix-js-sdk/package.json"), '..', 'src');
const themeBundles = {
"theme-legacy": "./node_modules/matrix-react-sdk/res/themes/legacy-light/css/legacy-light.scss",
"theme-legacy-dark": "./node_modules/matrix-react-sdk/res/themes/legacy-dark/css/legacy-dark.scss",
"theme-light": "./node_modules/matrix-react-sdk/res/themes/light/css/light.scss",
"theme-dark": "./node_modules/matrix-react-sdk/res/themes/dark/css/dark.scss",
"theme-light-custom": "./node_modules/matrix-react-sdk/res/themes/light-custom/css/light-custom.scss",
"theme-dark-custom": "./node_modules/matrix-react-sdk/res/themes/dark-custom/css/dark-custom.scss",
};
return {
...development,
@ -40,14 +49,10 @@ 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",
"theme-legacy-dark": "./node_modules/matrix-react-sdk/res/themes/legacy-dark/css/legacy-dark.scss",
"theme-light": "./node_modules/matrix-react-sdk/res/themes/light/css/light.scss",
"theme-dark": "./node_modules/matrix-react-sdk/res/themes/dark/css/dark.scss",
"theme-light-custom": "./node_modules/matrix-react-sdk/res/themes/light-custom/css/light-custom.scss",
"theme-dark-custom": "./node_modules/matrix-react-sdk/res/themes/dark-custom/css/dark-custom.scss",
...themeBundles,
},
optimization: {
@ -313,7 +318,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,
@ -328,6 +333,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', ...Object.keys(themeBundles)],
}),
// This is the mobile guide's entry point (separate for faster mobile loading)
new HtmlWebpackPlugin({
template: './src/vector/mobile_guide/index.html',