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: initial work for blur browser + bunch of fixes

+409 -143
+1 -1
.astro/data-store.json
··· 1 - [["Map",1,2,9,10],"meta::meta",["Map",3,4,5,6,7,8],"astro-version","5.10.1","content-config-digest","436d74edbfb2fef7","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"never\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false},\"legacy\":{\"collections\":false}}","manifests",["Map",11,12,28,29,65,66,148,149,180,181,214,215,241,242,268,269,276,277,284,285,292,293,305,306,318,319,331,332,355,356,379,380,387,388,403,404],"configurator/output/_manifest",{"id":11,"data":13,"filePath":26,"digest":27},{"name":14,"title":15,"entrypoint":16,"actions":17},"diffuse/configurator/output","Diffuse Configurator | Output","index.html",{"tracks":18},{"title":19,"description":20,"params_schema":21},"Tracks","Store or retrieve tracks. Passing in an array of tracks, stores them; passing no parameter, retrieves them.",{"type":22,"description":23,"items":24},"array","List of tracks",{"type":25},"object","src/pages/configurator/output/_manifest.json","4659e937f3f7ce97","configurator/input/_manifest",{"id":28,"data":30,"filePath":63,"digest":64},{"name":31,"title":32,"entrypoint":16,"actions":33},"diffuse/configurator/input","Diffuse Configurator | Input",{"consult":34,"contextualize":39,"list":45,"resolve":51},{"title":35,"params_schema":36},"Consult",{"type":37,"description":38},"string","The uri to check the availability of.",{"title":40,"description":41,"params_schema":42},"Contextualize","Provide context to all inputs.",{"type":22,"description":43,"items":44},"Array of tracks",{"type":25},{"title":46,"description":47,"params_schema":48},"List","List tracks from all inputs.",{"type":22,"description":49,"items":50},"A list of (cached) tracks",{"type":25},{"title":52,"description":53,"params_schema":54},"Resolve","Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes or an audio stream. If it can be resolved that is, otherwise you'll get `undefined`.",{"type":25,"properties":55,"required":60},{"method":56,"uri":58},{"type":37,"description":57},"The HTTP method that is going to be used on the resolved URI.",{"type":37,"description":59},"The URI to resolve.",[61,62],"method","uri","src/pages/configurator/input/_manifest.json","deab08f592f33c5d","engine/audio/_manifest",{"id":65,"data":67,"filePath":146,"digest":147},{"name":68,"title":69,"entrypoint":16,"actions":70},"diffuse/engine/audio","Diffuse Audio",{"pause":71,"play":79,"render":89,"reload":117,"seek":127,"volume":136},{"title":72,"description":73,"params_schema":74},"Pause","Pause audio",{"type":25,"properties":75,"required":77},{"audioId":76},{"type":37},[78],"audioId",{"title":80,"description":81,"params_schema":82},"Play","Play audio",{"type":25,"properties":83,"required":88},{"audioId":84,"volume":85},{"type":37},{"type":86,"default":87},"number",0.5,[78],{"title":90,"description":91,"params_schema":92},"Render","Determine the active set of audio elements.",{"type":25,"properties":93,"required":115},{"audio":94,"play":107},{"type":22,"description":95,"items":96},"The audio items we want to render. These represent the audio elements that are in the DOM.",{"type":25,"properties":97,"required":104},{"id":98,"isPreload":99,"mimeType":101,"progress":102,"url":103},{"type":37},{"type":100},"boolean",{"type":37},{"type":86},{"type":37},[105,106],"id","url",{"type":25,"description":108,"properties":109,"required":114},"Pass in this object to immediately start playing one of the rendered audio items.",{"audioId":110,"volume":112},{"type":37,"description":111},"The id of the rendered audio item we want to play.",{"type":86,"default":87,"description":113},"A number equal to, or between, 0 and 1, that determines how loud the audio should play.",[78],[116],"audio",{"title":118,"description":119,"params_schema":120},"Reload","Make sure the audio with the given id is loading properly. This should be used when for example, the internet connection comes back and the loading of the audio depended on said internet connection.",{"type":25,"properties":121,"required":125},{"audioId":122,"play":123,"progress":124},{"type":37},{"type":100},{"type":86},[78,126],"percentage",{"title":128,"description":129,"params_schema":130},"Seek","Seek audio to a given position",{"type":25,"properties":131,"required":135},{"audioId":132,"percentage":133},{"type":37},{"type":86,"description":134},"A number between 0 and 1 that determines the new current position in the audio",[78,126],{"title":137,"description":138,"params_schema":139},"Volume","Set the volume of all audio and the default value, or a specific audio node.",{"type":25,"properties":140,"required":144},{"audioId":141,"volume":142},{"type":37},{"type":86,"description":143},"A number between 0 and 1 that determines the new volume of all audio elements",[145],"volume","src/pages/engine/audio/_manifest.json","fad9de8481968df7","engine/queue/_manifest",{"id":148,"data":150,"filePath":178,"digest":179},{"name":151,"title":152,"entrypoint":16,"actions":153},"diffuse/engine/queue","Diffuse Queue",{"add":154,"pool":163,"shift":172,"unshift":175},{"title":155,"description":156,"params_schema":157},"Add","Add tracks to the queue.",{"type":22,"description":43,"items":158},{"type":25,"properties":159,"required":162},{"id":160,"uri":161},{"type":37},{"type":37},[105,62],{"title":164,"description":165,"params_schema":166},"Pool","Set the queue pool.",{"type":22,"description":43,"items":167},{"type":25,"properties":168,"required":171},{"id":169,"uri":170},{"type":37},{"type":37},[105,62],{"title":173,"description":174},"Shift","Shift the queue, picking the first item from the up next array and putting the currently playing item into the history list.",{"title":176,"description":177},"Unshift","Unshift the queue, going backwards in time, picking the last item from the history array and putting the currently playing item into the up next list.","src/pages/engine/queue/_manifest.json","bdd435b3e8277673","input/native-fs/_manifest",{"id":180,"data":182,"filePath":212,"digest":213},{"name":183,"title":184,"entrypoint":16,"input_properties":185,"actions":187},"diffuse/input/native-fs","Diffuse Input | Native File System",{"scheme":186},"file+local",{"consult":188,"list":192,"resolve":197,"mount":204,"unmount":207},{"title":35,"description":189,"params_schema":190},"Check if a handle is available to be used by passing in a file uri that uses that handle as the host.",{"type":37,"description":191},"The uri with the handle to check the availability of.",{"title":46,"description":193,"params_schema":194},"List tracks.",{"type":22,"description":195,"items":196},"A list of (cached) tracks with an uri matching the scheme",{"type":25},{"title":52,"description":198,"params_schema":199},"Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes or an audio stream. If it can be resolved that is, otherwise you'll get `undefined`. Use the `consult` action to get a more detailed answer.",{"type":25,"properties":200,"required":203},{"method":201,"uri":202},{"type":37,"description":57},{"type":37,"description":59},[61,62],{"title":205,"description":206},"Mount","Prepare for usage.",{"title":208,"description":209,"params_schema":210},"Unmount","Callback after usage.",{"type":37,"description":211},"The handle id to unmount","src/pages/input/native-fs/_manifest.json","bbfb366cb25470ac","input/s3/_manifest",{"id":214,"data":216,"filePath":239,"digest":240},{"name":217,"title":218,"entrypoint":16,"input_properties":219,"actions":221},"diffuse/input/s3","Diffuse Input | S3",{"scheme":220},"s3",{"consult":222,"contextualize":224,"list":227,"resolve":230,"mount":236,"unmount":237},{"title":35,"params_schema":223},{"type":37,"description":38},{"title":40,"params_schema":225},{"type":22,"description":43,"items":226},{"type":25},{"title":46,"description":193,"params_schema":228},{"type":22,"description":195,"items":229},{"type":25},{"title":52,"description":198,"params_schema":231},{"type":25,"properties":232,"required":235},{"method":233,"uri":234},{"type":37,"description":57},{"type":37,"description":59},[61,62],{"title":205,"description":206},{"title":208,"description":209,"params_schema":238},{"type":37,"description":211},"src/pages/input/s3/_manifest.json","458b0c64643bf8a8","input/opensubsonic/_manifest",{"id":241,"data":243,"filePath":266,"digest":267},{"name":244,"title":245,"entrypoint":16,"input_properties":246,"actions":248},"diffuse/input/opensubsonic","Diffuse Input | OpenSubsonic API",{"scheme":247},"opensubsonic",{"consult":249,"contextualize":251,"list":254,"resolve":257,"mount":263,"unmount":264},{"title":35,"params_schema":250},{"type":37,"description":38},{"title":40,"params_schema":252},{"type":22,"description":43,"items":253},{"type":25},{"title":46,"description":193,"params_schema":255},{"type":22,"description":195,"items":256},{"type":25},{"title":52,"description":198,"params_schema":258},{"type":25,"properties":259,"required":262},{"method":260,"uri":261},{"type":37,"description":57},{"type":37,"description":59},[61,62],{"title":205,"description":206},{"title":208,"description":209,"params_schema":265},{"type":37,"description":211},"src/pages/input/opensubsonic/_manifest.json","463ff2f82f27fed9","orchestrator/process-tracks/_manifest",{"id":268,"data":270,"filePath":274,"digest":275},{"name":271,"title":272,"entrypoint":16,"actions":273},"diffuse/orchestrator/process-tracks","Diffuse Orchestrator | Process Tracks",{},"src/pages/orchestrator/process-tracks/_manifest.json","4d30ef9c892a16b8","orchestrator/queue-audio/_manifest",{"id":276,"data":278,"filePath":282,"digest":283},{"name":279,"title":280,"entrypoint":16,"actions":281},"diffuse/orchestrator/queue-audio","Diffuse Orchestrator | Queue Audio",{},"src/pages/orchestrator/queue-audio/_manifest.json","5c66731fcc2fae2d","orchestrator/queue-tracks/_manifest",{"id":284,"data":286,"filePath":290,"digest":291},{"name":287,"title":288,"entrypoint":16,"actions":289},"diffuse/orchestrator/queue-tracks","Diffuse Orchestrator | Queue Tracks",{},"src/pages/orchestrator/queue-tracks/_manifest.json","56a0c769ecdc8cbb","output/indexed-db/_manifest",{"id":292,"data":294,"filePath":303,"digest":304},{"name":295,"title":296,"entrypoint":16,"actions":297},"diffuse/output/indexed-db","Diffuse Output | IndexedDB",{"tracks":298,"mount":301,"unmount":302},{"title":19,"description":20,"params_schema":299},{"type":22,"description":23,"items":300},{"type":25},{"title":205,"description":206},{"title":208,"description":209},"src/pages/output/indexed-db/_manifest.json","c75e5169818995c0","output/native-fs/_manifest",{"id":305,"data":307,"filePath":316,"digest":317},{"name":308,"title":309,"entrypoint":16,"actions":310},"diffuse/output/native-fs","Diffuse Output | Native File System",{"tracks":311,"mount":314,"unmount":315},{"title":19,"description":20,"params_schema":312},{"type":22,"description":23,"items":313},{"type":25},{"title":205,"description":206},{"title":208,"description":209},"src/pages/output/native-fs/_manifest.json","9f36293a08c3d233","output/storacha-automerge/_manifest",{"id":318,"data":320,"filePath":329,"digest":330},{"name":321,"title":322,"entrypoint":16,"actions":323},"diffuse/output/storacha-automerge","Diffuse Output | Storacha Storage + Automerge CRDT",{"tracks":324,"mount":327,"unmount":328},{"title":19,"description":20,"params_schema":325},{"type":22,"description":23,"items":326},{"type":25},{"title":205,"description":206},{"title":208,"description":209},"src/pages/output/storacha-automerge/_manifest.json","607193e5156e1220","processor/metadata/_manifest",{"id":331,"data":333,"filePath":353,"digest":354},{"name":334,"title":335,"entrypoint":16,"actions":336},"diffuse/processor/metadata","Diffuse Processor | Metadata fetcher",{"supply":337},{"title":338,"description":339,"params_schema":340},"Supply","Get the metadata for a given URL or stream.",{"type":25,"properties":341},{"includeArtwork":342,"mimeType":344,"stream":345,"urls":346},{"type":100,"description":343},"Include artwork in the output.",{"type":37},{"type":25},{"type":25,"properties":347,"required":350},{"get":348,"head":349},{"type":37},{"type":37},[351,352],"get","head","src/pages/processor/metadata/_manifest.json","0f144e608574be9b","processor/artwork/_manifest",{"id":355,"data":357,"filePath":377,"digest":378},{"name":358,"title":359,"description":360,"entrypoint":16,"actions":361},"diffuse/processor/artwork","Diffuse Processor | Artwork fetcher","Tries to get artwork for a given URL or stream.",{"supply":362},{"title":338,"description":363,"params_schema":364},"Get the artwork for a given URL.",{"type":22,"items":365},{"type":25,"properties":366,"required":375},{"cacheId":367,"mimeType":368,"stream":369,"urls":370},{"type":37},{"type":37},{"type":25},{"type":25,"properties":371,"required":374},{"get":372,"head":373},{"type":37},{"type":37},[351,352],[376],"cacheId","src/pages/processor/artwork/_manifest.json","9401bdfa33e2f0db","processor/search/_manifest",{"id":379,"data":381,"filePath":385,"digest":386},{"name":382,"title":383,"entrypoint":16,"actions":384},"diffuse/processor/search","Diffuse Processor | Search",{},"src/pages/processor/search/_manifest.json","ec562d1125821f8a","theme/pilot/audio/_manifest",{"id":387,"data":389,"filePath":401,"digest":402},{"name":390,"title":391,"entrypoint":16,"actions":392},"diffuse/constituent/pilot/audio","",{"modifyIsPlaying":393,"modifyProgress":397},{"title":394,"description":395,"params_schema":396},"Set is-playing state","Indicate if audio is playing or not.",{"type":100},{"title":398,"description":399,"params_schema":400},"Set progress","Indicate how far the audio has progressed.",{"type":86},"src/pages/theme/pilot/audio/_manifest.json","7c3a0b25e8d14fd2","constituent/blur/artwork-controller/_manifest",{"id":403,"data":405,"filePath":413,"digest":414},{"name":406,"title":407,"entrypoint":16,"actions":408},"diffuse/constituent/blur/artwork-controller","Diffuse Blur Theme | Artwork Controller",{"modifyIsPlaying":409,"modifyProgress":411},{"title":394,"description":395,"params_schema":410},{"type":100},{"title":398,"description":399,"params_schema":412},{"type":86},"src/pages/constituent/blur/artwork-controller/_manifest.json","1b705295c5fa221a"] 1 + [["Map",1,2,9,10],"meta::meta",["Map",3,4,5,6,7,8],"astro-version","5.10.1","content-config-digest","436d74edbfb2fef7","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"never\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false},\"legacy\":{\"collections\":false}}","manifests",["Map",11,12,28,29,65,66,148,149,180,181,214,215,241,242,268,269,276,277,284,285,292,293,305,306,318,319,331,332,355,356,379,380,387,388,403,404,411,412,419,420],"configurator/output/_manifest",{"id":11,"data":13,"filePath":26,"digest":27},{"name":14,"title":15,"entrypoint":16,"actions":17},"diffuse/configurator/output","Diffuse Configurator | Output","index.html",{"tracks":18},{"title":19,"description":20,"params_schema":21},"Tracks","Store or retrieve tracks. Passing in an array of tracks, stores them; passing no parameter, retrieves them.",{"type":22,"description":23,"items":24},"array","List of tracks",{"type":25},"object","src/pages/configurator/output/_manifest.json","4659e937f3f7ce97","configurator/input/_manifest",{"id":28,"data":30,"filePath":63,"digest":64},{"name":31,"title":32,"entrypoint":16,"actions":33},"diffuse/configurator/input","Diffuse Configurator | Input",{"consult":34,"contextualize":39,"list":45,"resolve":51},{"title":35,"params_schema":36},"Consult",{"type":37,"description":38},"string","The uri to check the availability of.",{"title":40,"description":41,"params_schema":42},"Contextualize","Provide context to all inputs.",{"type":22,"description":43,"items":44},"Array of tracks",{"type":25},{"title":46,"description":47,"params_schema":48},"List","List tracks from all inputs.",{"type":22,"description":49,"items":50},"A list of (cached) tracks",{"type":25},{"title":52,"description":53,"params_schema":54},"Resolve","Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes or an audio stream. If it can be resolved that is, otherwise you'll get `undefined`.",{"type":25,"properties":55,"required":60},{"method":56,"uri":58},{"type":37,"description":57},"The HTTP method that is going to be used on the resolved URI.",{"type":37,"description":59},"The URI to resolve.",[61,62],"method","uri","src/pages/configurator/input/_manifest.json","deab08f592f33c5d","engine/audio/_manifest",{"id":65,"data":67,"filePath":146,"digest":147},{"name":68,"title":69,"entrypoint":16,"actions":70},"diffuse/engine/audio","Diffuse Audio",{"pause":71,"play":79,"render":89,"reload":117,"seek":127,"volume":136},{"title":72,"description":73,"params_schema":74},"Pause","Pause audio",{"type":25,"properties":75,"required":77},{"audioId":76},{"type":37},[78],"audioId",{"title":80,"description":81,"params_schema":82},"Play","Play audio",{"type":25,"properties":83,"required":88},{"audioId":84,"volume":85},{"type":37},{"type":86,"default":87},"number",0.5,[78],{"title":90,"description":91,"params_schema":92},"Render","Determine the active set of audio elements.",{"type":25,"properties":93,"required":115},{"audio":94,"play":107},{"type":22,"description":95,"items":96},"The audio items we want to render. These represent the audio elements that are in the DOM.",{"type":25,"properties":97,"required":104},{"id":98,"isPreload":99,"mimeType":101,"progress":102,"url":103},{"type":37},{"type":100},"boolean",{"type":37},{"type":86},{"type":37},[105,106],"id","url",{"type":25,"description":108,"properties":109,"required":114},"Pass in this object to immediately start playing one of the rendered audio items.",{"audioId":110,"volume":112},{"type":37,"description":111},"The id of the rendered audio item we want to play.",{"type":86,"default":87,"description":113},"A number equal to, or between, 0 and 1, that determines how loud the audio should play.",[78],[116],"audio",{"title":118,"description":119,"params_schema":120},"Reload","Make sure the audio with the given id is loading properly. This should be used when for example, the internet connection comes back and the loading of the audio depended on said internet connection.",{"type":25,"properties":121,"required":125},{"audioId":122,"play":123,"progress":124},{"type":37},{"type":100},{"type":86},[78,126],"percentage",{"title":128,"description":129,"params_schema":130},"Seek","Seek audio to a given position",{"type":25,"properties":131,"required":135},{"audioId":132,"percentage":133},{"type":37},{"type":86,"description":134},"A number between 0 and 1 that determines the new current position in the audio",[78,126],{"title":137,"description":138,"params_schema":139},"Volume","Set the volume of all audio and the default value, or a specific audio node.",{"type":25,"properties":140,"required":144},{"audioId":141,"volume":142},{"type":37},{"type":86,"description":143},"A number between 0 and 1 that determines the new volume of all audio elements",[145],"volume","src/pages/engine/audio/_manifest.json","fad9de8481968df7","engine/queue/_manifest",{"id":148,"data":150,"filePath":178,"digest":179},{"name":151,"title":152,"entrypoint":16,"actions":153},"diffuse/engine/queue","Diffuse Queue",{"add":154,"pool":163,"shift":172,"unshift":175},{"title":155,"description":156,"params_schema":157},"Add","Add tracks to the queue.",{"type":22,"description":43,"items":158},{"type":25,"properties":159,"required":162},{"id":160,"uri":161},{"type":37},{"type":37},[105,62],{"title":164,"description":165,"params_schema":166},"Pool","Set the queue pool.",{"type":22,"description":43,"items":167},{"type":25,"properties":168,"required":171},{"id":169,"uri":170},{"type":37},{"type":37},[105,62],{"title":173,"description":174},"Shift","Shift the queue, picking the first item from the up next array and putting the currently playing item into the history list.",{"title":176,"description":177},"Unshift","Unshift the queue, going backwards in time, picking the last item from the history array and putting the currently playing item into the up next list.","src/pages/engine/queue/_manifest.json","bdd435b3e8277673","input/native-fs/_manifest",{"id":180,"data":182,"filePath":212,"digest":213},{"name":183,"title":184,"entrypoint":16,"input_properties":185,"actions":187},"diffuse/input/native-fs","Diffuse Input | Native File System",{"scheme":186},"file+local",{"consult":188,"list":192,"resolve":197,"mount":204,"unmount":207},{"title":35,"description":189,"params_schema":190},"Check if a handle is available to be used by passing in a file uri that uses that handle as the host.",{"type":37,"description":191},"The uri with the handle to check the availability of.",{"title":46,"description":193,"params_schema":194},"List tracks.",{"type":22,"description":195,"items":196},"A list of (cached) tracks with an uri matching the scheme",{"type":25},{"title":52,"description":198,"params_schema":199},"Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes or an audio stream. If it can be resolved that is, otherwise you'll get `undefined`. Use the `consult` action to get a more detailed answer.",{"type":25,"properties":200,"required":203},{"method":201,"uri":202},{"type":37,"description":57},{"type":37,"description":59},[61,62],{"title":205,"description":206},"Mount","Prepare for usage.",{"title":208,"description":209,"params_schema":210},"Unmount","Callback after usage.",{"type":37,"description":211},"The handle id to unmount","src/pages/input/native-fs/_manifest.json","bbfb366cb25470ac","input/s3/_manifest",{"id":214,"data":216,"filePath":239,"digest":240},{"name":217,"title":218,"entrypoint":16,"input_properties":219,"actions":221},"diffuse/input/s3","Diffuse Input | S3",{"scheme":220},"s3",{"consult":222,"contextualize":224,"list":227,"resolve":230,"mount":236,"unmount":237},{"title":35,"params_schema":223},{"type":37,"description":38},{"title":40,"params_schema":225},{"type":22,"description":43,"items":226},{"type":25},{"title":46,"description":193,"params_schema":228},{"type":22,"description":195,"items":229},{"type":25},{"title":52,"description":198,"params_schema":231},{"type":25,"properties":232,"required":235},{"method":233,"uri":234},{"type":37,"description":57},{"type":37,"description":59},[61,62],{"title":205,"description":206},{"title":208,"description":209,"params_schema":238},{"type":37,"description":211},"src/pages/input/s3/_manifest.json","458b0c64643bf8a8","input/opensubsonic/_manifest",{"id":241,"data":243,"filePath":266,"digest":267},{"name":244,"title":245,"entrypoint":16,"input_properties":246,"actions":248},"diffuse/input/opensubsonic","Diffuse Input | OpenSubsonic API",{"scheme":247},"opensubsonic",{"consult":249,"contextualize":251,"list":254,"resolve":257,"mount":263,"unmount":264},{"title":35,"params_schema":250},{"type":37,"description":38},{"title":40,"params_schema":252},{"type":22,"description":43,"items":253},{"type":25},{"title":46,"description":193,"params_schema":255},{"type":22,"description":195,"items":256},{"type":25},{"title":52,"description":198,"params_schema":258},{"type":25,"properties":259,"required":262},{"method":260,"uri":261},{"type":37,"description":57},{"type":37,"description":59},[61,62],{"title":205,"description":206},{"title":208,"description":209,"params_schema":265},{"type":37,"description":211},"src/pages/input/opensubsonic/_manifest.json","463ff2f82f27fed9","orchestrator/process-tracks/_manifest",{"id":268,"data":270,"filePath":274,"digest":275},{"name":271,"title":272,"entrypoint":16,"actions":273},"diffuse/orchestrator/process-tracks","Diffuse Orchestrator | Process Tracks",{},"src/pages/orchestrator/process-tracks/_manifest.json","4d30ef9c892a16b8","orchestrator/queue-audio/_manifest",{"id":276,"data":278,"filePath":282,"digest":283},{"name":279,"title":280,"entrypoint":16,"actions":281},"diffuse/orchestrator/queue-audio","Diffuse Orchestrator | Queue Audio",{},"src/pages/orchestrator/queue-audio/_manifest.json","5c66731fcc2fae2d","orchestrator/queue-tracks/_manifest",{"id":284,"data":286,"filePath":290,"digest":291},{"name":287,"title":288,"entrypoint":16,"actions":289},"diffuse/orchestrator/queue-tracks","Diffuse Orchestrator | Queue Tracks",{},"src/pages/orchestrator/queue-tracks/_manifest.json","56a0c769ecdc8cbb","output/indexed-db/_manifest",{"id":292,"data":294,"filePath":303,"digest":304},{"name":295,"title":296,"entrypoint":16,"actions":297},"diffuse/output/indexed-db","Diffuse Output | IndexedDB",{"tracks":298,"mount":301,"unmount":302},{"title":19,"description":20,"params_schema":299},{"type":22,"description":23,"items":300},{"type":25},{"title":205,"description":206},{"title":208,"description":209},"src/pages/output/indexed-db/_manifest.json","c75e5169818995c0","output/native-fs/_manifest",{"id":305,"data":307,"filePath":316,"digest":317},{"name":308,"title":309,"entrypoint":16,"actions":310},"diffuse/output/native-fs","Diffuse Output | Native File System",{"tracks":311,"mount":314,"unmount":315},{"title":19,"description":20,"params_schema":312},{"type":22,"description":23,"items":313},{"type":25},{"title":205,"description":206},{"title":208,"description":209},"src/pages/output/native-fs/_manifest.json","9f36293a08c3d233","output/storacha-automerge/_manifest",{"id":318,"data":320,"filePath":329,"digest":330},{"name":321,"title":322,"entrypoint":16,"actions":323},"diffuse/output/storacha-automerge","Diffuse Output | Storacha Storage + Automerge CRDT",{"tracks":324,"mount":327,"unmount":328},{"title":19,"description":20,"params_schema":325},{"type":22,"description":23,"items":326},{"type":25},{"title":205,"description":206},{"title":208,"description":209},"src/pages/output/storacha-automerge/_manifest.json","607193e5156e1220","processor/metadata/_manifest",{"id":331,"data":333,"filePath":353,"digest":354},{"name":334,"title":335,"entrypoint":16,"actions":336},"diffuse/processor/metadata","Diffuse Processor | Metadata fetcher",{"supply":337},{"title":338,"description":339,"params_schema":340},"Supply","Get the metadata for a given URL or stream.",{"type":25,"properties":341},{"includeArtwork":342,"mimeType":344,"stream":345,"urls":346},{"type":100,"description":343},"Include artwork in the output.",{"type":37},{"type":25},{"type":25,"properties":347,"required":350},{"get":348,"head":349},{"type":37},{"type":37},[351,352],"get","head","src/pages/processor/metadata/_manifest.json","0f144e608574be9b","processor/artwork/_manifest",{"id":355,"data":357,"filePath":377,"digest":378},{"name":358,"title":359,"description":360,"entrypoint":16,"actions":361},"diffuse/processor/artwork","Diffuse Processor | Artwork fetcher","Tries to get artwork for a given URL or stream.",{"supply":362},{"title":338,"description":363,"params_schema":364},"Get the artwork for a given URL.",{"type":22,"items":365},{"type":25,"properties":366,"required":375},{"cacheId":367,"mimeType":368,"stream":369,"urls":370},{"type":37},{"type":37},{"type":25},{"type":25,"properties":371,"required":374},{"get":372,"head":373},{"type":37},{"type":37},[351,352],[376],"cacheId","src/pages/processor/artwork/_manifest.json","9401bdfa33e2f0db","processor/search/_manifest",{"id":379,"data":381,"filePath":385,"digest":386},{"name":382,"title":383,"entrypoint":16,"actions":384},"diffuse/processor/search","Diffuse Processor | Search",{},"src/pages/processor/search/_manifest.json","ec562d1125821f8a","theme/pilot/audio/_manifest",{"id":387,"data":389,"filePath":401,"digest":402},{"name":390,"title":391,"entrypoint":16,"actions":392},"diffuse/constituent/pilot/audio","",{"modifyIsPlaying":393,"modifyProgress":397},{"title":394,"description":395,"params_schema":396},"Set is-playing state","Indicate if audio is playing or not.",{"type":100},{"title":398,"description":399,"params_schema":400},"Set progress","Indicate how far the audio has progressed.",{"type":86},"src/pages/theme/pilot/audio/_manifest.json","7c3a0b25e8d14fd2","constituent/blur/artwork-controller/_manifest",{"id":403,"data":405,"filePath":409,"digest":410},{"name":406,"title":407,"entrypoint":16,"actions":408},"diffuse/constituent/blur/artwork-controller","Diffuse Blur Theme | Artwork Controller",{},"src/pages/constituent/blur/artwork-controller/_manifest.json","8fc3503208f14724","constituent/blur/browser/_manifest",{"id":411,"data":413,"filePath":417,"digest":418},{"name":414,"title":415,"entrypoint":16,"actions":416},"diffuse/constituent/blur/browser","Diffuse Blur Theme | Browser",{},"src/pages/constituent/blur/browser/_manifest.json","60cf6e2d4c21ecd2","orchestrator/search-tracks/_manifest",{"id":419,"data":421,"filePath":423,"digest":291},{"name":287,"title":288,"entrypoint":16,"actions":422},{},"src/pages/orchestrator/search-tracks/_manifest.json"]
+1 -1
.astro/settings.json
··· 1 1 { 2 2 "_variables": { 3 - "lastUpdateCheck": 1756909768278 3 + "lastUpdateCheck": 1757947671266 4 4 }, 5 5 "devToolbar": { 6 6 "enabled": false
+1
deno.lock
··· 27 27 "npm:@jsr/bradenmacdonald__s3-lite-client@0.9", 28 28 "npm:@jsr/okikio__transferables@^1.0.2", 29 29 "npm:@jsr/std__media-types@^1.1.0", 30 + "npm:@lift-html/alien@^0.0.1", 30 31 "npm:@okikio/sharedworker@^1.1.0", 31 32 "npm:@orama/orama@^3.1.7", 32 33 "npm:@orama/plugin-qps@^3.1.7",
+17
package-lock.json
··· 8 8 "@automerge/automerge": "^3.0.0-beta.0", 9 9 "@bradenmacdonald/s3-lite-client": "npm:@jsr/bradenmacdonald__s3-lite-client@^0.9.0", 10 10 "@js-temporal/polyfill": "^0.5.1", 11 + "@lift-html/alien": "^0.0.1", 11 12 "@okikio/sharedworker": "^1.1.0", 12 13 "@okikio/transferables": "npm:@jsr/okikio__transferables@^1.0.2", 13 14 "@orama/orama": "^3.1.7", ··· 1142 1143 "engines": { 1143 1144 "node": ">=12" 1144 1145 } 1146 + }, 1147 + "node_modules/@lift-html/alien": { 1148 + "version": "0.0.1", 1149 + "resolved": "https://registry.npmjs.org/@lift-html/alien/-/alien-0.0.1.tgz", 1150 + "integrity": "sha512-WQJOI2rZvEW5tAvBvDkCUQMnmuhBpXt0Q6rigAt8v5XONECqj97eUqm0I5Q096BzDS2sWirlS8EkSp8ahwm5jg==", 1151 + "license": "CC0-1.0", 1152 + "dependencies": { 1153 + "@lift-html/core": "*", 1154 + "alien-signals": "*" 1155 + } 1156 + }, 1157 + "node_modules/@lift-html/core": { 1158 + "version": "0.0.4", 1159 + "resolved": "https://registry.npmjs.org/@lift-html/core/-/core-0.0.4.tgz", 1160 + "integrity": "sha512-QFfCu2PwGhN+dQ697TPuxvaeLo8swhHA0Bivypg3Owk5VcPkP6WHdzrrDVCQXstr1ANP8NFmFjX1WBM2y7ZCHA==", 1161 + "license": "CC0-1.0" 1145 1162 }, 1146 1163 "node_modules/@okikio/sharedworker": { 1147 1164 "version": "1.1.0",
+1
package.json
··· 3 3 "@automerge/automerge": "^3.0.0-beta.0", 4 4 "@bradenmacdonald/s3-lite-client": "npm:@jsr/bradenmacdonald__s3-lite-client@^0.9.0", 5 5 "@js-temporal/polyfill": "^0.5.1", 6 + "@lift-html/alien": "^0.0.1", 6 7 "@okikio/sharedworker": "^1.1.0", 7 8 "@okikio/transferables": "npm:@jsr/okikio__transferables@^1.0.2", 8 9 "@orama/orama": "^3.1.7",
+5 -9
src/pages/constituent/blur/artwork-controller/_applet.astro
··· 43 43 flex-direction: column; 44 44 font-size: var(--fs-sm); 45 45 height: 100dvh; 46 - /* max-width: var(--container-xs); */ 47 46 overflow: hidden; 48 47 position: relative; 49 48 transition: ··· 54 53 /* Artwork */ 55 54 56 55 .artwork { 56 + app-region: drag; 57 57 flex: 1; 58 58 position: relative; 59 + user-select: none; 59 60 } 60 61 61 62 .artwork img { ··· 335 336 import { FastAverageColor } from "fast-average-color"; 336 337 import { Temporal } from "@js-temporal/polyfill"; 337 338 import { xxh32r } from "xxh32/dist/raw.js"; 339 + import { debounce } from "throttle-debounce"; 338 340 339 341 import { computed, effect, signal } from "@scripts/spellcaster"; 340 342 import { tags, text, type ElementConfigurator } from "@scripts/spellcaster/hyperscript.js"; 341 343 344 + import type { Artwork } from "@applets/processor/artwork/types"; 342 345 import type { Track } from "@applets/core/types"; 343 346 import { applet, hs, inputUrl, reactive, register } from "@scripts/applet/common"; 344 347 import { trackArtworkCacheId } from "@scripts/common"; ··· 348 351 //////////////////////////////////////////// 349 352 import type * as AudioEngine from "@applets/engine/audio/types.d.ts"; 350 353 import type * as QueueEngine from "@applets/engine/queue/types.d.ts"; 351 - 352 - import type { Artwork } from "@applets/processor/artwork/types"; 353 - import { debounce } from "throttle-debounce"; 354 354 355 355 // Register 356 356 const context = register(); ··· 529 529 530 530 if (!bg || !controller || !main || !showcase) throw new Error("Missing DOM elements"); 531 531 532 - const h = ( 533 - tag: string, 534 - props?: Record<string, any> | (() => Record<string, any>), 535 - configure?: ElementConfigurator, 536 - ) => hs(tag, scope, props, configure); 532 + const h = hs(scope); 537 533 538 534 //////////////////////////////////////////// 539 535 // UI ░ ARTWORK
+1 -16
src/pages/constituent/blur/artwork-controller/_manifest.json
··· 2 2 "name": "diffuse/constituent/blur/artwork-controller", 3 3 "title": "Diffuse Blur Theme | Artwork Controller", 4 4 "entrypoint": "index.html", 5 - "actions": { 6 - "modifyIsPlaying": { 7 - "title": "Set is-playing state", 8 - "description": "Indicate if audio is playing or not.", 9 - "params_schema": { 10 - "type": "boolean" 11 - } 12 - }, 13 - "modifyProgress": { 14 - "title": "Set progress", 15 - "description": "Indicate how far the audio has progressed.", 16 - "params_schema": { 17 - "type": "number" 18 - } 19 - } 20 - } 5 + "actions": {} 21 6 }
+160
src/pages/constituent/blur/browser/_applet.astro
··· 1 + --- 2 + import "@styles/reset.css"; 3 + import "@styles/variables.css"; 4 + import "@styles/fonts.css"; 5 + import "@styles/animations.css"; 6 + import "@styles/icons/phosphor.css"; 7 + 8 + import "@styles/diffuse/colors.css"; 9 + import "@styles/diffuse/fonts.css"; 10 + --- 11 + 12 + <main> 13 + <search> 14 + <form> 15 + <input type="search" placeholder="Search" /> 16 + </form> 17 + </search> 18 + 19 + <blur-list> </blur-list> 20 + </main> 21 + 22 + <!-- STYLE --> 23 + 24 + <style> 25 + main { 26 + background: white; 27 + color: var(--color-3); 28 + display: flex; 29 + flex-direction: column; 30 + font-size: var(--fs-sm); 31 + height: 100dvh; 32 + overflow: hidden; 33 + position: relative; 34 + transition: 35 + background-color var(--transition-durition), 36 + color var(--transition-durition); 37 + } 38 + 39 + /* SEARCH */ 40 + 41 + search { 42 + & input { 43 + display: block; 44 + width: 100%; 45 + } 46 + } 47 + 48 + /* LIST */ 49 + 50 + blur-list { 51 + display: block; 52 + flex: 1; 53 + overflow: hidden; 54 + 55 + & ul { 56 + height: 100%; 57 + overflow-y: scroll; 58 + } 59 + 60 + & ul li { 61 + content-visibility: auto; 62 + contain-intrinsic-size: auto 21px; 63 + overflow: hidden; 64 + text-overflow: ellipsis; 65 + white-space: nowrap; 66 + } 67 + } 68 + </style> 69 + 70 + <style is:global> 71 + iframe { 72 + display: none; 73 + } 74 + </style> 75 + 76 + <script> 77 + import astroScope from "astro:scope"; 78 + import { effect, signal } from "alien-signals"; 79 + import { liftAlien } from "@lift-html/alien"; 80 + 81 + import { applet, reactive, register } from "@scripts/applet/common"; 82 + import type { ManagedOutput, Track } from "@applets/core/types"; 83 + 84 + //////////////////////////////////////////// 85 + // SETUP 86 + //////////////////////////////////////////// 87 + import type * as Search from "@applets/processor/search/types.d.ts"; 88 + 89 + // Register 90 + const context = register(); 91 + 92 + // Signals 93 + const $searchTerm = signal<string>(""); 94 + const $tracks = signal<Track[]>([]); 95 + 96 + // Is main group 97 + function isMainGroup() { 98 + return context.groupId === undefined || context.groupId.toLowerCase() === "main"; 99 + } 100 + 101 + // Applet connections 102 + const orchestrator = { 103 + // processTracks: isMainGroup() ? applet("/orchestrator/process-tracks") : undefined, 104 + searchTracks: isMainGroup() 105 + ? applet("/orchestrator/search-tracks", { groupId: context.groupId }) 106 + : undefined, 107 + }; 108 + 109 + const processor = { 110 + search: applet<Search.State>("/processor/search"), 111 + }; 112 + 113 + //////////////////////////////////////////// 114 + // ✨ EFFECTS 115 + //////////////////////////////////////////// 116 + 117 + processor.search.then((search) => { 118 + reactive( 119 + search, 120 + (data) => data.cacheId, 121 + async () => { 122 + console.log("Search cache id:", search.data.cacheId); 123 + const tracks = await search.sendAction("search", $searchTerm().trim(), { worker: true }); 124 + $tracks(tracks); 125 + }, 126 + ); 127 + }); 128 + 129 + //////////////////////////////////////////// 130 + // UI 131 + //////////////////////////////////////////// 132 + const html = (strings: string[] | ArrayLike<string>, ...values: any[]) => 133 + String.raw({ raw: strings }, ...values); 134 + const scope = `data-astro-cid-${astroScope}`; 135 + 136 + //////////////////////////////////////////// 137 + // UI ░ LIST 138 + //////////////////////////////////////////// 139 + 140 + liftAlien("blur-list", { 141 + observedAttributes: ["item-height"], 142 + 143 + async init() { 144 + // const props = useAttributes(this); 145 + 146 + effect(() => { 147 + this.innerHTML = html` 148 + <ul ${scope}> 149 + ${$tracks() 150 + .slice(0, 100) 151 + .map((track: Track) => { 152 + return html`<li ${scope}>${track.tags?.artist} - ${track.tags?.title}</li>`; 153 + }) 154 + .join("")} 155 + </ul> 156 + `; 157 + }); 158 + }, 159 + }); 160 + </script>
+6
src/pages/constituent/blur/browser/_manifest.json
··· 1 + { 2 + "name": "diffuse/constituent/blur/browser", 3 + "title": "Diffuse Blur Theme | Browser", 4 + "entrypoint": "index.html", 5 + "actions": {} 6 + }
+9
src/pages/constituent/blur/browser/index.astro
··· 1 + --- 2 + import Layout from "@layouts/applet.astro"; 3 + import Applet from "./_applet.astro"; 4 + import { title } from "./_manifest.json"; 5 + --- 6 + 7 + <Layout title={title}> 8 + <Applet /> 9 + </Layout>
+2 -1
src/pages/index.astro
··· 26 26 27 27 // Constituents 28 28 const constituents = [ 29 - { url: "constituent/blur/artwork-controller/", title: "(WIP) Blur ⦚ Artwork Controller" }, 29 + { url: "constituent/blur/artwork-controller/", title: "Blur ⦚ Artwork Controller" }, 30 + { url: "constituent/blur/browser/", title: "(WIP) Blur ⦚ Browser" }, 30 31 ]; 31 32 32 33 // Applets
+1 -1
src/pages/input/opensubsonic/_applet.astro
··· 46 46 47 47 const contextualize = async (tracks: Track[]) => { 48 48 const s = await worker.contextualize(transfer(tracks)); 49 - ui?.setServers({ ...ui?.servers(), ...s }); 49 + ui?.servers({ ...ui?.servers(), ...s }); 50 50 }; 51 51 52 52 const groupConsult = async (tracks: Track[]) => {
+6 -3
src/pages/orchestrator/process-tracks/_applet.astro
··· 1 1 <script> 2 2 import type { Applet } from "@web-applets/sdk"; 3 3 import type { ManagedOutput, ResolvedUri, Track } from "@applets/core/types.d.ts"; 4 - import { applet, register } from "@scripts/applet/common"; 4 + import { applet, register, wait } from "@scripts/applet/common"; 5 5 import { tracksCacheId } from "@scripts/output/common"; 6 6 7 7 //////////////////////////////////////////// ··· 112 112 //////////////////////////////////////////// 113 113 // 🚀 114 114 //////////////////////////////////////////// 115 - context.settled().then(() => { 116 - if (context.isMainInstance()) processInputs(); 115 + context.settled().then(async () => { 116 + if (context.isMainInstance()) { 117 + await wait(await configurator.output, (d) => d?.tracks.state === "loaded"); 118 + processInputs(); 119 + } 117 120 }); 118 121 </script>
-2
src/pages/orchestrator/queue-tracks/_applet.astro
··· 5 5 //////////////////////////////////////////// 6 6 // SETUP 7 7 //////////////////////////////////////////// 8 - import type * as AudioEngine from "@applets/engine/audio/types.d.ts"; 9 8 import type * as QueueEngine from "@applets/engine/queue/types.d.ts"; 10 9 11 10 const context = register(); ··· 17 16 }; 18 17 19 18 const engine = { 20 - audio: applet<AudioEngine.State>("/engine/audio", { groupId: context.groupId }), 21 19 queue: applet<QueueEngine.State>("/engine/queue", { groupId: context.groupId }), 22 20 }; 23 21
+68
src/pages/orchestrator/search-tracks/_applet.astro
··· 1 + <script> 2 + import type { GroupConsult, ManagedOutput, Track } from "@applets/core/types.d.ts"; 3 + import { applet, reactive, register, wait } from "@scripts/applet/common"; 4 + 5 + //////////////////////////////////////////// 6 + // SETUP 7 + //////////////////////////////////////////// 8 + const context = register(); 9 + 10 + // Applet connections 11 + const configurator = { 12 + input: applet("/configurator/input"), 13 + output: applet<ManagedOutput>("/configurator/output"), 14 + }; 15 + 16 + const processor = { 17 + search: applet("/processor/search"), 18 + }; 19 + 20 + //////////////////////////////////////////// 21 + // SEARCH ⭤ TRACKS 22 + //////////////////////////////////////////// 23 + async function monitorTracks() { 24 + await context.settled(); 25 + 26 + // Add tracks to the search supply once the tracks have been loaded; 27 + // and every time the collection changes. 28 + 29 + const input = await configurator.input; 30 + const output = await configurator.output; 31 + const search = await processor.search; 32 + 33 + await wait(output, (d) => d?.tracks.state === "loaded"); 34 + 35 + reactive( 36 + output, 37 + (data) => data.tracks.cacheId, 38 + async () => { 39 + if (!context.isMainInstance()) return; 40 + 41 + const groups = await input.sendAction<GroupConsult>( 42 + "groupConsult", 43 + output.data.tracks.collection, 44 + { timeoutDuration: 60000 * 5, worker: true }, 45 + ); 46 + 47 + // Available tracks 48 + let tracks: Track[] = []; 49 + 50 + Object.values(groups).forEach((value) => { 51 + if (value.available === false) return; 52 + tracks = tracks.concat(value.tracks); 53 + }, []); 54 + 55 + // Supply 56 + await search.sendAction("supply", tracks, { 57 + timeoutDuration: 60000, 58 + worker: true, 59 + }); 60 + }, 61 + ); 62 + } 63 + 64 + //////////////////////////////////////////// 65 + // 🚀 66 + //////////////////////////////////////////// 67 + monitorTracks(); 68 + </script>
+6
src/pages/orchestrator/search-tracks/_manifest.json
··· 1 + { 2 + "name": "diffuse/orchestrator/queue-tracks", 3 + "title": "Diffuse Orchestrator | Queue Tracks", 4 + "entrypoint": "index.html", 5 + "actions": {} 6 + }
+9
src/pages/orchestrator/search-tracks/index.astro
··· 1 + --- 2 + import Layout from "@layouts/applet.astro"; 3 + import Applet from "./_applet.astro"; 4 + import { title } from "./_manifest.json"; 5 + --- 6 + 7 + <Layout title={title}> 8 + <Applet /> 9 + </Layout>
+21 -6
src/pages/processor/search/_applet.astro
··· 1 1 <script> 2 2 import type { Tasks } from "@scripts/processor/search/worker"; 3 3 import type { Track } from "@applets/core/types"; 4 + import type { State } from "@scripts/processor/search/types"; 4 5 import { register } from "@scripts/applet/common"; 5 - import { endpoint, transfer } from "@scripts/common"; 6 + import { endpoint, sync, transfer } from "@scripts/common"; 6 7 import manifest from "./_manifest.json"; 7 8 8 9 //////////////////////////////////////////// 9 10 // SETUP 10 11 //////////////////////////////////////////// 11 - const worker = endpoint<Tasks>( 12 - new SharedWorker(new URL("../../../scripts/processor/search/worker", import.meta.url), { 12 + const port = new SharedWorker( 13 + new URL("../../../scripts/processor/search/worker", import.meta.url), 14 + { 13 15 type: "module", 14 16 name: manifest.name, 15 - }).port, 16 - ); 17 + }, 18 + ).port; 19 + 20 + const worker = endpoint<Tasks>(port); 17 21 18 22 // Register applet 19 - const context = register({ worker }); 23 + const context = register<State>({ worker, mode: "shared-worker" }); 24 + 25 + // Initial state 26 + context.data = { 27 + cacheId: "", 28 + inserted: new Set<string>(), 29 + }; 30 + 31 + context.data = await worker.data(); 32 + 33 + // Keep applet data with worker data in sync 34 + sync(context, port); 20 35 21 36 //////////////////////////////////////////// 22 37 // ACTIONS
+1
src/pages/processor/search/types.d.ts
··· 1 + export * from "@scripts/processor/search/types.d.ts";
+14 -13
src/scripts/applet/common.ts
··· 4 4 import { applets } from "@web-applets/sdk"; 5 5 import QS from "query-string"; 6 6 7 - import { type ElementConfigurator, h } from "@scripts/spellcaster/hyperscript.js"; 7 + import { type ElementConfigurator, h as hyperscript } from "@scripts/spellcaster/hyperscript.js"; 8 8 import { isSignal, type Signal, signal } from "@scripts/spellcaster"; 9 9 10 10 import type { ResolvedUri } from "@applets/core/types"; ··· 462 462 return () => port; 463 463 } 464 464 465 - export function hs( 466 - tag: string, 467 - astroScope: string, 468 - props?: Record<string, unknown> | Signal<Record<string, unknown>>, 469 - configure?: ElementConfigurator, 470 - ) { 471 - const propsWithScope = 472 - props && isSignal(props) 473 - ? () => addScope(astroScope, props()) 474 - : addScope(astroScope, props || {}); 465 + export const hs = 466 + (astroScope: string) => 467 + ( 468 + tag: string, 469 + props?: Record<string, any> | (() => Record<string, any>), 470 + configure?: ElementConfigurator, 471 + ) => { 472 + const propsWithScope = 473 + props && isSignal(props) 474 + ? () => addScope(astroScope, props()) 475 + : addScope(astroScope, props || {}); 475 476 476 - return h(tag, propsWithScope, configure); 477 - } 477 + return hyperscript(tag, propsWithScope, configure); 478 + }; 478 479 479 480 export function wait<A>(applet: Applet<A>, dataFn: (a: A | undefined) => boolean): Promise<void> { 480 481 return new Promise((resolve) => {
+1 -1
src/scripts/engine/queue/worker.ts
··· 2 2 3 3 import type { Track } from "@applets/core/types.js"; 4 4 import type { Item, State } from "./types"; 5 - import { arrayShuffle, postMessages, provide, transfer } from "@scripts/common.ts"; 5 + import { arrayShuffle, postMessages, provide } from "@scripts/common.ts"; 6 6 7 7 //////////////////////////////////////////// 8 8 // SETUP
+3 -1
src/scripts/input/opensubsonic/worker.ts
··· 58 58 : { available, reason: "Server ping failed", tracks }; 59 59 60 60 return { 61 - key: `${SCHEME}:${serverId}`, 61 + // key: `${SCHEME}:${serverId}`, 62 + key: SCHEME, 62 63 grouping, 63 64 }; 64 65 }); ··· 115 116 ? song.path 116 117 : `/${song.path}` 117 118 : undefined; 119 + 118 120 const fromCache = path ? cache[sid]?.[path] : undefined; 119 121 if (fromCache) return fromCache; 120 122
+1
src/scripts/processor/search/types.d.ts
··· 1 1 export type State = { 2 + cacheId: string; 2 3 inserted: Set<string>; 3 4 };
+61 -21
src/scripts/processor/search/worker.ts
··· 1 1 import * as Orama from "@orama/orama"; 2 + import { getTransferables } from "@okikio/transferables"; 3 + import { xxh32 } from "xxh32"; 2 4 // import { pluginQPS } from "@orama/plugin-qps"; 3 5 4 6 import type { Track } from "@applets/core/types"; 5 - import { expose, provide, transfer } from "@scripts/common"; 7 + import type { State } from "./types"; 8 + import { postMessages, provide, transfer } from "@scripts/common"; 6 9 import { SCHEMA } from "./constants"; 7 - import type { State } from "./types"; 8 10 9 11 //////////////////////////////////////////// 10 12 // SETUP 11 13 //////////////////////////////////////////// 12 14 15 + const actions = { 16 + search, 17 + supply, 18 + }; 19 + 20 + const { ports, tasks } = provide({ 21 + actions, 22 + tasks: { ...actions, data }, 23 + }); 24 + 25 + export type Actions = typeof actions; 26 + export type Tasks = typeof tasks; 27 + 28 + //////////////////////////////////////////// 29 + // STATE 30 + //////////////////////////////////////////// 31 + 13 32 let state: State = { 33 + cacheId: "", 14 34 inserted: new Set<string>(), 15 35 }; 36 + 37 + function data() { 38 + return state; 39 + } 40 + 41 + function notify() { 42 + const d = data(); 43 + 44 + postMessages({ 45 + data: { 46 + type: "data", 47 + data: d, 48 + }, 49 + ports: ports.applets, 50 + transfer: getTransferables(d), 51 + }); 52 + } 16 53 17 54 // TODO: Generate embeddings plugin 18 55 // ··· 52 89 // }, 53 90 }); 54 91 55 - // 🚀 56 - 57 - const actions = { 58 - search, 59 - supply, 60 - }; 61 - 62 - const { tasks } = provide({ 63 - actions, 64 - tasks: actions, 65 - }); 66 - 67 - export type Actions = typeof actions; 68 - export type Tasks = typeof tasks; 69 - 70 92 //////////////////////////////////////////// 71 93 // ACTIONS 72 94 //////////////////////////////////////////// 73 95 74 96 async function search(term: string): Promise<Track[]> { 97 + term = term.trim(); 98 + const tracks: Track[] = await _search(term, []); 99 + return transfer(tracks); 100 + } 101 + 102 + async function _search(term: string, tracks: Track[]) { 103 + console.log("Search with offset:", tracks.length); 104 + 75 105 const results = await Orama.search(db, { 76 106 // mode: "hybrid", 77 107 term, 108 + limit: 10000, 109 + offset: tracks.length, 78 110 }); 79 111 80 - const tracks = results.hits.map((hit) => hit.document as unknown as Track); 81 - return transfer(tracks); 112 + const allTracks = tracks.concat(results.hits.map((hit) => hit.document as unknown as Track)); 113 + 114 + if (allTracks.length < results.count) { 115 + return await _search(term, allTracks); 116 + } else { 117 + return allTracks; 118 + } 82 119 } 83 120 84 121 async function supply(tracks: Track[]) { 85 122 // TODO: Generate a hash based on the track itself, 86 123 // so we can detect changes to tags or other data. 87 124 88 - const ids = []; 125 + const ids: string[] = []; 89 126 const tracksMap: Record<string, Track> = {}; 90 127 91 128 tracks.forEach((track) => { ··· 94 131 }); 95 132 96 133 const currentSet = state.inserted; 97 - const newSet = new Set(tracks.map((t) => t.id)); 134 + const newSet = new Set(ids); 98 135 99 136 const removedIds = currentSet.difference(newSet); 100 137 const newIds = newSet.difference(currentSet); ··· 104 141 await Orama.insertMultiple(db, newTracks); 105 142 106 143 state.inserted = newSet; 144 + state.cacheId = xxh32(ids.sort().join("")).toString(); 145 + 146 + notify(); 107 147 }
-65
src/scripts/signal.ts
··· 1 - import { Signal } from "signal-polyfill"; 2 - 3 - // SIGNAL 4 - 5 - export type Signal<T> = () => T; 6 - 7 - export const signal = <T>(initial: T): [Signal<T>, (value: T) => void] => { 8 - const state = new Signal.State(initial); 9 - const get = () => state.get(); 10 - const set = (value: T) => state.set(value); 11 - return [get, set]; 12 - }; 13 - 14 - // EFFECT 15 - 16 - export const throttled = ( 17 - job: () => void, 18 - queue: (callback: () => void) => void = queueMicrotask, 19 - ): (() => void) => { 20 - let isScheduled = false; 21 - 22 - const perform = () => { 23 - job(); 24 - isScheduled = false; 25 - }; 26 - 27 - const schedule = () => { 28 - if (!isScheduled) { 29 - isScheduled = true; 30 - queue(perform); 31 - } 32 - }; 33 - 34 - return schedule; 35 - }; 36 - 37 - const watcher = new Signal.subtle.Watcher( 38 - throttled(() => { 39 - for (const signal of watcher.getPending()) { 40 - signal.get(); 41 - } 42 - watcher.watch(); 43 - }), 44 - ); 45 - 46 - export type Cancel = () => void; 47 - 48 - export const effect = (perform: () => Cancel | void) => { 49 - let cleanup: Cancel | undefined; 50 - 51 - const signal = new Signal.Computed(() => { 52 - cleanup?.(); 53 - cleanup = perform() ?? undefined; 54 - }); 55 - 56 - watcher.watch(signal); 57 - signal.get(); 58 - 59 - const dispose = () => { 60 - cleanup?.(); 61 - watcher.unwatch(signal); 62 - }; 63 - 64 - return dispose; 65 - };
+2
src/scripts/spellcaster/spellcaster.ts
··· 1 1 import { computed } from "alien-signals"; 2 2 3 + export type Props = Record<string, any> | (() => Record<string, any>); 4 + 3 5 /** 4 6 * A signal is a zero-argument function that returns a value. 5 7 * Reactive signals created with `signal()` will cause reactive contexts
+3
src/scripts/theme/blur/index.ts
··· 25 25 container, 26 26 groupId: labelB, 27 27 }), 28 + browser: applet("/constituent/blur/browser", { 29 + container, 30 + }), 28 31 }; 29 32 30 33 // TODO:
-2
src/scripts/theme/webamp/index.ts
··· 13 13 }; 14 14 15 15 const orchestrator = { 16 - queueAudio: applet("/orchestrator/queue-audio"), 17 - queueTracks: applet("/orchestrator/queue-tracks"), 18 16 processTracks: applet("/orchestrator/process-tracks"), 19 17 }; 20 18
+8
src/styles/theme/blur/index.css
··· 76 76 max-width: var(--container-3xs); 77 77 width: 100%; 78 78 } 79 + 80 + iframe[src*="/browser/"] { 81 + grid-column: 1 / span 2; 82 + grid-row: 1 / span 2; 83 + height: 100%; 84 + justify-self: end; 85 + width: 100%; 86 + }