A music player that connects to your cloud/distributed storage. diffuse.sh
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384import { html, render } from "lit-html";import { classMap } from "lit-html/directives/class-map.js";import { keyed } from "lit-html/directives/keyed.js";import { marked } from "marked";import { unsafeHTML } from "lit-html/directives/unsafe-html.js";
import * as FacetCategory from "~/common/facets/category.js";import { batch, effect, signal } from "~/common/signal.js";
import { nothing } from "~/common/element.js";
import { deleteFacet, toggleFacetEnabled, toggleFacetFavourite } from "./crud.js";import { output } from "./output.js";import { openAddFromURIModal } from "./from-uri.js";
// Signalsconst FILTER_STORAGE_KEY = "diffuse/dashboard/filter";const storedFilter = localStorage.getItem(FILTER_STORAGE_KEY);const activeFilter = signal( storedFilter === "prelude" || storedFilter === "interface" || storedFilter === "base" ? storedFilter : "all",);
effect(() => { localStorage.setItem(FILTER_STORAGE_KEY, activeFilter.get());});
const UNFAVOURITE_COLLAPSED_KEY = "diffuse/dashboard/unfavourite-collapsed";const unfavouriteCollapsed = signal( localStorage.getItem(UNFAVOURITE_COLLAPSED_KEY) === "true",);
effect(() => { localStorage.setItem( UNFAVOURITE_COLLAPSED_KEY, String(unfavouriteCollapsed.get()), );});
/** @param {string} filter */function setFilter(filter) { const url = new URL(location.href); url.searchParams.delete("filter"); history.replaceState(null, "", url); batch(() => { activeFilter.set(filter); unfavouriteCollapsed.set(false); });}
/** * @import OutputOrchestrator from "~/components/orchestrator/output/element.js"; * @import {Facet} from "~/definitions/types.d.ts"; */
const emptyFacetsList = () => html` <p> <span> You haven't saved anything yet. Add a facet by browsing the <a href="featured/" >featured ones</a> or any of the other categories. You can click the toggle to quickly add or remove from your collection. </span> </p> `;
////////////////////////////////////////////// LIST////////////////////////////////////////////
/** @type {() => void | undefined} */let stopMonitor;
/** */export async function renderList() { if (stopMonitor) stopMonitor(); activeFilter.set((() => { const param = new URL(location.href).searchParams.get("filter"); if ( param === "prelude" || param === "interface" || param === "base" || param === "all" ) return param; const stored = localStorage.getItem(FILTER_STORAGE_KEY); return stored === "prelude" || stored === "interface" || stored === "base" ? stored : "all"; })());
/** @type {HTMLElement | null} */ const listEl = document.querySelector("#list"); if (!listEl) throw new Error("List element not found");
if (listEl.getAttribute("data-rendered") === "f") { listEl.innerHTML = ""; listEl.removeAttribute("data-rendered"); }
const out = await output();
stopMonitor = effect(() => { _renderList(out, listEl); });}
/** * @param {OutputOrchestrator} output * @param {HTMLElement} listEl */function _renderList(output, listEl) { const facetsCol = output.facets.collection();
if (facetsCol.state !== "loaded") { const loading = html` <div class="with-icon" style="font-size: var(--fs-sm)"> <i class="ph-bold ph-spinner animate-spin"></i> Loading your software </div> `;
render(loading, listEl); return; }
const filter = activeFilter.get();
/** @param {Facet} a @param {Facet} b */ const sortByName = (a, b) => a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) || a.id.localeCompare(b.id);
const filtered = facetsCol.state === "loaded" ? [...facetsCol.data].filter((c) => filter === "base" ? !!c.tags?.includes("base") : (filter === "all" || (filter === "prelude" ? c.kind === "prelude" : c.kind !== "prelude")) && !c.tags?.includes("base") ) : [];
const favouriteCol = filtered.filter((c) => c.favourite).sort(sortByName); const unfavouriteCol = filtered.filter((c) => !c.favourite).sort(sortByName); const col = favouriteCol.concat(unfavouriteCol);
const selected = output.selected(); const outputLabel = selected?.label ?? selected?.getAttribute?.("label") ?? "Local storage";
const filterBar = html` <div class="grid-filter"> <span class="grid-filter--label">Filter by</span> <button class="button--border button--tiny ${filter === "all" ? "" : "button--transparent"}" @click="${() => setFilter("all")}" > All </button> <button class="button--border button--tiny button--bg-twist-4 button--tr-twist-4 ${filter === "prelude" ? "" : "button--transparent"}" @click="${() => setFilter("prelude")}" > Features </button> <button class="button--border button--tiny button--bg-twist-2 button--tr-twist-2 ${filter === "interface" ? "" : "button--transparent"}" @click="${() => setFilter("interface")}" > Interfaces </button>
<button class="button--border button--tiny ${filter === "base" ? "" : "button--transparent"}" title="Show the hidden essential features" @click="${() => setFilter("base")}" > Base </button>
<span class="divider"></span>
<button class="button--border button--tiny button--bg-accent button--tr-accent button--transparent" @click="${() => openAddFromURIModal()}" > <span class="with-repositioned-icon"> <i class="ph-fill ph-plus-circle"></i> <span class="button__supplementary-text">Add from URI</span> </span> </button>
<div style="flex: 1"></div>
<span class="grid-filter--label grid-filter--label-output" >Userdata from</span> <span class="grid-filter--output">${outputLabel}</span> </div> `;
/** @param {Facet[]} items */ const renderItems = (items) => items.map((c) => { const color = FacetCategory.color(c);
const title = c.kind === "prelude" ? html` <span style="display: inline-block; padding: var(--space-3xs) 0"> ${c.name} </span> ` : html` <a href="l/?id=${c.id}" style="display: inline-block; padding: var(--space-3xs) 0" > ${c.name} </a> `;
return keyed( c.id, html` <li class="grid-item" style="--grid-item-color: ${color}" ?data-disabled="${!(c.enabled ?? true)}" > <div class="grid-item__contents"> <div class="grid-item__title" style="color: ${color}"> ${title} </div> <div class="list-description"> <div> ${c.description?.trim().length ? unsafeHTML( marked.parse(c.description, { async: false }), ) : nothing} </div> <div style="opacity: 0.55"> ${c.uri && !c.resources ? html` <span class="with-icon"> <i class="ph-fill ph-binoculars"></i> <span>Tracking the original <a href="${c .uri}">URI</a></span> </span> ` : html` <span class="with-icon"> <i class="ph-fill ph-code-simple"></i> <span>Custom code</span> </span> `} </div> </div> </div>
<div class="grid-item__menu ${classMap({ "grid-item__menu--active": c.enabled ?? true, })}"> <button class="button--transparent" title="${(c.enabled ?? true) ? c.kind === "prelude" ? "Disable" : "Dim" : c.kind === "prelude" ? "Enable" : "Light"}" @click="${toggleFacetEnabled({ id: c.id })}" > <i class="${(c.enabled ?? true) ? c.kind === "prelude" ? "ph-fill ph-lightning" : "ph-fill ph-eye" : c.kind === "prelude" ? "ph-bold ph-lightning-slash" : "ph-bold ph-eye-slash"}"></i> </button> <hr /> <button class="button--transparent" title="More actions" popovertarget="facet-menu-${c.id}" > <i class="ph-bold ph-dots-three-vertical"></i> </button> <div id="facet-menu-${c.id}" class="dropdown" popover> <a class="with-icon" href="#" @click="${(/** @type {MouseEvent} */ e) => { e.preventDefault(); if (!c.favourite) unfavouriteCollapsed.set(false); toggleFacetFavourite({ id: c.id })(); }}" > <i class="${c.favourite ? "ph-bold ph-star" : "ph-fill ph-star"}"></i> ${c.favourite ? "Unfavourite" : "Favourite"} </a> <a class="with-icon" href="create/?id=${encodeURIComponent(c.id)}" > <i class="ph-fill ph-code-block"></i> Edit </a> <a class="with-icon" href="#" @click="${(/** @type {MouseEvent} */ e) => { e.preventDefault(); deleteFacet({ id: c.id })(); }}" > <i class="ph-fill ph-skull"></i> Delete </a> </div> </div> </li> `, ); });
const collapsed = unfavouriteCollapsed.get() && favouriteCol.length > 0;
const h = col.length || filter !== "all" ? html` ${filterBar} ${favouriteCol.length ? html` <ul class="grid" style="margin: 0">${renderItems(favouriteCol)}</ul> ` : nothing} ${favouriteCol.length && unfavouriteCol.length ? html` <div class="grid-section-divider"> <button title="${collapsed ? "Show all" : "Only show favourites"}" @click="${() => unfavouriteCollapsed.set(!collapsed)}" > <i class="ph-fill ${collapsed ? "ph-caret-circle-down" : "ph-caret-circle-up"}"></i> </button> <hr class="hr--dashes" /> </div> ` : nothing} ${unfavouriteCol.length && !collapsed ? html` <ul class="grid" style="margin: 0">${renderItems(unfavouriteCol)}</ul> ` : nothing} ` : html` ${filterBar} ${emptyFacetsList()} `;
render(h, listEl);
setTimeout(() => { /** @type {HTMLElement | null} */ const l = listEl.querySelector(".grid-filter--label-output");
/** @type {HTMLElement | null} */ const o = listEl.querySelector(".grid-filter--output");
if (o && l) { l.style.opacity = "0.4"; o.style.opacity = "1"; } }, 250);}