diff --git a/src/vector/dummy_widget/index.html b/src/vector/dummy_widget/index.html new file mode 100644 index 0000000000..15428f7f6c --- /dev/null +++ b/src/vector/dummy_widget/index.html @@ -0,0 +1,12 @@ + + + + + Dummy Widget + + + +

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