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 code editor to loader

+192 -30
+5
deno.jsonc
··· 9 9 "@atcute/lexicons": "npm:@atcute/lexicons@^1.2.7", 10 10 "@automerge/automerge": "npm:@automerge/automerge@^3.2.3", 11 11 "@bradenmacdonald/s3-lite-client": "jsr:@bradenmacdonald/s3-lite-client@^0.9.4", 12 + "@codemirror/autocomplete": "npm:@codemirror/autocomplete@^6.20.0", 13 + "@codemirror/lang-css": "npm:@codemirror/lang-css@^6.3.1", 14 + "@codemirror/lang-html": "npm:@codemirror/lang-html@^6.4.11", 15 + "@codemirror/lang-javascript": "npm:@codemirror/lang-javascript@^6.2.4", 12 16 "@fry69/deep-diff": "jsr:@fry69/deep-diff@^0.1.10", 13 17 "@js-temporal/polyfill": "npm:@js-temporal/polyfill@^0.5.1", 14 18 "@kunkun/kkrpc": "jsr:@kunkun/kkrpc@^0.6.0", ··· 18 22 "@phosphor-icons/web": "npm:@phosphor-icons/web@^2.1.2", 19 23 "@vicary/debounce-microtask": "jsr:@vicary/debounce-microtask@^0.1.8", 20 24 "alien-signals": "npm:alien-signals@^3.0.0", 25 + "codemirror": "npm:codemirror@^6.0.2", 21 26 "fast-average-color": "npm:fast-average-color@^9.5.0", 22 27 "idb-keyval": "npm:idb-keyval@^6.2.2", 23 28 "iso-base": "npm:iso-base@^4.3.0",
+7 -4
src/common/constituents/foundation.js
··· 110 110 const a = new AudioEngine(); 111 111 a.setAttribute("group", GROUP); 112 112 113 - return findExistingOrAdd(a) 113 + return findExistingOrAdd(a); 114 114 } 115 115 116 116 function queue() { ··· 240 240 // 🛠️ 241 241 242 242 /** 243 - * @param {DiffuseElement} element 243 + * @template {DiffuseElement} T 244 + * @param {T} element 245 + * @returns {T} 244 246 */ 245 247 export function findExistingOrAdd(element) { 248 + /** @type {T | null} */ 246 249 const alreadyAdded = document.body.querySelector(element.selector); 247 250 if (!alreadyAdded) { 248 251 document.body.append(element); 249 - return element 252 + return element; 250 253 } 251 254 252 - return alreadyAdded 255 + return alreadyAdded; 253 256 }
+19 -10
src/styles/diffuse/colors.css
··· 4 4 --color-2: oklch(98.369% 0.01834 67.664); 5 5 --color-3: oklch(26.787% 0.00168 186.65); 6 6 7 - /* Orange/Red */ 8 - /*--accent: oklch(86.947% 0.25527 28.789);*/ 9 - /*--accent: hsl(51, 100%, 50%);*/ 10 - /*--accent: #9e86b8;*/ 11 - 12 7 /* Green */ 13 - /*--accent: hsl(120, 73.4%, 74.9%);*/ 14 8 --accent: hsl(82, 39%, 30.2%); 15 - /*--accent: hsl(80, 60.5%, 34.7%);*/ 9 + 10 + /* Based on accent */ 11 + --accent-twist-1: oklch(0.4394087182327507 0.07236154661033459 126.18581796616421); 12 + --accent-twist-2: oklch(0.595520818778872 0.0651155417011046 241.18580587451083); 13 + --accent-twist-3: oklch(0.6455208185961163 0.07999998225841164 241.185809134907); 14 + --accent-twist-4: oklch(0.49552082249860496 0.056052673525189174 11.185823191211629); 15 + --accent-twist-5: oklch(0.4155208216940155 0.07999998497864118 11.185815502393897); 16 16 17 - /* Blue */ 18 - /*--accent: hsl(203, 92%, 75.5%);*/ 17 + /* Derivatives */ 18 + --accent-mark: oklch(from var(--accent-twist-1) l c h / 0.125); 19 + --accent-highlight: oklch(from var(--accent) l c h / 0.375); 19 20 20 21 --bg-color: var(--color-2); 21 22 --text-color: var(--color-1); 22 23 23 - --code-color: oklch(from var(--bg-color) calc(l - 0.0375) c h / 1); 24 + --code-color: oklch(from #fefcf1 l c h); 24 25 --form-color: oklch(from var(--bg-color) calc(l - 0.075) c h / 1); 25 26 } 26 27 ··· 28 29 :root { 29 30 --accent: #9e86b8; 30 31 32 + /* Based on accent */ 33 + --accent-twist-1: oklch(0.8304625409973806 0.04463693607878053 306.25919097736113); 34 + --accent-twist-2: oklch(0.8598598403285739 0.0562638076130437 71.2592389751063); 35 + --accent-twist-3: oklch(0.9098598401854464 0.04000001621772218 71.25925756817853); 36 + --accent-twist-4: oklch(0.759859834391464 0.0968658786601377 181.2591638095228); 37 + --accent-twist-5: oklch(0.5798598316017731 0.08000002153797516 181.25916489227464); 38 + 39 + /* Derivatives */ 31 40 --bg-color: var(--color-3); 32 41 --text-color: var(--color-2); 33 42
+99
src/styles/diffuse/page.css
··· 7 7 scroll-margin-top: var(--space-md); 8 8 } 9 9 10 + ::selection { 11 + background: var(--accent-highlight); 12 + } 13 + 10 14 /** 11 15 * Code 12 16 */ ··· 33 37 max-width: var(--container-xl); 34 38 } 35 39 40 + .code-editor { 41 + font-size: var(--fs-sm); 42 + font-size: calc((var(--fs-xs) + var(--fs-sm)) / 2); 43 + height: var(--container-xs); 44 + } 45 + 46 + .code-editor .cm-editor { 47 + background: var(--code-color); 48 + height: 100%; 49 + 50 + .cm-selectionBackground, 51 + &::selection { 52 + background: var(--accent-mark) !important; 53 + } 54 + 55 + .cm-content { 56 + padding: var(--space-2xs) var(--space-3xs); 57 + padding-right: var(--space-2xs); 58 + } 59 + 60 + .cm-gutters { 61 + background: oklch(from var(--code-color) calc(l - 0.025) c h); 62 + border: 0; 63 + color: oklch(from var(--text-color) l c h / 0.375); 64 + font-size: var(--fs-xs); 65 + line-height: 20px; 66 + } 67 + 68 + .cm-activeLineGutter { 69 + background: var(--accent); 70 + color: var(--bg-color); 71 + } 72 + 73 + .cm-scroller { 74 + font-family: inherit; 75 + } 76 + 77 + .cm-selectionMatch, 78 + .cm-matchingBracket { 79 + background: var(--accent-highlight); 80 + } 81 + 82 + .cm-activeline { 83 + background: oklch(from var(--text-color) l c h / 0.075); 84 + } 85 + 86 + .cm-cursor, 87 + .cm-dropCursor { 88 + border-left-color: var(--text-color); 89 + } 90 + 91 + .cm-tooltip { 92 + background: var(--bg-color); 93 + border: 0; 94 + padding: var(--space-3xs); 95 + } 96 + 97 + .cm-tooltip-autocomplete ul li[aria-selected] { 98 + background: var(--accent); 99 + color: var(--bg-color); 100 + } 101 + 102 + /* Code styling */ 103 + .ͼi { 104 + color: var(--accent); 105 + } 106 + 107 + .ͼe { 108 + color: var(--accent-twist-4); 109 + } 110 + 111 + .ͼb { 112 + color: var(--accent-twist-1); 113 + color: oklch(from currentColor l c h / 0.6); 114 + } 115 + 116 + .ͼg { 117 + color: var(--accent-twist-2); 118 + } 119 + 120 + .ͼf { 121 + color: var(--accent-twist-5); 122 + } 123 + 124 + .ͼ5, 125 + .ͼm { 126 + color: oklch(from currentColor l c h / 0.4); 127 + } 128 + } 129 + 36 130 /** 37 131 * Containers 38 132 */ ··· 110 204 /** 111 205 * Forms 112 206 */ 207 + 208 + /*select, 209 + ::picker(select) { 210 + appearance: base-select; 211 + }*/ 113 212 114 213 input, 115 214 textarea {
+16 -4
src/themes/loader/constituent/examples/now-playing.txt
··· 1 - <div id="now-playing">Loading ...</div> 2 - <button>⏭</button> 1 + <main> 2 + <div id="now-playing">Loading tracks ...</div> 3 + <button>⏭</button> 4 + </main> 5 + 6 + <style> 7 + @import "./styles/base.css"; 8 + @import "./styles/diffuse/page.css"; 9 + </style> 3 10 4 11 <script type="module"> 5 12 import foundation from "./common/constituents/foundation.js"; 6 - import { effect } from "./common/signal.js"; 13 + import { computed, effect } from "./common/signal.js"; 7 14 8 15 const components = foundation.assemblage.queueManagement(); 9 16 const queue = components.engine.queue; 10 17 18 + const isLoadingTracks = computed(() => { 19 + return components.orchestrator.output.tracks.state() !== "loaded"; 20 + }); 21 + 11 22 effect(() => { 12 23 const currentlyPlaying = queue.now(); 13 24 const tags = currentlyPlaying?.tags; ··· 17 28 18 29 if (currentlyPlaying) { 19 30 element.innerText = `${tags.artist} - ${tags.title}`; 31 + } else if (isLoadingTracks()) { 32 + // Keep original text 20 33 } else { 21 34 element.innerText = "Nothing is playing yet"; 22 35 } 23 36 }); 24 37 25 38 document.body.querySelector("button").onclick = () => { 26 - if (components.orchestrator.output.tracks.state() !== "loaded") return; 27 39 queue.shift(); 28 40 }; 29 41 </script>
+41 -7
src/themes/loader/constituent/index.js
··· 1 1 import * as CID from "@atcute/cid"; 2 2 import { html, render } from "lit-html"; 3 3 4 + import { basicSetup, EditorView } from "codemirror"; 5 + import { css as langCss } from "@codemirror/lang-css"; 6 + import { html as langHtml } from "@codemirror/lang-html"; 7 + import { javascript as langJs } from "@codemirror/lang-javascript"; 8 + import { autocompletion } from "@codemirror/autocomplete"; 9 + 4 10 import foundation from "@common/constituents/foundation.js"; 5 11 import { effect } from "@common/signal.js"; 6 12 ··· 55 61 // BUILD 56 62 //////////////////////////////////////////// 57 63 64 + // Code editor 65 + const editorContainer = document.body.querySelector("#html-input-container"); 66 + if (!editorContainer) throw new Error("Editor container not found"); 67 + 68 + const editor = new EditorView({ 69 + parent: editorContainer, 70 + doc: ` 71 + <script type="module"> 72 + import foundation from "./common/constituents/foundation.js"; 73 + import { effect } from "./common/signal.js"; 74 + 75 + const components = foundation.assemblage.queueManagement(); 76 + 77 + effect(() => { 78 + const currentlyPlaying = components.engine.queue.now(); 79 + }) 80 + </script> 81 + `.trim(), 82 + extensions: [ 83 + basicSetup, 84 + langHtml(), 85 + langCss(), 86 + langJs(), 87 + autocompletion(), 88 + ], 89 + }); 90 + 91 + // Form submit 58 92 document.querySelector("#build-form")?.addEventListener( 59 93 "submit", 60 94 onBuildSubmit, ··· 66 100 async function onBuildSubmit(event) { 67 101 event.preventDefault(); 68 102 69 - const htmlEl = 70 - /** @type {HTMLTextAreaElement | null} */ (document.querySelector( 71 - "#html-input", 72 - )); 73 103 const nameEl = /** @type {HTMLInputElement | null} */ (document.querySelector( 74 104 "#name-input", 75 105 )); 76 106 77 - const html = htmlEl?.value ?? ""; 107 + const html = editor.state.doc.toString(); 78 108 const cid = await CID.create(0x55, new TextEncoder().encode(html)); 79 109 const name = nameEl?.value ?? "nameless"; 80 110 ··· 91 121 /** @type {HTMLSelectElement | null} */ 92 122 const selected = document.body.querySelector("#example-select"); 93 123 94 - if (htmlEl && selected?.value) { 95 - htmlEl.value = await fetch( 124 + if (selected?.value) { 125 + const text = await fetch( 96 126 `themes/loader/constituent/examples/${selected.value}`, 97 127 ).then((r) => r.text()); 128 + 129 + editor.dispatch({ 130 + changes: { from: 0, to: editor.state.doc.length, insert: text }, 131 + }); 98 132 } 99 133 break; 100 134 }
+5 -2
src/themes/loader/constituent/index.vto
··· 69 69 If you know a bit of HTML & Javascript, you can write your own or plug in some code you found elsewhere: 70 70 </p> 71 71 72 - <div> 73 - <textarea id="html-input" class="monospace-font" placeholder="<div>goes here</div>"></textarea> 72 + <div id="html-input-container" class="code-editor monospace-font"> 74 73 </div> 75 74 </div> 76 75 ··· 90 89 </p> 91 90 <div> 92 91 <select id="example-select"> 92 + <button> 93 + <selectedcontent></selectedcontent> 94 + </button> 95 + 93 96 <option value="now-playing.txt" selected>Now playing + Next Queue Item</option> 94 97 </select> 95 98 </div>
-3
src/themes/loader/constituent/s/index.vto
··· 2 2 layout: layouts/constituent.vto 3 3 base: ../../../../ 4 4 5 - styles: 6 - - styles/base.css 7 - 8 5 scripts: 9 6 - themes/loader/constituent/s/index.js 10 7 ---