From 7f8691585741d64bbe1a91c2c1548560d9ee1ffd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 5 Sep 2020 02:31:34 +0300 Subject: Initial commit --- web/index.js | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 web/index.js (limited to 'web/index.js') diff --git a/web/index.js b/web/index.js new file mode 100644 index 0000000..4c0dab5 --- /dev/null +++ b/web/index.js @@ -0,0 +1,175 @@ +// Copyright (c) 2020 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +import { html, render, Component } from "https://unpkg.com/htm/preact/index.mjs?module" + +// The base URL for fetching packs. The app will first fetch ${PACK_BASE_URL}/index.json, +// then ${PACK_BASE_URL}/${packFile} for each packFile in the packs object of the index.json file. +const PACKS_BASE_URL = "packs" +// This is updated from packs/index.json +let HOMESERVER_URL = "https://matrix-client.matrix.org" + +const makeThumbnailURL = mxc => `${HOMESERVER_URL}/_matrix/media/r0/thumbnail/${mxc.substr(6)}?height=128&width=128&method=scale` + +class App extends Component { + constructor(props) { + super(props) + this.state = { + packs: [], + loading: true, + error: null, + } + this.observer = null + } + + observeIntersection = intersections => { + for (const entry of intersections) { + const img = entry.target.children.item(0) + if (entry.isIntersecting) { + img.setAttribute("src", img.getAttribute("data-src")) + img.classList.add("visible") + } else { + img.removeAttribute("src") + img.classList.remove("visible") + } + } + } + + componentDidMount() { + fetch(`${PACKS_BASE_URL}/index.json`).then(async indexRes => { + if (indexRes.status >= 400) { + this.setState({ + loading: false, + error: indexRes.status !== 404 ? indexRes.statusText : null, + }) + return + } + const indexData = await indexRes.json() + HOMESERVER_URL = indexData.homeserver_url || HOMESERVER_URL + // TODO only load pack metadata when scrolled into view? + for (const packFile of indexData.packs) { + const packRes = await fetch(`${PACKS_BASE_URL}/${packFile}`) + const packData = await packRes.json() + this.setState({ + packs: [...this.state.packs, packData], + loading: false, + }) + } + }, error => this.setState({ loading: false, error })) + this.observer = new IntersectionObserver(this.observeIntersection, { + rootMargin: "100px", + threshold: 0, + }) + } + + componentDidUpdate() { + for (const elem of document.getElementsByClassName("sticker")) { + this.observer.observe(elem) + } + } + + componentWillUnmount() { + this.observer.disconnect() + } + + render() { + if (this.state.loading) { + return html`
<${Spinner} size=${80} green />
` + } else if (this.state.error) { + return html`
+

Failed to load packs

+

${this.state.error}

+
` + } else if (this.state.packs.length === 0) { + return html`

No packs found :(

` + } + return html`
+ ${this.state.packs.map(pack => html`<${Pack} id=${pack.id} ...${pack}/>`)} +
` + } +} + +const Spinner = ({ size = 40, noCenter = false, noMargin = false, green = false }) => { + let margin = 0 + if (!isNaN(+size)) { + size = +size + margin = noMargin ? 0 : `${Math.round(size / 6)}px` + size = `${size}px` + } + const noInnerMargin = !noCenter || !margin + const comp = html` +
+
+
+
+
+
+
+
+ ` + if (!noCenter) { + return html`
${comp}
` + } + return comp +} + +const Pack = ({ title, stickers }) => html`
+

${title}

+
+ ${stickers.map(sticker => html` + <${Sticker} key=${sticker["net.maunium.telegram.sticker"].id} content=${sticker}/> + `)} +
+
` + +const Sticker = ({ content }) => html`
sendSticker(content)}> + ${content.body} +
` + +function sendSticker(content) { + window.parent.postMessage({ + api: "fromWidget", + action: "m.sticker", + requestId: `sticker-${Date.now()}`, + widgetId, + data: { + name: content.body, + content, + }, + }, "*") +} + +let widgetId = null + +window.onmessage = event => { + if (!window.parent || !event.data) { + return + } + + const request = event.data + if (!request.requestId || !request.widgetId || !request.action || request.api !== "toWidget") { + return + } + + if (widgetId) { + if (widgetId !== request.widgetId) { + return + } + } else { + widgetId = request.widgetId + } + + window.parent.postMessage({ + ...request, + response: request.action === "capabilities" ? { + capabilities: ["m.sticker"], + } : { + error: { message: "Action not supported" }, + }, + }, event.origin) +} + +render(html`<${App} />`, document.body) -- cgit v1.2.3