This isn't using the neat theme stuff over the widget API
+
+
+
diff --git a/src/vector/dummy_widget/index.scss b/src/vector/dummy_widget/index.scss
new file mode 100644
index 0000000000..07c1871c7e
--- /dev/null
+++ b/src/vector/dummy_widget/index.scss
@@ -0,0 +1,50 @@
+/*
+Copyright 2020 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/riot-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');
+}
+
+body {
+ font-family: Nunito, Arial, Helvetica, sans-serif;
+ background-color: #181b21;
+ color: #edf3ff;
+}
+
+body, html {
+ padding: 0;
+ margin: 0;
+ text-align: center;
+ padding-top: 30px;
+}
+
+button {
+ // A mix of AccessibleButton styles
+ cursor: pointer;
+ padding: 7px 18px;
+ text-align: center;
+ border-radius: 4px;
+ display: inline-block;
+ font-size: 14px;
+ color: #ffffff;
+ background-color: #03b381;
+ border: 0;
+}
diff --git a/src/vector/dummy_widget/index.ts b/src/vector/dummy_widget/index.ts
new file mode 100644
index 0000000000..064ab18d4c
--- /dev/null
+++ b/src/vector/dummy_widget/index.ts
@@ -0,0 +1,54 @@
+/*
+Copyright 2020 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 { Capability, KnownWidgetActions, WidgetApi } from 'matrix-react-sdk/src/widgets/WidgetApi';
+
+let widgetApi: WidgetApi;
+(async function() {
+ try {
+ // 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 query = Object.assign({}, qs.parse(window.location.search.substring(1)), widgetQuery);
+ const qsParam = (name: string, optional = false): string => {
+ if (!optional && (!query[name] || typeof (query[name]) !== 'string')) {
+ throw new Error(`Expected singular ${name} in query string`);
+ }
+ return query[name];
+ };
+
+ // Set this up as early as possible because Element will be hitting it almost immediately.
+ widgetApi = new WidgetApi(qsParam('parentUrl'), qsParam('widgetId'), []);
+
+ widgetApi.on(KnownWidgetActions.ClosedWidgetResponse, req => {
+ document.getElementById("answer").innerText = "RESPONSE FROM TEMP WIDGET: " + JSON.stringify(req.data);
+ });
+
+ document.getElementById("demoBtn").onclick = () => {
+ const url = new URL(window.location.href);
+ url.pathname = "/theme_widget.html";
+ url.search = "";
+ widgetApi.openTempWidget(url.toString(), {question: "Answer to everything?"});
+ };
+ } catch (e) {
+ console.error("Error setting up Jitsi widget", e);
+ document.getElementById("widgetActionContainer").innerText = "Failed to load Jitsi widget";
+ }
+})();
diff --git a/src/vector/theme_widget/index.html b/src/vector/theme_widget/index.html
new file mode 100644
index 0000000000..4edba67241
--- /dev/null
+++ b/src/vector/theme_widget/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+ Theme Widget
+
+
+
Example Theme Widget
+
+
Button Decorations
+
+
+
+
+
+
Actual Dialog Controls (closes the dialog)
+
+
+
+
Metadata
+
+
+
+
+
diff --git a/src/vector/theme_widget/index.scss b/src/vector/theme_widget/index.scss
new file mode 100644
index 0000000000..b437bdfd39
--- /dev/null
+++ b/src/vector/theme_widget/index.scss
@@ -0,0 +1,103 @@
+/*
+Copyright 2020 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/riot-web/issues/12794
+
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ unicode-range: U+0000-20e2,U+20e4-23ce,U+23d0-24c1,U+24c3-259f,U+25c2-2664,U+2666-2763,U+2765-2b05,U+2b07-2b1b,U+2b1d-10FFFF;
+ src: url("~matrix-react-sdk/res/fonts/Inter/Inter-Regular.woff2?v=3.13") format("woff2"),
+ url("~matrix-react-sdk/res/fonts/Inter/Inter-Regular.woff?v=3.13") format("woff");
+}
+
+body {
+ font-family: Inter, Arial, Helvetica, sans-serif;
+}
+
+body, html {
+ padding: 10px;
+ margin: 0;
+}
+
+body.client-element-web {
+ &.is-dark {
+ background-color: #181b21;
+ color: #edf3ff;
+ }
+
+ &:not(.is-dark) {
+ background-color: #edf3ff;
+ color: #181b21;
+ }
+
+ button {
+ cursor: pointer;
+ padding: 7px 18px;
+ text-align: center;
+ border-radius: 4px;
+ display: inline-block;
+ font-size: 14px;
+ color: #ffffff;
+ background-color: var(--accent-color, #03b381);
+ border: 0;
+ }
+
+ button.danger {
+ background-color: #ff4b55;
+ }
+
+ button.cancel {
+ background-color: transparent;
+ color: #03b381;
+ }
+}
+
+body.client-element-android {
+ &.is-dark {
+ background-color: #062600;
+ color: #c6ffc6;
+ }
+
+ &:not(.is-dark) {
+ background-color: #c6ffc6;
+ color: #181b21;
+ }
+
+ button {
+ cursor: pointer;
+ padding: 12px 24px;
+ text-align: center;
+ border-radius: 12px;
+ display: inline-block;
+ font-size: 15px;
+ font-weight: bold;
+ color: #ffffff;
+ background-color: var(--accent-color, #03b381);
+ border: 0;
+ }
+
+ button.danger {
+ background-color: #ff4b55;
+ }
+
+ button.cancel {
+ background-color: transparent;
+ color: var(--accent-color, #03b381);
+ }
+}
diff --git a/src/vector/theme_widget/index.ts b/src/vector/theme_widget/index.ts
new file mode 100644
index 0000000000..99f5a3bc93
--- /dev/null
+++ b/src/vector/theme_widget/index.ts
@@ -0,0 +1,63 @@
+/*
+Copyright 2020 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 { KnownWidgetActions, WidgetApi } from 'matrix-react-sdk/src/widgets/WidgetApi';
+
+let widgetApi: WidgetApi;
+(async function() {
+ try {
+ // 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 query = Object.assign({}, qs.parse(window.location.search.substring(1)), widgetQuery);
+ const qsParam = (name: string, optional = false): string => {
+ if (!optional && (!query[name] || typeof (query[name]) !== 'string')) {
+ throw new Error(`Expected singular ${name} in query string`);
+ }
+ return query[name];
+ };
+
+ // Set this up as early as possible because Element will be hitting it almost immediately.
+ widgetApi = new WidgetApi(qsParam('parentUrl'), qsParam('widgetId'), []);
+
+ widgetApi.on(KnownWidgetActions.UpdateThemeInfo, (themeInfo) => {
+ console.log("Got theme info: ", themeInfo);
+ document.getElementById("theme").innerText = "THEME: " + JSON.stringify(themeInfo.data);
+
+ const clientName = themeInfo.data.clientName || "element-web";
+ const isDark = themeInfo.data.isDark || false;
+ const accentColour = themeInfo.data.accentColor || "#03b381";
+
+ document.body.className = `client-${clientName} ${isDark ? 'is-dark' : ''}`;
+ document.body.style.setProperty("--accent-color", accentColour);
+ });
+ widgetApi.on(KnownWidgetActions.SendWidgetConfig, (config) => {
+ console.log("Got widget config: ", config);
+ document.getElementById("question").innerText = "INIT PARAMS: " + JSON.stringify(config.data);
+ });
+
+ document.getElementById("closeButton").onclick = () => {
+ widgetApi.closeWidget({answer: 42});
+ };
+ } catch (e) {
+ console.error("Error setting up Jitsi widget", e);
+ document.getElementById("widgetActionContainer").innerText = "Failed to load Jitsi widget";
+ }
+})();
diff --git a/webpack.config.js b/webpack.config.js
index 23db22890b..6689163c28 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -39,6 +39,8 @@ module.exports = (env, argv) => {
"indexeddb-worker": "./src/vector/indexeddb-worker.js",
"mobileguide": "./src/vector/mobile_guide/index.js",
"jitsi": "./src/vector/jitsi/index.ts",
+ "theme_widget": "./src/vector/theme_widget/index.ts",
+ "dummy_widget": "./src/vector/dummy_widget/index.ts",
"usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js",
// CSS themes
@@ -312,7 +314,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', 'theme_widget', 'dummy_widget'],
minify: argv.mode === 'production',
vars: {
og_image_url: og_image_url,
@@ -327,6 +329,22 @@ module.exports = (env, argv) => {
chunks: ['jitsi'],
}),
+ // This is the theme_widget wrapper (embedded, so isolated stack)
+ new HtmlWebpackPlugin({
+ template: './src/vector/theme_widget/index.html',
+ filename: 'theme_widget.html',
+ minify: argv.mode === 'production',
+ chunks: ['theme_widget'],
+ }),
+
+ // This is the dummy_widget wrapper (embedded, so isolated stack)
+ new HtmlWebpackPlugin({
+ template: './src/vector/dummy_widget/index.html',
+ filename: 'dummy_widget.html',
+ minify: argv.mode === 'production',
+ chunks: ['dummy_widget'],
+ }),
+
// This is the mobile guide's entry point (separate for faster mobile loading)
new HtmlWebpackPlugin({
template: './src/vector/mobile_guide/index.html',