Rewild Your Web
18
fork

Configure Feed

Select the types of activity you want to include in your feed.

ui: Add a toast component

webbeef a5e4ac84 e88359ff

+124
+2
ui/system/index.html
··· 14 14 <script src="./third_party/fend/fend_wasm.js"></script> 15 15 <script type="module" src="//shared.localhost:8888/theme.js"></script> 16 16 <script type="module" src="notification_panel.js"></script> 17 + <script type="module" src="toasts.js"></script> 17 18 <script type="module" src="index.js"></script> 18 19 </head> 19 20 <body> ··· 35 36 <notification-panel id="notification-panel"></notification-panel> 36 37 <div id="root"></div> 37 38 </main> 39 + <toast-manager id="toast-manager"></toast-manager> 38 40 <footer id="footer"> 39 41 </footer> 40 42 </body>
+10
ui/system/index.js
··· 142 142 143 143 // Update panels and badge 144 144 updateNotificationUIs(); 145 + 146 + // Show a toast for the notification 147 + if (notification.title) { 148 + toastManager?.add(notification.title); 149 + } 145 150 } 146 151 147 152 function removeNotificationByTag(tag) { ··· 171 176 // P2P pairing request handling 172 177 const pendingPairingRequests = new Map(); // id -> peer 173 178 const pairingApi = navigator.embedder.pairing; 179 + 180 + pairingApi.addEventListener("peerdiscovered", (e) => { 181 + const name = e.peer.displayName || e.peer.id; 182 + toastManager?.add(`${name} appeared`); 183 + }); 174 184 175 185 pairingApi.addEventListener("pairingrequest", (e) => { 176 186 const peer = e.peer;
+2
ui/system/index_mobile.html
··· 21 21 <script src="./third_party/fend/fend_wasm.js"></script> 22 22 <script type="module" src="//shared.localhost:8888/theme.js"></script> 23 23 <script type="module" src="notification_panel.js"></script> 24 + <script type="module" src="toasts.js"></script> 24 25 <script type="module" src="index.js"></script> 25 26 </head> 26 27 <body> ··· 43 44 <main> 44 45 <div id="root"></div> 45 46 </main> 47 + <toast-manager id="toast-manager"></toast-manager> 46 48 <footer id="footer"></footer> 47 49 </body> 48 50 </html>
+40
ui/system/toasts.css
··· 1 + /* SPDX-License-Identifier: AGPL-3.0-or-later */ 2 + 3 + :host { 4 + position: fixed; 5 + top: 0; 6 + left: 0; 7 + right: 0; 8 + z-index: var(--z-modal); 9 + display: flex; 10 + flex-direction: column; 11 + align-items: center; 12 + pointer-events: none; 13 + padding-top: env(safe-area-inset-top, var(--spacing-sm)); 14 + } 15 + 16 + .toast { 17 + background: var(--bg-surface); 18 + color: var(--color-text); 19 + font-family: var(--font-family-base); 20 + font-size: var(--font-size-menu); 21 + padding: var(--spacing-sm) var(--spacing-md); 22 + margin: var(--spacing-xs); 23 + border-radius: var(--radius-md); 24 + box-shadow: 0 2px 12px var(--color-shadow); 25 + max-width: 90vw; 26 + text-align: center; 27 + transform: translateY(-100%); 28 + opacity: 0; 29 + transition: transform 0.3s ease, opacity 0.3s ease; 30 + } 31 + 32 + .toast.visible { 33 + transform: translateY(0); 34 + opacity: 1; 35 + } 36 + 37 + .toast.hiding { 38 + transform: translateY(-100%); 39 + opacity: 0; 40 + }
+70
ui/system/toasts.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-or-later 2 + 3 + import { 4 + LitElement, 5 + html, 6 + css, 7 + } from "//shared.localhost:8888/third_party/lit/lit-all.min.js"; 8 + 9 + class ToastManager extends LitElement { 10 + static properties = { 11 + _toasts: { type: Array, state: true }, 12 + }; 13 + 14 + static styles = css` 15 + @import url("//system.localhost:8888/toasts.css"); 16 + `; 17 + 18 + constructor() { 19 + super(); 20 + this._toasts = []; 21 + this._nextId = 0; 22 + } 23 + 24 + add(text, duration = 1000) { 25 + const id = this._nextId++; 26 + this._toasts = [...this._toasts, { id, text }]; 27 + 28 + // Wait for render, then trigger the enter animation. 29 + this.updateComplete.then(() => { 30 + const el = this.shadowRoot.querySelector(`[data-id="${id}"]`); 31 + if (el) { 32 + requestAnimationFrame(() => el.classList.add("visible")); 33 + } 34 + }); 35 + 36 + // Schedule removal. 37 + setTimeout(() => this._dismiss(id), duration); 38 + } 39 + 40 + _dismiss(id) { 41 + const el = this.shadowRoot.querySelector(`[data-id="${id}"]`); 42 + if (!el) return; 43 + 44 + el.classList.remove("visible"); 45 + el.classList.add("hiding"); 46 + 47 + el.addEventListener( 48 + "transitionend", 49 + () => { 50 + this._toasts = this._toasts.filter((t) => t.id !== id); 51 + }, 52 + { once: true } 53 + ); 54 + } 55 + 56 + render() { 57 + return html`${this._toasts.map( 58 + (t) => html`<div class="toast" data-id="${t.id}">${t.text}</div>` 59 + )}`; 60 + } 61 + } 62 + 63 + customElements.define("toast-manager", ToastManager); 64 + 65 + // Expose a global convenience reference once the element is in the DOM. 66 + Object.defineProperty(window, "toastManager", { 67 + get() { 68 + return document.getElementById("toast-manager"); 69 + }, 70 + });