ATProto app badge generator for static web pages colddark.world/tools/badger/index.html
atproto web-app vanilla-js web-components oauth
at trunk 118 lines 3.0 kB view raw
1/* 2Toaster(-inator) - Easy to use toast message element/library thing. 3Copyright (C) 2026 Grant Mulholland <toasterinator@colddark.world> 4 5This program is free software: you can redistribute it and/or modify 6it under the terms of the GNU General Public License as published by 7the Free Software Foundation, either version 3 of the License, or 8(at your option) any later version. 9 10This program is distributed in the hope that it will be useful, 11but WITHOUT ANY WARRANTY; without even the implied warranty of 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13GNU General Public License for more details. 14 15You should have received a copy of the GNU General Public License 16along with this program. If not, see <https://www.gnu.org/licenses/>. 17*/ 18 19class ToasterElement extends HTMLElement { 20 #messages = document.createElement("div"); 21 constructor() { 22 super(); 23 this.#messages.id = "messages"; 24 } 25 connectedCallback() { 26 const shadow = this.attachShadow({ mode: "open" }); 27 28 const style = document.createElement("style"); 29 style.textContent = ` 30 :host { 31 --space: 1ch; 32 } 33 #messages { 34 position: absolute; 35 left: var(--space); 36 bottom: var(--space); 37 transition: translate 0.5s; 38 } 39 .box { 40 background-color: #eee; 41 border: 0.5ch solid #ccc; 42 border-radius: 1ch; 43 margin: var(--space); 44 padding: 1ch; 45 position: relative; 46 width: fit-content; 47 } 48 .box span { 49 padding-inline: 1ch; 50 } 51 @media (prefers-color-scheme: dark) { 52 .box { 53 background-color: #444; 54 border-color: #222; 55 } 56 } 57 .error { 58 border-color: indianred; 59 } 60 .info { 61 border-color: dodgerblue; 62 } 63 `; 64 65 shadow.appendChild(style); 66 shadow.appendChild(this.#messages); 67 } 68 #message(type, msg) { 69 const el = document.createElement("div"); 70 el.classList.add(type, "box"); 71 72 const btn = document.createElement("button"); 73 btn.textContent = "×"; 74 btn.classList.add("close-button"); 75 btn.addEventListener("click", ()=>{ 76 el.remove(); 77 }); 78 79 const text = document.createElement("span"); 80 text.textContent = msg; 81 82 el.appendChild(btn); 83 el.appendChild(text); 84 85 const oldTop = this.#messages.getBoundingClientRect().top; 86 this.#messages.appendChild(el); 87 const newTop = this.#messages.getBoundingClientRect().top; 88 this.#messages.animate([ 89 {translate: `0 ${oldTop-newTop}px`}, 90 {translate: "0 0"} 91 ], {duration: 500, iterations: 1, easing: "ease"}); 92 } 93 info(msg) { 94 this.#message("info", msg); 95 } 96 error(msg) { 97 this.#message("error", msg); 98 } 99} 100 101if (!customElements.get("toaster-inator")) { 102 customElements.define( 103 "toaster-inator", 104 ToasterElement 105 ); 106} 107 108let instances = document.getElementsByTagName("toaster-inator"); 109if (instances.length == 0) { 110 instances = [document.createElement("toaster-inator")]; 111 document.body.appendChild(instances[0]); 112} 113/** 114 * @type {ToasterElement} Automatically created toaster instance. 115 */ 116export const toaster = instances[0]; 117// call it a crime if you want, it's handy 118globalThis.toaster = toaster;