···2525`src/definitions/` are lexicons, JSON schemas that describe data in the system.
262627272828-## Themes
2929-3030-Like orchestrator components, these are compositions of elements. Unlike orchestrators however, it doesn't compose by the use of selectors, instead we write the custom elements as HTML and use the DOM as the composition layer. Alternatively, custom elements can be created in Javascript and then added to the DOM from there.
3131-3232-3328## Other directories
34293530- `src/common`: Common Javascript code shared by various components and/or themes.
3631- `src/styles`: Common CSS shared by themes, the index page or facets.
3732- `src/favicons`, `src/fonts`, `src/images` are binary assets for themes and the index page (`src/index.vto`)
3838-- `src/_components` and `src/_includes` are templates used in `.vto` templates, again themes and index page.
3333+- `src/_components` and `src/_includes` are templates used in `.vto` templates.
···11+---
22+layout: layouts/diffuse.vto
33+base: ../
44+title: Elements | Diffuse
55+66+styles:
77+ - styles/base.css
88+ - styles/diffuse/page.css
99+ - vendor/@phosphor-icons/bold/style.css
1010+ - vendor/@phosphor-icons/fill/style.css
1111+1212+scripts:
1313+ - index.js
1414+ - common/pages/version-upgrade.js
1515+1616+# ELEMENTS
1717+1818+configurators:
1919+ - url: "components/configurator/input/element.js"
2020+ title: "Input"
2121+ desc: "Allows for multiple inputs to be used at once."
2222+ - url: "components/configurator/output/element.js"
2323+ title: "Output"
2424+ desc: "Enables the user to configure a specific output. If no default output is set, it creates a temporary session by storing everything in memory."
2525+ - url: "components/configurator/scrobbles/element.js"
2626+ title: "Scrobbles"
2727+ desc: "Configure multiple scrobblers (music trackers)."
2828+2929+engines:
3030+ - url: "components/engine/audio/element.js"
3131+ title: "Audio"
3232+ desc: "Plays audio through audio elements."
3333+ - url: "components/engine/queue/element.js"
3434+ title: "Queue"
3535+ desc: "A queue for tracks."
3636+ - url: "components/engine/repeat-shuffle/element.js"
3737+ title: "Repeat & Shuffle"
3838+ desc: "Signals synced with local storage (classified by group) that decide if audio should be repeated and if the queue should be shuffled when filling it."
3939+ - url: "components/engine/scope/element.js"
4040+ title: "Scope"
4141+ desc: >
4242+ Signals that could influence the scope of a set of tracks.
4343+4444+input:
4545+ - url: "components/input/https/element.js"
4646+ title: "HTTPS"
4747+ desc: >
4848+ HTTPS URLs to audio files or streams.
4949+ - title: "HTTPS (JSON)"
5050+ desc: >
5151+ Generate tracks based on HTTPS servers that provide JSON (directory) listings.
5252+ todo: true
5353+ - url: "components/input/icecast/element.js"
5454+ title: "Icecast"
5555+ desc: >
5656+ Icecast internet radio streams. Fetches ICY metadata to populate track information.
5757+ - url: "components/input/local/element.js"
5858+ title: "Local"
5959+ desc: >
6060+ Audio files or directories from your local device, using the browser's File System Access API.
6161+ - url: "components/input/opensubsonic/element.js"
6262+ title: "Opensubsonic"
6363+ desc: >
6464+ Add any (open)subsonic server.
6565+ - url: "components/input/s3/element.js"
6666+ title: "S3"
6767+ desc: >
6868+ AWS S3 and services that provide the same surface API such as Cloudflare R2.
6969+ - title: "WebDAV"
7070+ desc: >
7171+ Add any WebDAV server.
7272+ todo: true
7373+7474+orchestrators:
7575+ - url: "components/orchestrator/auto-queue/element.js"
7676+ title: "Automatic Queue"
7777+ desc: >
7878+ Fill the queue automatically with non-manual items (shuffled or regular, based on repeat-shuffle engine).
7979+ - url: "components/orchestrator/favourites/element.js"
8080+ title: "Favourites"
8181+ desc: >
8282+ Mark tracks as favourites. Automatically creates an unordered 'Favourites' playlist.
8383+ - url: "components/orchestrator/input/element.js"
8484+ title: "Input"
8585+ desc: "**A default input configuration.** Contains all the inputs provided here."
8686+ - url: "components/orchestrator/media-session/element.js"
8787+ title: "Media Session"
8888+ desc: "Keeps the browser/os media session in sync with queue and audio state. Adds handlers for previous, next, seek to, etc."
8989+ - url: "components/orchestrator/offline/element.js"
9090+ title: "Offline"
9191+ desc: "Registers a service worker that makes the page available offline. Resources (except audio & video) are cached as they load and served from cache when offline."
9292+ - url: "components/orchestrator/output/element.js"
9393+ title: "Output"
9494+ desc: "**A default output configuration.** Contains all the outputs provided here along with the relevant transformers."
9595+ - url: "components/orchestrator/path-collections/element.js"
9696+ title: "Path Collections"
9797+ desc: "Wraps an output element to generate ephemeral playlists based on the first path segment of each track's URI. Ephemeral items are excluded from storage."
9898+ - url: "components/orchestrator/process-tracks/element.js"
9999+ title: "Process Inputs Into Tracks"
100100+ desc: "Whenever the cached tracks are initially loaded through the passed output element it will list tracks by using the passed input element. Afterwards it loops over all tracks and checks if metadata needs to be fetched. If anything has changed, it'll pass the results to the output element."
101101+ - url: "components/orchestrator/queue-audio/element.js"
102102+ title: "Queue ⭤ Audio"
103103+ desc: "Connects the given queue engine to the given audio engine."
104104+ - url: "components/orchestrator/scrobble-audio/element.js"
105105+ title: "Scrobble ⭤ Audio"
106106+ desc: "Connects the audio engine with a scrobbler element. Calls `nowPlaying` when a track starts playing and `scrobble` once the user has listened long enough."
107107+ - url: "components/orchestrator/sources/element.js"
108108+ title: "Sources"
109109+ desc: "Monitor tracks from the given output to form a list of sources based on the input's sources return value."
110110+ - url: "components/orchestrator/scoped-tracks/element.js"
111111+ title: "Scoped Tracks"
112112+ desc: "Supplies the tracks from the given output to the given search processor whenever the tracks collection changes. Additionally it can perform a search and other ways to reduce the scope of tracks based on the given scope engine. Provides a `tracks` signal similar to `output.tracks.collection`"
113113+114114+output:
115115+ - url: "components/output/polymorphic/indexed-db/element.js"
116116+ title: "Polymorphic / IndexedDB"
117117+ desc: "Stores output into the local indexedDB. Supports any type of data that indexedDB supports."
118118+ - url: "components/output/bytes/s3/element.js"
119119+ title: "Bytes / S3"
120120+ desc: >
121121+ Store output data on AWS S3 or compatible services such as Cloudflare R2.
122122+ - url: "components/output/raw/atproto/element.js"
123123+ title: "Raw / AT Protocol"
124124+ desc: >
125125+ Store your user data on the storage associated with your ATProtocol identity. Data is lexicon shaped by default so this element takes in that data directly without any transformations.
126126+127127+processors:
128128+ - url: "components/processor/artwork/element.js"
129129+ title: "Artwork"
130130+ desc: "Fetches cover art for a given set of tracks, stored locally in indexedDB. Checks the audio metadata first, then MusicBrainz and uses Last.fm as the fallback."
131131+ - url: "components/processor/metadata/element.js"
132132+ title: "Metadata"
133133+ desc: "Fetch audio metadata for a given set of tracks, adding to the `Track` object."
134134+ - url: "components/processor/search/element.js"
135135+ title: "Search"
136136+ desc: "Provides a way to search through a collection of tracks, powered by orama.js"
137137+138138+supplements:
139139+ - url: "components/supplement/last.fm/element.js"
140140+ title: "Last.fm Scrobbler"
141141+ - title: "ListenBrainz Scrobbler"
142142+ todo: true
143143+ - title: "Rocksky Scrobbler"
144144+ todo: true
145145+ - title: "Teal.fm Scrobbler"
146146+ todo: true
147147+148148+transformers:
149149+ - title: "Output / Bytes / Automerge"
150150+ desc: "Translate data to and from an Automerge CRDT."
151151+ url: "components/transformer/output/bytes/automerge/element.js"
152152+ todo: true
153153+ - title: "Output / Bytes / Cambria Lenses"
154154+ desc: "Uses the Cambria library to seamlessly translate between data schemas so that no data migration is needed."
155155+ todo: true
156156+ - title: "Output / Bytes / DASL Sync"
157157+ desc: "Syncs data between local and remote using CID-based diffing and performs union merges with tombstone tracking when both sides have diverged."
158158+ url: "components/transformer/output/bytes/dasl-sync/element.js"
159159+ - title: "Output / Bytes / JSON"
160160+ desc: "Raw data schema output ⇄ JSON Uint8Array."
161161+ url: "components/transformer/output/bytes/json/element.js"
162162+ - title: "Output / Raw / AT Protocol Sync"
163163+ desc: "Wraps an AT Protocol output with a local IndexedDB cache. Uses the repo revision to skip unnecessary fetches and performs union merges with tombstone tracking when both local and remote have diverged."
164164+ url: "components/transformer/output/raw/atproto-sync/element.js"
165165+ - title: "Output / Refiner / Default"
166166+ desc: "The task of a refiner transformer is to remove the output state that is not meant to be saved to storage. For example, ephemeral tracks; this transformer will keep them in memory, but they will not be present in the output. **Ideally this is part of every theme, but you may swap it out with another transformer that might provide better defaults.**"
167167+ url: "components/transformer/output/refiner/default/element.js"
168168+ - title: "Output / Refiner / Track URI Passkey"
169169+ desc: "Encrypts track URIs using a passkey-derived PRF key. On read, decrypts `encrypted://` URIs transparently; on write, re-encrypts all URIs before passing downstream. Tracks that cannot be decrypted are held separately and excluded from the visible collection."
170170+ url: "components/transformer/output/refiner/track-uri-passkey/element.js"
171171+ - title: "Output / String / JSON"
172172+ desc: "Raw data schema output ⇄ JSON UTF8 string."
173173+ url: "components/transformer/output/string/json/element.js"
174174+175175+# DEFINITIONS
176176+177177+definitions:
178178+ - title: "Output / Collaboration"
179179+ desc: >
180180+ Represents a collaboration between multiple collaborators on a subject, such as a playlist.
181181+ url: "definitions/output/collaboration.json"
182182+ - title: "Output / Facet"
183183+ desc: >
184184+ Facet pointer or HTML snippet.
185185+ url: "definitions/output/facet.json"
186186+ - title: "Output / Playlist Item"
187187+ desc: >
188188+ Represents a single item in a playlist. Tracks are matched based on the given criteria. A playlist is formed by grouping items by their playlist property.
189189+ url: "definitions/output/playlistItem.json"
190190+ - title: "Output / Playlist Item Bundle"
191191+ desc: >
192192+ A bundle of playlist items.
193193+ url: "definitions/output/playlistItemBundle.json"
194194+ - title: "Output / Progress"
195195+ desc: >
196196+ Used to track progress of (long) audio playback.
197197+ todo: true
198198+ - title: "Output / Track"
199199+ desc: >
200200+ Represents audio that can be played, or a placeholder for a source of tracks. Contains a URI that will resolve to the audio.
201201+ url: "definitions/output/track.json"
202202+ - title: "Output / Track Bundle"
203203+ desc: >
204204+ A bundle of tracks.
205205+ url: "definitions/output/trackBundle.json"
206206+207207+---
208208+209209+<header>
210210+ <div>
211211+ <div class="diffuse-logo-container">
212212+ <a href="./" style="display: inline-block;">
213213+ {{ await comp.diffuse.logo() }}
214214+ </a>
215215+ </div>
216216+ <p class="construct dither-mask">
217217+ Elements
218218+ </p>
219219+ <p>
220220+ Diffuse was built using these custom elements (aka. web components), consume these using the <a href="facets/#builder">build tool</a>, the Javascript <a href="https://jsr.io/@toko/diffuse">package</a>, or the linked Javascript files down below.
221221+ </p>
222222+ <ul class="table-of-contents">
223223+ <li><a href="elements/#configurators">Configurators</a></li>
224224+ <li><a href="elements/#engines">Engines</a></li>
225225+ <li><a href="elements/#input">Input</a></li>
226226+ <li><a href="elements/#orchestrators">Orchestrators</a></li>
227227+ <li><a href="elements/#output">Output</a></li>
228228+ <li><a href="elements/#processors">Processors</a></li>
229229+ <li><a href="elements/#supplements">Supplements</a></li>
230230+ <li><a href="elements/#transformers">Transformers</a></li>
231231+ <!---->
232232+ <li style="margin-top: var(--space-xs);"><a href="#definitions">Definitions</a></li>
233233+ </ul>
234234+ </div>
235235+ <div class="dither-mask filler"></div>
236236+</header>
237237+<main>
238238+ <!-- ELEMENTS -->
239239+ <section>
240240+ <div class="columns">
241241+ {{ await comp.element({
242242+ title: "Configurators",
243243+ items: configurators,
244244+ content: `
245245+ Elements that serve as an intermediate in order to make a particular kind of element configurable. In other words, these allow for an element to be swapped out with another that takes the same set of the actions and data output.
246246+ `
247247+ }) }}
248248+249249+ {{ await comp.element({
250250+ title: "Engines",
251251+ items: engines,
252252+ content: `
253253+ Elements with each a singular purpose and don't have any UI. There are specialised UI and orchestrator elements that control these.
254254+ `
255255+ }) }}
256256+257257+ {{ await comp.element({
258258+ title: "Input",
259259+ items: input,
260260+ content: `
261261+ Inputs are sources of audio tracks. Each track is an entry in the list of possible items to play. These can be files or streams, static or dynamic.
262262+ `
263263+ }) }}
264264+265265+ {{ await comp.element({
266266+ title: "Orchestrators",
267267+ items: orchestrators,
268268+ content: `
269269+ These too are element compositions. However, unlike themes, these are purely logical. Mostly exist in order to construct sensible defaults to use across themes and other compositions.
270270+ `
271271+ }) }}
272272+273273+ {{ await comp.element({
274274+ title: "Output",
275275+ items: output,
276276+ content: `
277277+ Output is application-derived data such as playlists. These elements can receive such data and keep it around. These are categorised by the type of data they ingest, or many types in the case of polymorphic. Optionally use transformers to convert output into the expected format.
278278+ `
279279+ }) }}
280280+281281+ {{ await comp.element({
282282+ title: "Processors",
283283+ items: processors,
284284+ content: `
285285+ These elements work with the output generated by the input elements to add more data to them, or process them in some other way.
286286+ `
287287+ }) }}
288288+289289+ {{ await comp.element({
290290+ title: "Supplements",
291291+ items: supplements,
292292+ content: `
293293+ Additional elements, such as scrobblers.
294294+ `
295295+ }) }}
296296+297297+ {{ await comp.element({
298298+ title: "Transformers",
299299+ items: transformers,
300300+ content: `
301301+ Transform data from one format or schema into another. See schema section below for more information. Just as configurators, these are intermediates and require to have the same set of actions as the element it targets.
302302+ `
303303+ }) }}
304304+ </div>
305305+ </section>
306306+307307+ <!-- DEFINITIONS -->
308308+ <section>
309309+ <h2 id="definitions">Definitions</h2>
310310+311311+ <p>All of the elements here are built with these data definitions in mind. That said, you can mix elements that use different definitions; you just have to put a transformer between them in order to translate between them, if needed.</p>
312312+313313+ {{ await comp.list({ items: definitions }) }}
314314+ </section>
315315+</main>
···11+---
22+layout: layouts/kitchen.vto
33+base: ../
44+title: Guide | Diffuse
55+---
66+77+<h1 hidden>Guide</h1>
88+99+<div class="columns">
1010+ <section>
1111+ <h3>Tutorial</h3>
1212+1313+ <p>
1414+ <strong>Diffuse is not your typical streaming service, we have to add sources of audio so we have stuff to play.</strong> This button below adds some demo content, so you can experiment with the software right away.
1515+ </p>
1616+1717+ <p>
1818+ <button id="add-sample-content" class="button--bg-twist-2">
1919+ <span>Add sample content</span>
2020+ </button>
2121+ </p>
2222+2323+ <p>
2424+ Now we should explore what is possible with our audio. Because Diffuse is cooperative and malleable software, our interface can look like anything, and we can pick the features we like. That might sound overwhelming, so let's keep it simple for now.
2525+ </p>
2626+2727+ <p>
2828+ <strong>Let's pick an interface</strong> that automatically puts audio from our collection into the queue, and another to play what got put into the queue.
2929+ </p>
3030+3131+ <p>
3232+ <span class="button-row">
3333+ <a class="button button--bg-twist-2" href="{{ ('facets/playback/auto-queue/index.html') |> facetLoaderURL }}" target="_blank">
3434+ <span class="with-icon">
3535+ <i class="ph-fill ph-number-circle-one"></i>
3636+ Fill up queue
3737+ </span>
3838+ </a>
3939+ <a class="button button--bg-twist-2" href="{{ ('themes/blur/artwork-controller/facet/index.html') |> facetLoaderURL }}" target="_blank">
4040+ <span class="with-icon">
4141+ <i class="ph-fill ph-number-circle-two"></i>
4242+ Play audio
4343+ </span>
4444+ </a>
4545+ </span>
4646+ </p>
4747+4848+ <p>
4949+ <em>So you said I could pick the features and interfaces that I liked, how does that work?</em>
5050+ </p>
5151+5252+ <p>
5353+ To do that, we have to look at the other pages shown in the navigation here, such as the <a href="featured/">featured page</a>. There you'll be able to browse through all the features and interfaces that are provided by Diffuse.
5454+ </p>
5555+5656+ <p>
5757+ <strong>To use a feature, you click the toggle to enable it</strong>, this will add it to your software. Interfaces can be added too, but it's not required, you can try them out right away by clicking the link in their title. For interfaces that are more like traditional web applications, delivering more encompassing experiences, look at <a href="themes/">themes</a>.
5858+ </p>
5959+6060+ <p>
6161+ TODO: explain adding your own inputs, syncing user data, etc.
6262+ </p>
6363+ </section>
6464+6565+ <section>
6666+ <h3>Concept</h3>
6767+6868+ <p>
6969+ Diffuse is unlike traditional software; instead of combining several features into a single user interface and producing data output, we do the opposite, we start with the data and work our way up from there.
7070+ </p>
7171+7272+ <p>
7373+ <strong>It provides every user the ability to choose which features and interfaces they want to layer on top of their data.</strong>
7474+ </p>
7575+7676+ <p>
7777+ These features and interfaces are housed into units that we call "facets". They consist of <a href="elements/">Diffuse elements</a> that are connected, they broadcast their state and your data that has been updated.
7878+ </p>
7979+8080+ <p>
8181+ Finally, facets are just regular web pages so they can live wherever. We save them to the user-data storage that's configured and give the user the option to share it. This means that you can load features and interfaces from other people, <strong>building software cooperatively</strong>.
8282+ </p>
8383+ </section>
8484+</div>
···1111 - index.js
1212 - common/pages/version-upgrade.js
13131414-# ELEMENTS
1515-1616-configurators:
1717- - url: "components/configurator/input/element.js"
1818- title: "Input"
1919- desc: "Allows for multiple inputs to be used at once."
2020- - url: "components/configurator/output/element.js"
2121- title: "Output"
2222- desc: "Enables the user to configure a specific output. If no default output is set, it creates a temporary session by storing everything in memory."
2323- - url: "components/configurator/scrobbles/element.js"
2424- title: "Scrobbles"
2525- desc: "Configure multiple scrobblers (music trackers)."
2626-2727-engines:
2828- - url: "components/engine/audio/element.js"
2929- title: "Audio"
3030- desc: "Plays audio through audio elements."
3131- - url: "components/engine/queue/element.js"
3232- title: "Queue"
3333- desc: "A queue for tracks."
3434- - url: "components/engine/repeat-shuffle/element.js"
3535- title: "Repeat & Shuffle"
3636- desc: "Signals synced with local storage (classified by group) that decide if audio should be repeated and if the queue should be shuffled when filling it."
3737- - url: "components/engine/scope/element.js"
3838- title: "Scope"
3939- desc: >
4040- Signals that could influence the scope of a set of tracks.
4141-4242-input:
4343- - url: "components/input/https/element.js"
4444- title: "HTTPS"
4545- desc: >
4646- HTTPS URLs to audio files or streams.
4747- - title: "HTTPS (JSON)"
4848- desc: >
4949- Generate tracks based on HTTPS servers that provide JSON (directory) listings.
5050- todo: true
5151- - url: "components/input/icecast/element.js"
5252- title: "Icecast"
5353- desc: >
5454- Icecast internet radio streams. Fetches ICY metadata to populate track information.
5555- - url: "components/input/local/element.js"
5656- title: "Local"
5757- desc: >
5858- Audio files or directories from your local device, using the browser's File System Access API.
5959- - url: "components/input/opensubsonic/element.js"
6060- title: "Opensubsonic"
6161- desc: >
6262- Add any (open)subsonic server.
6363- - url: "components/input/s3/element.js"
6464- title: "S3"
6565- desc: >
6666- AWS S3 and services that provide the same surface API such as Cloudflare R2.
6767- - title: "WebDAV"
6868- desc: >
6969- Add any WebDAV server.
7070- todo: true
7171-7272-orchestrators:
7373- - url: "components/orchestrator/auto-queue/element.js"
7474- title: "Automatic Queue"
7575- desc: >
7676- Fill the queue automatically with non-manual items (shuffled or regular, based on repeat-shuffle engine).
7777- - url: "components/orchestrator/favourites/element.js"
7878- title: "Favourites"
7979- desc: >
8080- Mark tracks as favourites. Automatically creates an unordered 'Favourites' playlist.
8181- - url: "components/orchestrator/input/element.js"
8282- title: "Input"
8383- desc: "**A default input configuration.** Contains all the inputs provided here."
8484- - url: "components/orchestrator/media-session/element.js"
8585- title: "Media Session"
8686- desc: "Keeps the browser/os media session in sync with queue and audio state. Adds handlers for previous, next, seek to, etc."
8787- - url: "components/orchestrator/offline/element.js"
8888- title: "Offline"
8989- desc: "Registers a service worker that makes the page available offline. Resources (except audio & video) are cached as they load and served from cache when offline."
9090- - url: "components/orchestrator/output/element.js"
9191- title: "Output"
9292- desc: "**A default output configuration.** Contains all the outputs provided here along with the relevant transformers."
9393- - url: "components/orchestrator/path-collections/element.js"
9494- title: "Path Collections"
9595- desc: "Wraps an output element to generate ephemeral playlists based on the first path segment of each track's URI. Ephemeral items are excluded from storage."
9696- - url: "components/orchestrator/process-tracks/element.js"
9797- title: "Process Inputs Into Tracks"
9898- desc: "Whenever the cached tracks are initially loaded through the passed output element it will list tracks by using the passed input element. Afterwards it loops over all tracks and checks if metadata needs to be fetched. If anything has changed, it'll pass the results to the output element."
9999- - url: "components/orchestrator/queue-audio/element.js"
100100- title: "Queue ⭤ Audio"
101101- desc: "Connects the given queue engine to the given audio engine."
102102- - url: "components/orchestrator/scrobble-audio/element.js"
103103- title: "Scrobble ⭤ Audio"
104104- desc: "Connects the audio engine with a scrobbler element. Calls `nowPlaying` when a track starts playing and `scrobble` once the user has listened long enough."
105105- - url: "components/orchestrator/sources/element.js"
106106- title: "Sources"
107107- desc: "Monitor tracks from the given output to form a list of sources based on the input's sources return value."
108108- - url: "components/orchestrator/scoped-tracks/element.js"
109109- title: "Scoped Tracks"
110110- desc: "Supplies the tracks from the given output to the given search processor whenever the tracks collection changes. Additionally it can perform a search and other ways to reduce the scope of tracks based on the given scope engine. Provides a `tracks` signal similar to `output.tracks.collection`"
111111-112112-output:
113113- - url: "components/output/polymorphic/indexed-db/element.js"
114114- title: "Polymorphic / IndexedDB"
115115- desc: "Stores output into the local indexedDB. Supports any type of data that indexedDB supports."
116116- - url: "components/output/bytes/s3/element.js"
117117- title: "Bytes / S3"
118118- desc: >
119119- Store output data on AWS S3 or compatible services such as Cloudflare R2.
120120- - url: "components/output/raw/atproto/element.js"
121121- title: "Raw / AT Protocol"
122122- desc: >
123123- Store your user data on the storage associated with your ATProtocol identity. Data is lexicon shaped by default so this element takes in that data directly without any transformations.
124124-125125-processors:
126126- - url: "components/processor/artwork/element.js"
127127- title: "Artwork"
128128- desc: "Fetches cover art for a given set of tracks, stored locally in indexedDB. Checks the audio metadata first, then MusicBrainz and uses Last.fm as the fallback."
129129- - url: "components/processor/metadata/element.js"
130130- title: "Metadata"
131131- desc: "Fetch audio metadata for a given set of tracks, adding to the `Track` object."
132132- - url: "components/processor/search/element.js"
133133- title: "Search"
134134- desc: "Provides a way to search through a collection of tracks, powered by orama.js"
135135-136136-supplements:
137137- - url: "components/supplement/last.fm/element.js"
138138- title: "Last.fm Scrobbler"
139139- - title: "ListenBrainz Scrobbler"
140140- todo: true
141141- - title: "Rocksky Scrobbler"
142142- todo: true
143143- - title: "Teal.fm Scrobbler"
144144- todo: true
145145-146146-transformers:
147147- - title: "Output / Bytes / Automerge"
148148- desc: "Translate data to and from an Automerge CRDT."
149149- url: "components/transformer/output/bytes/automerge/element.js"
150150- todo: true
151151- - title: "Output / Bytes / Cambria Lenses"
152152- desc: "Uses the Cambria library to seamlessly translate between data schemas so that no data migration is needed."
153153- todo: true
154154- - title: "Output / Bytes / DASL Sync"
155155- desc: "Syncs data between local and remote using CID-based diffing and performs union merges with tombstone tracking when both sides have diverged."
156156- url: "components/transformer/output/bytes/dasl-sync/element.js"
157157- - title: "Output / Bytes / JSON"
158158- desc: "Raw data schema output ⇄ JSON Uint8Array."
159159- url: "components/transformer/output/bytes/json/element.js"
160160- - title: "Output / Raw / AT Protocol Sync"
161161- desc: "Wraps an AT Protocol output with a local IndexedDB cache. Uses the repo revision to skip unnecessary fetches and performs union merges with tombstone tracking when both local and remote have diverged."
162162- url: "components/transformer/output/raw/atproto-sync/element.js"
163163- - title: "Output / Refiner / Default"
164164- desc: "The task of a refiner transformer is to remove the output state that is not meant to be saved to storage. For example, ephemeral tracks; this transformer will keep them in memory, but they will not be present in the output. **Ideally this is part of every theme, but you may swap it out with another transformer that might provide better defaults.**"
165165- url: "components/transformer/output/refiner/default/element.js"
166166- - title: "Output / Refiner / Track URI Passkey"
167167- desc: "Encrypts track URIs using a passkey-derived PRF key. On read, decrypts `encrypted://` URIs transparently; on write, re-encrypts all URIs before passing downstream. Tracks that cannot be decrypted are held separately and excluded from the visible collection."
168168- url: "components/transformer/output/refiner/track-uri-passkey/element.js"
169169- - title: "Output / String / JSON"
170170- desc: "Raw data schema output ⇄ JSON UTF8 string."
171171- url: "components/transformer/output/string/json/element.js"
172172-173173-# DEFINITIONS
174174-175175-definitions:
176176- - title: "Output / Collaboration"
177177- desc: >
178178- Represents a collaboration between multiple collaborators on a subject, such as a playlist.
179179- url: "definitions/output/collaboration.json"
180180- - title: "Output / Facet"
181181- desc: >
182182- Facet pointer or HTML snippet.
183183- url: "definitions/output/facet.json"
184184- - title: "Output / Playlist Item"
185185- desc: >
186186- Represents a single item in a playlist. Tracks are matched based on the given criteria. A playlist is formed by grouping items by their playlist property.
187187- url: "definitions/output/playlistItem.json"
188188- - title: "Output / Playlist Item Bundle"
189189- desc: >
190190- A bundle of playlist items.
191191- url: "definitions/output/playlistItemBundle.json"
192192- - title: "Output / Progress"
193193- desc: >
194194- Used to track progress of (long) audio playback.
195195- todo: true
196196- - title: "Output / Track"
197197- desc: >
198198- Represents audio that can be played, or a placeholder for a source of tracks. Contains a URI that will resolve to the audio.
199199- url: "definitions/output/track.json"
200200- - title: "Output / Track Bundle"
201201- desc: >
202202- A bundle of tracks.
203203- url: "definitions/output/trackBundle.json"
204204-20514# LINKS
2061520716links:
···23746 <i class="ph-fill ph-crane"></i>
23847 <strong style="font-weight: 700;">WORK IN PROGRESS</strong>
23948 </p>
4949+ <p style="margin: var(--space-lg) 0">
5050+ <a class="button" href="dashboard/">Open Diffuse</a>
5151+ </p>
24052 <ul class="table-of-contents">
241241- <li><a href="#usage">Usage</a></li>
242242- <li><a href="#developers">Developers</a></li>
243243- <li><a href="#definitions">Definitions</a></li>
244244- <li><a href="#links">Links</a></li>
245245- </ul>
5353+ <li><a href="guide/">Guide</a></li>
5454+ <li><a href="featured/">Featured</a></li>
5555+ </ul>
24656 <p>
247247- <small>Built by <a href="https://tokono.ma">tokono.ma</a></small>
5757+ <small style="line-height: var(--leading-relaxed)">
5858+ Built with <a href="elements/">Diffuse elements</a><br />
5959+ Created by <a href="https://tokono.ma">tokono.ma</a>
6060+ </small>
24861 </p>
24962 </div>
25063 <div class="dither-mask filler"></div>
25164</header>
25265<main>
253253- <!-- USAGE -->
254254- <section>
255255- <h2 id="usage">Usage</h2>
256256-257257- <div class="columns">
258258- <div class="element">
259259- <p>
260260- <strong style="color: var(--accent)">Diffuse is not your typical streaming service, we have to add sources of audio so we have stuff to play.</strong> This button below adds some demo content, so you can experiment with the software right away.
261261- </p>
262262-263263- <p>
264264- <button id="add-sample-content">
265265- <span>Add sample content</span>
266266- </button>
267267- </p>
268268- </div>
269269-270270- <div class="element">
271271- <p>
272272- If you do already have a place where you keep your audio files and want to connect it first thing, browse through all the various services that can be connected to Diffuse.
273273- </p>
274274-275275- <p><em>Look for "Connect ..."</em></p>
276276-277277- <p>
278278- <a class="button" href="kitchen/data/" target="_blank">Discover integrations</a>
279279- </p>
280280- </div>
281281- </div>
282282-283283- <div class="columns" style="margin-top: var(--space-xl)">
284284- <div class="element">
285285- <p>
286286- Great, one of the hardest parts is already done. Now we should explore what is possible with our audio. Because Diffuse is cooperative and malleable software, our interface can look like anything, and we can pick the features we like. That might sound overwhelming, so let's keep it simple for now.
287287- </p>
288288- </div>
289289-290290- <div class="element">
291291- <p>
292292- <strong>Let's pick an interface</strong> that automatically puts audio from our collection into the queue, and another to play what got put into the queue.
293293- </p>
294294-295295- <p>
296296- <span class="button-row">
297297- <a class="button button--bg-twist-1" href="{{ ('facets/playback/auto-queue/index.html') |> facetLoaderURL }}" target="_blank">
298298- <span class="with-icon">
299299- <i class="ph-fill ph-number-circle-one"></i>
300300- Fill up queue
301301- </span>
302302- </a>
303303- <a class="button button--bg-twist-1" href="{{ ('themes/blur/artwork-controller/facet/index.html') |> facetLoaderURL }}" target="_blank">
304304- <span class="with-icon">
305305- <i class="ph-fill ph-number-circle-two"></i>
306306- Play audio
307307- </span>
308308- </a>
309309- </span>
310310- </p>
311311- </div>
312312- </div>
313313-314314- <div class="columns" style="margin-top: var(--space-xl)">
315315- <div class="element">
316316- <p>
317317- <em>So you said I could pick the features and interfaces that I liked, how does that work?</em>
318318- </p>
319319-320320- <p>
321321- To do that, we have to visit our <strong style="color: var(--accent-twist-2)">kitchen</strong>. There you'll be able to browse through all the features and interfaces that are provided by Diffuse. To use a feature, you click the toggle to enable it, this will add it to your software. Interfaces can be added too, but it's not required, you can try them out right away by clicking the link in their title.
322322- </p>
323323-324324- <p>
325325- <a class="button button--bg-twist-2" href="kitchen/" target="_blank">Explore the kitchen</a>
326326- </p>
327327- </div>
328328-329329- <div class="element">
330330- <p>
331331- <em>What you mean, my software? What is software anyways?</em>
332332- </p>
333333-334334- <p>
335335- When we say software, we mean, having a user interface or code that manipulates your data in some way. Because you are picking those pieces that change how it looks and behaves, you are composing your own software, you made it yours.
336336- </p>
337337- </div>
338338- </div>
339339-340340- <div class="columns" style="margin-top: var(--space-xl)">
341341- <div class="element">
342342- <p>
343343- <em>What is a feature exactly?</em>
344344- </p>
345345-346346- <p>
347347- A <strong style="color: var(--accent-twist-4)">feature</strong> is a piece of code that expands the capabilities of your software or that makes it behave in a certain kind of way. It runs each time you open an interface, but may not necessarily be activated. Some examples, support for a particular audio source, or always add things automatically to the queue.
348348- </p>
349349- </div>
350350-351351- <div class="element">
352352- <p>
353353- <em>What makes it cooperative?</em>
354354- </p>
355355-356356- <p>
357357- These features and interfaces aren't required to be built into Diffuse, they can come from any place on the web. This makes it cooperative, you can share your features and interfaces, and use ones from other people. Any of these can be edited by clicking the edit button on the "your software" page.
358358- </p>
359359- </div>
360360- </div>
361361-362362- <div class="columns" style="margin-top: var(--space-xl)">
363363- <div class="element">
364364- <p>
365365- <em>How do I sync my data with my other devices?</em>
366366- </p>
367367-368368- <p>
369369- #
370370- </p>
371371- </div>
372372- </div>
373373- </section>
374374-375375- <!-- ELEMENTS -->
376376- <section>
377377- <h2 id="developers">Developers</h2>
378378-379379- <p>
380380- If you're a programmer, you can use the (web) components of the system. These custom elements can be combined into an entire music player and browser, or whatever you want to build.
381381- </p>
382382-383383- <p>
384384- Consume these using the kitchen <a href="facets/#builder">build tool</a>, the Javascript <a href="https://jsr.io/@toko/diffuse">package</a>, or the linked Javascript files down below.
385385- </p>
386386-387387- <div class="columns">
388388- {{ await comp.element({
389389- title: "Configurators",
390390- items: configurators,
391391- content: `
392392- Elements that serve as an intermediate in order to make a particular kind of element configurable. In other words, these allow for an element to be swapped out with another that takes the same set of the actions and data output.
393393- `
394394- }) }}
395395-396396- {{ await comp.element({
397397- title: "Engines",
398398- items: engines,
399399- content: `
400400- Elements with each a singular purpose and don't have any UI. There are specialised UI and orchestrator elements that control these.
401401- `
402402- }) }}
403403-404404- {{ await comp.element({
405405- title: "Input",
406406- items: input,
407407- content: `
408408- Inputs are sources of audio tracks. Each track is an entry in the list of possible items to play. These can be files or streams, static or dynamic.
409409- `
410410- }) }}
411411-412412- {{ await comp.element({
413413- title: "Orchestrators",
414414- items: orchestrators,
415415- content: `
416416- These too are element compositions. However, unlike themes, these are purely logical. Mostly exist in order to construct sensible defaults to use across themes and other compositions.
417417- `
418418- }) }}
419419-420420- {{ await comp.element({
421421- title: "Output",
422422- items: output,
423423- content: `
424424- Output is application-derived data such as playlists. These elements can receive such data and keep it around. These are categorised by the type of data they ingest, or many types in the case of polymorphic. Optionally use transformers to convert output into the expected format.
425425- `
426426- }) }}
427427-428428- {{ await comp.element({
429429- title: "Processors",
430430- items: processors,
431431- content: `
432432- These elements work with the output generated by the input elements to add more data to them, or process them in some other way.
433433- `
434434- }) }}
435435-436436- {{ await comp.element({
437437- title: "Supplements",
438438- items: supplements,
439439- content: `
440440- Additional elements, such as scrobblers.
441441- `
442442- }) }}
443443-444444- {{ await comp.element({
445445- title: "Transformers",
446446- items: transformers,
447447- content: `
448448- Transform data from one format or schema into another. See schema section below for more information. Just as configurators, these are intermediates and require to have the same set of actions as the element it targets.
449449- `
450450- }) }}
451451- </div>
452452- </section>
453453-454454- <!-- DEFINITIONS + LINKS -->
455455- <div class="columns">
456456- <section>
457457- <h2 id="definitions">Definitions</h2>
458458-459459- <p>All of the elements here are built with these data definitions in mind. That said, you can mix elements that use different definitions; you just have to put a transformer between them in order to translate between them, if needed.</p>
460460-461461- {{ await comp.list({ items: definitions }) }}
462462- </section>
463463-464464- <section>
465465- <h2 id="links">Links</h2>
466466-467467- {{ await comp.list({ items: links }) }}
468468- </section>
469469- </div>
47066</main>
+3-3
src/kitchen/build.vto
src/build.vto
···11---
22layout: layouts/kitchen.vto
33-base: ../../
44-title: Build | Kitchen | Diffuse
33+base: ../
44+title: Build | Diffuse
5566examples:
77 - url: "facets/examples/now-playing/index.html"
···9999 Some simple examples to help you understand how to build your own facet. Click the edit button to load them into the code editor above.
100100 </p>
101101102102- {{ await comp.facets.examples({ id: "examples", items: examples }) }}
102102+ {{ await comp.examples({ id: "examples", items: examples }) }}
103103104104 <h2 id="notes">Notes</h2>
105105 <p>
···11import * as Build from "./build.js";
22+import * as Dashboard from "./dashboard.js";
23import * as Grid from "./grid.js";
33-import * as You from "./you.js";
44+import * as Guide from "./guide.js";
4556/** Base pathname of the app (e.g. "/" at root, "/diffuse/" in a subdirectory). */
67const BASE_PATHNAME = new URL(document.baseURI).pathname;
7889/**
910 * Strips the app's base path prefix from an absolute pathname,
1010- * returning a root-relative path like "/kitchen/build".
1111+ * returning a root-relative path like "/build".
1112 *
1213 * @param {string} pathname
1314 */
···2930 await Grid.monitorToggleButtonStates();
30313132 switch (path) {
3232- case "/kitchen/build":
3333+ case "/build":
3334 Build.renderEditor();
3435 Build.handleBuildFormSubmit();
3536 Build.listenForExamplesEdit();
3637 await Build.editFacetFromURL();
3738 break;
3838- case "/kitchen/you":
3939- await You.renderList();
3939+ case "/dashboard":
4040+ await Dashboard.renderList();
4041 break;
4242+ case "/guide":
4343+ Guide.setupSampleButton();
4144 default:
4245 break;
4346 }
···6366 const url = new URL(event.destination.url);
6467 if (url.origin !== location.origin) return;
65686666- // Only intercept /kitchen/[section]/ paths (not deeper sub-paths like /kitchen/misc/*)
6969+ // Only intercept paths one level deep
6770 const relative = relativePathname(url.pathname);
6871 const parts = relative.split("/").filter(Boolean);
6969- if (parts[0] !== "kitchen") return;
7272+ if (parts.length === 0) return;
7073 if (parts.length > 2) return;
71747275 // Skip the loader page
···4040 <p>
4141 <span>
4242 You haven't saved anything yet. Add a facet by browsing the <a
4343- href="kitchen/"
4343+ href="featured/"
4444 >featured ones</a> or any of the other categories. You can click the toggle
4545 to quickly add or remove from your collection. Alternatively, add one using
4646 an URI:
···207207 const loading = html`
208208 <div class="with-icon">
209209 <i class="ph-bold ph-spinner animate-spin"></i>
210210- Loading items
210210+ Loading your software
211211 </div>
212212 `;
213213···238238 `
239239 : html`
240240 <a
241241- href="facets/l/?id=${c
241241+ href="l/?id=${c
242242 .id}"
243243 style="display: inline-block; padding: var(--space-3xs) 0"
244244 >
···287287 <a
288288 class="button button--transparent"
289289 title="Edit"
290290- href="kitchen/build/?id=${encodeURIComponent(c.id)}"
290290+ href="build/?id=${encodeURIComponent(c.id)}"
291291 >
292292 <i class="ph-fill ph-code-block"></i>
293293 </a>
-46
src/kitchen/guide.vto
···11----
22-layout: layouts/kitchen.vto
33-base: ../../
44-title: Guide | Kitchen | Diffuse
55----
66-77-<h1 hidden>Guide</h1>
88-99-<div class="columns">
1010- <section>
1111- <h3>Getting started</h3>
1212-1313- <p>
1414- To get started you can browse existing facets here on these pages, find one you like and then either you click the link in the title in case it's an <strong>interface</strong>. Or, you save it to your collection by using the toggle if it's a <strong>feature</strong>, which will activate it.
1515- </p>
1616- <p>
1717- For example, say you want to play music; two options would be: (1) <a href="{{ ('themes/webamp/browser/facet/index.html') |> facetLoaderURL }}">browse</a> for a specific song and add it to the queue, or (2) <a href="{{ ('facets/playback/auto-queue/index.html') |> facetLoaderURL }}">automatically</a> add a bunch of shuffled songs to the queue. Next, you need a way to play the items you added to the queue. That's where a <a href="{{ ('themes/blur/artwork-controller/facet/index.html') |> facetLoaderURL }}">controller</a> could be used.
1818- </p>
1919- <p>
2020- <em>You might ask, why can't I do all of this in just one window? That's what <a href="themes/">themes</a> are for, if you need something more streamlined. If you however want a customised experience, or prefer certain interfaces for certain things with an infinite number of optional features, that's what facets are for.</em>
2121- </p>
2222- <p>
2323- <small><i class="ph-fill ph-info"></i> Every facet has access to your audio collection and your user data, along with any other shared state, be mindful of which facets you're interacting with.</small>
2424- </p>
2525- </section>
2626-2727- <section>
2828- <h3>Concept</h3>
2929-3030- <p>
3131- Facets are unlike traditional software; instead of combining several features into a single user interface and producing data output, we do the opposite, we start with the data and work our way up from there.
3232- </p>
3333-3434- <p>
3535- <strong>It provides every user the ability to choose what features and interface they want to layer on top of their data.</strong>
3636- </p>
3737-3838- <p>
3939- Facets can provide any amount of features and user interface they see fit. The key here is that the user can combine facets because the components that consist of it are connected, they broadcast their state and your data that has been updated.
4040- </p>
4141-4242- <p>
4343- Finally, facets are just regular web pages so they can live wherever. We save them to the user-data storage that's configured and give the user the option to share it. This means that you can load facets from other people, <strong>building software cooperatively</strong>.
4444- </p>
4545- </section>
4646-</div>
···11----
22-layout: layouts/diffuse.vto
33-base: ../
44-55-styles:
66- - styles/base.css
77- - styles/diffuse/page.css
88- - vendor/@phosphor-icons/bold/style.css
99- - vendor/@phosphor-icons/fill/style.css
1010-1111-scripts:
1212- - themes/index.js
1313- - common/pages/version-upgrade.js
1414-1515-# THEMES
1616-1717-builtIn:
1818- - url: "themes/blur/"
1919- title: "Blur"
2020- todo: true
2121- desc: >
2222- **A theme with an Apple-inspired playback view.** Features two audio players instead of the usual one.
2323- - url: "themes/webamp/"
2424- title: "Webamp"
2525- desc: >
2626- **Winamp 2 + Windows 98**. Uses Webamp as the audio player connected to various Diffuse elements. Also features a desktop-like Windows 98 environment in which you can open "programs" that control the used Diffuse elements.
2727----
2828-2929-<header>
3030- <div>
3131- <div class="diffuse-logo-container">
3232- <a href="./" style="display: inline-block;">
3333- {{ await comp.diffuse.logo() }}
3434- </a>
3535- {{ await comp.diffuse.status() }}
3636- </div>
3737-3838- <p class="construct dither-mask" style="margin-top: 0">
3939- Themes
4040- </p>
4141-4242- <p>
4343- Themes are element compositions and provide a traditional browser web application way of
4444- using them. In other words, pretty much the whole thing, besides your data, lives inside a single browser tab.
4545- </p>
4646-4747- <p>
4848- <small>
4949- <strong><i class="ph-fill ph-info"></i></strong>
5050- Each theme is unique, not just a skin; each one might have a totally different feature set.
5151- </small>
5252- </p>
5353-5454- <ul class="table-of-contents">
5555- <li><a href="themes/#built-in">Built-in</a></li>
5656- <li><a href="themes/#community">Community</a></li>
5757- <li><a href="themes/#collection">Your collection</a></li>
5858- <li><a href="themes/#build">Build</a></li>
5959- </ul>
6060- </div>
6161- <div class="dither-mask filler"></div>
6262-</header>
6363-<main>
6464- <!-- BUILT-IN -->
6565- <div class="columns">
6666- <section class="flex">
6767- <h2 id="built-in">Built-in</h2>
6868-6969- {{ await comp.list({ items: builtIn }) }}
7070- </section>
7171-7272- <section class="flex">
7373- <h2 id="community">Community</h2>
7474- <p>
7575- Check out some themes from the community and load them here.
7676- </p>
7777- <p>
7878- <small><i class="ph-fill ph-info"></i> Nothing here yet, too early.</small>
7979- </p>
8080- </section>
8181- </div>
8282-8383- <!-- YOUR COLLECTION -->
8484- <div class="columns">
8585- <section class="flex">
8686- <h2 id="collection">Your collection</h2>
8787- <div id="list">
8888- <div class="with-icon" style="font-size: var(--fs-sm);">
8989- <i class="ph-bold ph-spinner-gap"></i>
9090- Loading items
9191- </div>
9292- </div>
9393- </section>
9494-9595- <section class="flex"></section>
9696- </div>
9797-9898- <!-- / -->
9999- <div class="dither-mask filler" style="height: var(--space-2xl); margin-top: var(--space-2xl);"></div>
100100-101101- <!-- BUILD -->
102102- <section>
103103- <h2 id="build">Build</h2>
104104-105105- <form id="build-form" class="columns">
106106- <div class="flex">
107107- <p style="margin-top: 0">
108108- If you know a bit of HTML & Javascript, you can write your own or plug in some code you found elsewhere:
109109- </p>
110110-111111- <div id="html-input-container" class="code-editor monospace-font">
112112- </div>
113113- </div>
114114-115115- <div class="flex">
116116- <p style="margin-top: 0">
117117- Your code here will be loaded in a dedicated page, it'll be injected into a <code><iframe></code> element in the body. You have access to the elements listed on the <a href="./#elements">index page</a> and the facets <a href="facets/#foundation">foundation</a>.
118118- </p>
119119- <input id="name-input" type="text" placeholder="Name" name="name" required />
120120- <p>
121121- <span class="button-row">
122122- <button name="save">Save</button>
123123- <button name="save+open">Save & Open</button>
124124- </span>
125125- </p>
126126- </div>
127127- </form>
128128- </section>
129129-130130-</main>