Canonical repo for Dong Web (dong.vielle.dev)
0
fork

Configure Feed

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

break audio somewhere also move logic to other file to make it easier to reuse (will be seperate package later)

+226 -137
+154
src/dong-io.ts
··· 1 + declare global { 2 + interface Uint8Array { 3 + toBase64(): string; 4 + } 5 + } 6 + 7 + const blobBytes = async (blob: Blob) => { 8 + if ("bytes" in blob) return blob.bytes(); 9 + return new Response(blob).arrayBuffer().then((buffer) => { 10 + const uint = new Uint8Array(buffer); 11 + return uint; 12 + }); 13 + }; 14 + 15 + const uint8array64 = (arru8: Uint8Array) => { 16 + if ("toBase64" in arru8) return arru8.toBase64(); 17 + 18 + function _arrayBufferToBase64(bytes: Uint8Array) { 19 + var binary = ""; 20 + var len = bytes.byteLength; 21 + for (var i = 0; i < len; i++) { 22 + binary += String.fromCharCode(bytes[i]); 23 + } 24 + return btoa(binary); 25 + } 26 + return _arrayBufferToBase64(arru8); 27 + }; 28 + 29 + export const createDong = async ( 30 + image: File, 31 + audio: File 32 + ): Promise<Blob | string> => { 33 + if (image.type === "" || audio.type === "") return "Mime types invalid"; 34 + return new Blob([ 35 + // version 36 + (() => { 37 + const version = new Int8Array(new ArrayBuffer(2)); 38 + version[0] = 0xd0; 39 + version[1] = 2; 40 + return version; 41 + })(), 42 + // image type 43 + image.type, 44 + // 00 padding 45 + new ArrayBuffer(256 - image.type.length), 46 + // image size 47 + (() => { 48 + const value = new Uint32Array(1); 49 + value[0] = image.size; 50 + return value; 51 + })(), 52 + // audio type 53 + audio.type, 54 + // 00 padding 55 + new ArrayBuffer(256 - audio.type.length), 56 + // audio size 57 + (() => { 58 + const value = new Uint32Array(1); 59 + value[0] = audio.size; 60 + return value; 61 + })(), 62 + // image data 63 + await blobBytes(image), 64 + // audio data 65 + await blobBytes(audio), 66 + ]); 67 + }; 68 + 69 + // base 64 overload 70 + export async function readDong( 71 + dongFile: File, 72 + opts?: { b64: true } 73 + ): Promise< 74 + | { 75 + image: { data: string; mime: string }; 76 + audio: { data: string; mime: string }; 77 + } 78 + | string 79 + >; 80 + // standard overload 81 + export async function readDong( 82 + dongFile: File, 83 + opts?: { b64: false } 84 + ): Promise< 85 + | { 86 + image: { data: Uint8Array; mime: string }; 87 + audio: { data: Uint8Array; mime: string }; 88 + } 89 + | string 90 + >; 91 + 92 + export async function readDong( 93 + dongFile: File, 94 + opts?: { b64: boolean } 95 + ): Promise< 96 + | { 97 + image: { data: Uint8Array | string; mime: string }; 98 + audio: { data: Uint8Array | string; mime: string }; 99 + } 100 + | string 101 + > { 102 + // get first 2 bytes and verify 103 + const version = new Uint8Array(await blobBytes(dongFile.slice(0, 2))); 104 + if (version[0] !== 0xd0 || version[1] !== 2) return "Invalid file"; 105 + 106 + // get next 256 bytes and get mime type 107 + const imgMimeType: string | undefined = ( 108 + await dongFile.slice(2, 258).text() 109 + ).match(/[a-zA-Z0-9.]+\/[a-zA-Z0-9.]+/gm)?.[0]; 110 + if (!imgMimeType) return "Image mime type parse failed"; 111 + 112 + // get next 4 bytes and get image size 113 + const imgSize = new Uint32Array( 114 + (await blobBytes(dongFile.slice(258, 262))).buffer 115 + )[0]; 116 + 117 + // get next 256 bytes and get mime type 118 + const audMimeType: string | undefined = ( 119 + await dongFile.slice(262, 518).text() 120 + ).match(/[a-zA-Z0-9.]+\/[a-zA-Z0-9.]+/gm)?.[0]; 121 + if (!audMimeType) return "Audio mime type parse failed"; 122 + 123 + // get next 4 bytes and get image size 124 + const audSize = new Uint32Array( 125 + (await blobBytes(dongFile.slice(518, 522))).buffer 126 + )[0]; 127 + 128 + const imageBytes = await blobBytes(dongFile.slice(522, 522 + imgSize)); 129 + const audioBytes = await blobBytes( 130 + dongFile.slice(522 + imgSize, 522 + imgSize + audSize) 131 + ); 132 + 133 + return { 134 + image: { 135 + mime: imgMimeType, 136 + data: opts?.b64 ? uint8array64(imageBytes) : imageBytes, 137 + }, 138 + audio: { 139 + mime: audMimeType, 140 + data: opts?.b64 ? uint8array64(audioBytes) : audioBytes, 141 + }, 142 + }; 143 + } 144 + 145 + export const download = (file: File) => { 146 + const url = URL.createObjectURL(file); 147 + const a = document.createElement("a"); 148 + a.href = url; 149 + a.download = file.name; 150 + document.body.appendChild(a); 151 + a.click(); 152 + document.body.removeChild(a); 153 + URL.revokeObjectURL(url); 154 + };
+72 -137
src/pages/index.astro
··· 4 4 5 5 <Base title="Dong Web"> 6 6 <script> 7 + import { createDong, download, readDong } from "../dong-io"; 8 + 7 9 declare global { 8 10 interface Uint8Array { 9 11 toBase64(): string; ··· 34 36 35 37 class CreateDong extends HTMLElement { 36 38 connectedCallback() { 37 - const shadow = this.attachShadow({ mode: "open" }); 38 - 39 39 // create input 40 40 const form = document.createElement("form"); 41 + 42 + const imageLabel = document.createElement("label"); 43 + const audioLabel = document.createElement("label"); 41 44 const imageSelect = document.createElement("input"); 42 45 const audioSelect = document.createElement("input"); 46 + 47 + const filename = document.createElement("input"); 43 48 const createButton = document.createElement("button"); 49 + 50 + const errormsg = document.createElement("div"); 44 51 45 52 imageSelect.type = "file"; 46 53 imageSelect.accept = "image/*"; 54 + imageSelect.id = "image-select"; 55 + 47 56 audioSelect.type = "file"; 48 57 audioSelect.accept = "audio/*"; 58 + audioSelect.id = "audio-select"; 59 + 60 + imageLabel.innerText = "Image"; 61 + imageLabel.htmlFor = imageSelect.id; 62 + audioLabel.innerText = "Audio"; 63 + audioLabel.htmlFor = audioSelect.id; 64 + 49 65 createButton.type = "submit"; 50 66 createButton.textContent = "Create"; 51 67 52 - form.appendChild(imageSelect); 53 - form.appendChild(audioSelect); 68 + imageLabel.appendChild(imageSelect); 69 + audioLabel.appendChild(audioSelect); 70 + 71 + form.appendChild(imageLabel); 72 + form.appendChild(audioLabel); 73 + form.appendChild(filename); 54 74 form.appendChild(createButton); 75 + form.appendChild(errormsg); 55 76 56 - shadow.appendChild(form); 77 + this.appendChild(form); 57 78 58 79 // functionality 59 80 createButton.addEventListener("click", async (e) => { ··· 65 86 !audioSelect.files || 66 87 imageSelect.files.length === 0 || 67 88 audioSelect.files.length === 0 68 - ) 69 - return console.warn("No files selected"); 89 + ) { 90 + errormsg.innerText = "No files selected"; 91 + return; 92 + } 70 93 71 94 // get files 72 95 const image = imageSelect.files[0]; 73 96 const audio = audioSelect.files[0]; 74 97 75 - if (image.type === "" || audio.type === "") 76 - return console.warn("Mime types invalid"); 98 + const res = await createDong(image, audio); 99 + // if error, show it 100 + if (typeof res === "string") { 101 + errormsg.innerText = res; 102 + return; 103 + } 77 104 78 - const dongFile = new File( 79 - [ 80 - // version 81 - (() => { 82 - const version = new Int8Array(new ArrayBuffer(2)); 83 - version[0] = 0xd0; 84 - version[1] = 2; 85 - return version; 86 - })(), 87 - // image type 88 - image.type, 89 - // 00 padding 90 - new ArrayBuffer(256 - image.type.length), 91 - // image size 92 - (() => { 93 - const value = new Uint32Array(1); 94 - value[0] = image.size; 95 - return value; 96 - })(), 97 - // audio type 98 - audio.type, 99 - // 00 padding 100 - new ArrayBuffer(256 - audio.type.length), 101 - // audio size 102 - (() => { 103 - const value = new Uint32Array(1); 104 - value[0] = audio.size; 105 - return value; 106 - })(), 107 - // image data 108 - await blobBytes(image), 109 - // audio data 110 - await blobBytes(audio), 111 - ], 112 - `${prompt("Filename:") ?? "file"}.dong`, 113 - { 114 - type: "application/prs.vielle.dong", 115 - } 116 - ); 105 + const dongFile = new File([res], `${filename.value}.dong`, { 106 + type: "application/prs.vielle.dong", 107 + }); 117 108 118 109 // download the dong file 119 - const url = URL.createObjectURL(dongFile); 120 - const a = document.createElement("a"); 121 - a.href = url; 122 - a.download = dongFile.name; 123 - document.body.appendChild(a); 124 - a.click(); 125 - document.body.removeChild(a); 126 - URL.revokeObjectURL(url); 110 + download(dongFile); 127 111 }); 128 112 } 129 113 } 130 114 131 115 class LoadDong extends HTMLElement { 132 116 connectedCallback() { 133 - const shadow = this.attachShadow({ mode: "open" }); 134 - 135 117 // create input 136 118 const form = document.createElement("form"); 137 119 const dongSelect = document.createElement("input"); 138 120 const loadButton = document.createElement("button"); 139 121 // image 122 + const errormsg = document.createElement("div"); 140 123 const image = document.createElement("img"); 141 124 142 125 // do not append as this is only for playing audio 143 126 // loaded here to prevent overlaying the sound 144 127 const audio = document.createElement("audio"); 145 128 146 - 147 129 dongSelect.type = "file"; 148 130 dongSelect.accept = ".dong"; 149 131 loadButton.type = "submit"; ··· 154 136 form.appendChild(dongSelect); 155 137 form.appendChild(loadButton); 156 138 157 - shadow.appendChild(form); 158 - shadow.appendChild(image); 159 - 160 - const sheet = new CSSStyleSheet(); 161 - sheet.replaceSync(` 162 - image { 163 - border: 1px solid white; 164 - } 165 - `); 166 - 167 - shadow.adoptedStyleSheets = [sheet]; 139 + this.appendChild(form); 140 + this.appendChild(image); 141 + this.appendChild(errormsg); 168 142 169 143 // functionality 170 144 loadButton.addEventListener("click", async (e) => { ··· 172 146 e.preventDefault(); 173 147 // quit early if no files 174 148 if (!dongSelect.files || dongSelect.files.length === 0) 175 - return console.warn("No files selected"); 149 + return (errormsg.innerText = "No files selected"); 176 150 177 151 // get files 178 152 const dongFile = dongSelect.files[0]; 179 153 180 - // get first 2 bytes and verify 181 - const version = new Uint8Array(await blobBytes(dongFile.slice(0, 2))); 182 - if (version[0] !== 0xd0 || version[1] !== 2) 183 - return console.warn("Invalid file"); 184 - 185 - // get next 256 bytes and get mime type 186 - const imgMimeType: string | undefined = ( 187 - await dongFile.slice(2, 258).text() 188 - ).match(/[a-zA-Z0-9.]+\/[a-zA-Z0-9.]+/gm)?.[0]; 189 - if (!imgMimeType) return console.warn("Image mime type parse failed"); 190 - 191 - // get next 4 bytes and get image size 192 - const imgSize = new Uint32Array( 193 - (await blobBytes(dongFile.slice(258, 262))).buffer 194 - )[0]; 195 - 196 - // get next 256 bytes and get mime type 197 - const audMimeType: string | undefined = ( 198 - await dongFile.slice(262, 518).text() 199 - ).match(/[a-zA-Z0-9.]+\/[a-zA-Z0-9.]+/gm)?.[0]; 200 - if (!audMimeType) return console.warn("Audio mime type parse failed"); 201 - 202 - // get next 4 bytes and get image size 203 - const audSize = new Uint32Array( 204 - (await blobBytes(dongFile.slice(518, 522))).buffer 205 - )[0]; 206 - 207 - const imageBytes = await blobBytes( 208 - dongFile.slice(522, 522 + imgSize) 209 - ); 210 - const audioBytes = await blobBytes( 211 - dongFile.slice(522 + imgSize, 522 + imgSize + audSize) 212 - ); 154 + const res = await readDong(dongFile, { b64: true }); 155 + if (typeof res === "string") { 156 + errormsg.innerText = res; 157 + return; 158 + } 213 159 214 - image.src = `data:${imgMimeType};base64,${uint8array64(imageBytes)}`; 160 + image.src = `data:${res.image.mime};base64,${res.image.data}`; 215 161 216 162 // audio play 217 - audio.src = `data:${audMimeType};base64,${uint8array64(audioBytes)}`; 163 + console.log("audio loaded") 164 + audio.src = `data:${res.audio.mime};base64,${res.image.data}`; 165 + console.log("audio played") 166 + console.log(audio.src) 218 167 audio.play(); 219 168 }); 220 169 } ··· 224 173 customElements.define("load-dong", LoadDong); 225 174 </script> 226 175 227 - <style slot="head"> 228 - ul { 229 - padding-left: 3rem; 176 + <style slot="head" is:inline> 177 + button { 178 + background-color: #f80085; 230 179 } 231 - </style> 232 180 233 - <h1> 234 - Primarily tested on firefox. Be warned if using iOS/another browser 235 - </h1> 181 + label { 182 + background-color: #f80085; 236 183 237 - <p>Known supported browsers</p> 238 - <ul> 239 - <li>Firefox (or Gecko based)</li> 240 - <li>Chromium (or Chromium based)</li> 241 - </ul> 184 + & > input[type="file"] { 185 + display: none; 186 + } 187 + } 188 + </style> 242 189 190 + <!-- logo --> 191 + 192 + <!-- creation --> 243 193 <create-dong></create-dong> 244 - <load-dong></load-dong> 245 194 246 - <!-- <form action=""> 247 - <label for="image"> 248 - Pick an image file 249 - <input type="file" id="image" name="image" accept="image/*" /> 250 - </label> 251 - <label for="audio"> 252 - Pick an audio file 253 - <input type="file" id="audio" name="audio" accept="audio/*" /> 254 - </label> 255 - <button id="create">Create</button> 256 - </form> --> 257 - <!-- <form action=""> 258 - <label> 259 - Pick a dong file 260 - <input type="file" id="dong" name="dong" accept=".dong" /> 261 - </label> 262 - <canvas id="renderer"></canvas> 263 - </form> --> 195 + <hr /> 196 + 197 + <!-- loading --> 198 + <load-dong></load-dong> 264 199 </Base>