A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

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

feat: add facet from uri

+184 -7
+158 -6
src/facets/common/you.js
··· 5 5 6 6 import * as Output from "~/common/output.js"; 7 7 import foundation from "~/common/facets/foundation.js"; 8 + import { effect } from "~/common/signal.js"; 9 + import { facetFromURI } from "~/common/facets/utils.js"; 8 10 import { nothing } from "~/common/element.js"; 9 11 10 - import { deleteFacet } from "./crud.js"; 12 + import { deleteFacet, saveFacet } from "./crud.js"; 13 + 14 + /** 15 + * @import {OutputElement} from "~/components/output/types.d.ts"; 16 + */ 17 + 18 + const ADD_FROM_URI_ITEM = html` 19 + <li 20 + class="grid-item" 21 + style="background: oklch(from var(--accent-twist-2) l c h / 0.0625);" 22 + > 23 + <div 24 + class="grid-item__contents" 25 + style="display: flex; align-items: center; justify-content: center;" 26 + > 27 + <button 28 + class="button--transparent with-icon" 29 + style="color: var(--accent-twist-2); font-size: var(--fs-sm); font-weight: 600;" 30 + @click="${openAddFromURIModal}" 31 + > 32 + <i class="ph-fill ph-plus-circle"></i> 33 + Add from URI 34 + </button> 35 + </div> 36 + </li> 37 + `; 11 38 12 39 const EMPTY_FACETS_LIST = html` 13 40 <div> ··· 15 42 </div> 16 43 `; 17 44 45 + //////////////////////////////////////////// 46 + // DIALOG 47 + //////////////////////////////////////////// 48 + 49 + function openAddFromURIModal() { 50 + let dialog = /** @type {HTMLDialogElement | null} */ ( 51 + document.getElementById("add-from-uri-dialog") 52 + ); 53 + 54 + if (!dialog) { 55 + dialog = /** @type {HTMLDialogElement} */ ( 56 + document.createElement("dialog") 57 + ); 58 + 59 + dialog.id = "add-from-uri-dialog"; 60 + dialog.style.cssText = 61 + "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0;"; 62 + 63 + render( 64 + html` 65 + <form id="add-from-uri-form"> 66 + <p> 67 + <strong>Load a facet from a URI.</strong> Currently supported URI schemes: 68 + <code>https</code>, <code>at</code> (AT Protocol) and <code>diffuse</code> 69 + (references internal facets). 70 + </p> 71 + 72 + <div style="display: flex; flex-direction: column; gap: var(--space-xs)"> 73 + <div> 74 + <label>Name</label> 75 + <input 76 + id="add-uri-name" 77 + type="text" 78 + placeholder="My Feature Name" 79 + required 80 + autocomplete="off" 81 + /> 82 + </div> 83 + <div> 84 + <label>URI</label> 85 + <input 86 + id="add-uri-uri" 87 + type="url" 88 + placeholder="at://..." 89 + required 90 + autocomplete="off" 91 + /> 92 + </div> 93 + </div> 94 + <div style="display: flex; gap: var(--space-xs); margin-top: var(--space-sm)"> 95 + <button class="button--bg-twist-2" type="submit">Add</button> 96 + <button class="button--bg-neutral" type="button" id="add-uri-cancel"> 97 + Cancel 98 + </button> 99 + </div> 100 + </form> 101 + `, 102 + dialog, 103 + ); 104 + 105 + document.body.appendChild(dialog); 106 + 107 + dialog.querySelector("#add-uri-cancel")?.addEventListener("click", () => { 108 + /** @type {HTMLDialogElement} */ (dialog).close(); 109 + }); 110 + 111 + dialog.querySelector("#add-from-uri-form")?.addEventListener( 112 + "submit", 113 + async (e) => { 114 + e.preventDefault(); 115 + const nameEl = /** @type {HTMLInputElement} */ ( 116 + dialog?.querySelector("#add-uri-name") 117 + ); 118 + const uriEl = /** @type {HTMLInputElement} */ ( 119 + dialog?.querySelector("#add-uri-uri") 120 + ); 121 + const name = nameEl?.value.trim() ?? ""; 122 + const uri = uriEl?.value.trim() ?? ""; 123 + if (!name || !uri) return; 124 + const facet = await facetFromURI({ name, uri }, { fetchHTML: false }); 125 + await saveFacet(facet); 126 + /** @type {HTMLDialogElement} */ (dialog).close(); 127 + }, 128 + ); 129 + } 130 + 131 + const nameEl = /** @type {HTMLInputElement} */ ( 132 + dialog.querySelector("#add-uri-name") 133 + ); 134 + const uriEl = /** @type {HTMLInputElement} */ ( 135 + dialog.querySelector("#add-uri-uri") 136 + ); 137 + if (nameEl) nameEl.value = ""; 138 + if (uriEl) uriEl.value = ""; 139 + 140 + dialog.showModal(); 141 + } 142 + 143 + //////////////////////////////////////////// 144 + // LIST 145 + //////////////////////////////////////////// 146 + 147 + /** @type {() => void | undefined} */ 148 + let stopMonitor; 149 + 18 150 /** */ 19 151 export async function renderList() { 152 + if (stopMonitor) stopMonitor(); 153 + 20 154 /** @type {HTMLElement | null} */ 21 155 const listEl = document.querySelector("#list"); 22 156 if (!listEl) throw new Error("List element not found"); 23 - listEl.innerHTML = ""; 157 + 158 + if (listEl.getAttribute("data-rendered") === "f") { 159 + listEl.innerHTML = ""; 160 + listEl.removeAttribute("data-rendered"); 161 + } 24 162 25 163 const output = foundation.orchestrator.output(); 164 + await Output.waitUntilLoaded(output.facets); 26 165 166 + stopMonitor = effect(() => { 167 + _renderList(output, listEl); 168 + }); 169 + } 170 + 171 + /** 172 + * @param {OutputElement} output 173 + * @param {HTMLElement} listEl 174 + */ 175 + function _renderList(output, listEl) { 27 176 if (output.facets.state() !== "loaded") { 28 177 const loading = html` 29 178 <div class="with-icon"> ··· 34 183 35 184 render(loading, listEl); 36 185 } 37 - 38 - await Output.waitUntilLoaded(output.facets); 39 186 40 187 const col = output.facets.collection().sort((a, b) => { 41 188 return a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()); ··· 106 253 </li> 107 254 `, 108 255 ) 109 - )} 256 + )} ${ADD_FROM_URI_ITEM} 110 257 </ul> 111 258 ` 112 - : EMPTY_FACETS_LIST; 259 + : html` 260 + ${EMPTY_FACETS_LIST} 261 + <ul class="grid" style="margin: var(--space-sm) 0 0"> 262 + ${ADD_FROM_URI_ITEM} 263 + </ul> 264 + `; 113 265 114 266 render(h, listEl); 115 267 }
+1 -1
src/facets/you.vto
··· 8 8 9 9 <div style="margin-top: var(--space-lg);"> 10 10 <section> 11 - <div id="list"> 11 + <div id="list" data-rendered="f"> 12 12 <div class="with-icon"> 13 13 <i class="ph-bold ph-spinner-gap"></i> 14 14 Loading items
+25
src/styles/diffuse/page.css
··· 133 133 } 134 134 135 135 /** 136 + * Dialog 137 + */ 138 + 139 + dialog { 140 + background: var(--bg-color); 141 + border: 2px solid var(--border-color); 142 + border-radius: var(--radius-md); 143 + padding: var(--space-lg); 144 + min-width: min(87.5dvw, var(--container-sm)); 145 + } 146 + 147 + /** 136 148 * Dropdown menu 137 149 */ 138 150 ··· 192 204 * Forms 193 205 */ 194 206 207 + label { 208 + display: block; 209 + font-size: var(--fs-sm); 210 + font-weight: 500; 211 + margin-bottom: var(--space-3xs); 212 + } 213 + 195 214 input, 196 215 textarea { 197 216 background: transparent; 198 217 border: 3px solid var(--form-color); 218 + border-radius: var(--radius-sm); 199 219 color: inherit; 200 220 font-size: var(--fs-sm); 201 221 padding: var(--space-2xs); ··· 225 245 226 246 .grid-item { 227 247 border: 1px solid var(--border-color); 248 + border-radius: var(--radius-md); 228 249 display: flex; 229 250 } 230 251 ··· 368 389 369 390 &.button--bg-twist-5 { 370 391 background-color: oklch(from var(--accent-twist-5) l c h / var(--button-bg-opacity)); 392 + } 393 + 394 + &.button--bg-neutral { 395 + background-color: oklch(from var(--text-color) l c h / calc(0.35 * var(--button-bg-opacity))); 371 396 } 372 397 373 398 &.button--border {