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

Compare changes

Choose any two refs to compare.

+3 -2
astro.config.mjs
··· 1 1 // @ts-check 2 - import { defineConfig } from 'astro/config'; 2 + import { defineConfig } from "astro/config"; 3 3 4 4 // https://astro.build/config 5 - export default defineConfig({}); 5 + export default defineConfig({ 6 + });
+107 -7
pnpm-lock.yaml
··· 10 10 dependencies: 11 11 astro: 12 12 specifier: ^5.1.8 13 - version: 5.1.8(rollup@4.31.0)(typescript@5.7.3) 13 + version: 5.1.8(@types/node@22.10.10)(rollup@4.31.0)(terser@5.37.0)(typescript@5.7.3) 14 14 15 15 packages: 16 16 ··· 306 306 cpu: [x64] 307 307 os: [win32] 308 308 309 + '@jridgewell/gen-mapping@0.3.8': 310 + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} 311 + engines: {node: '>=6.0.0'} 312 + 313 + '@jridgewell/resolve-uri@3.1.2': 314 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 315 + engines: {node: '>=6.0.0'} 316 + 317 + '@jridgewell/set-array@1.2.1': 318 + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 319 + engines: {node: '>=6.0.0'} 320 + 321 + '@jridgewell/source-map@0.3.6': 322 + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} 323 + 309 324 '@jridgewell/sourcemap-codec@1.5.0': 310 325 resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 326 + 327 + '@jridgewell/trace-mapping@0.3.25': 328 + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 311 329 312 330 '@nodelib/fs.scandir@2.1.5': 313 331 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} ··· 470 488 '@types/nlcst@2.0.3': 471 489 resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} 472 490 491 + '@types/node@22.10.10': 492 + resolution: {integrity: sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==} 493 + 473 494 '@types/unist@3.0.3': 474 495 resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} 475 496 ··· 540 561 resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 541 562 engines: {node: '>=8'} 542 563 564 + buffer-from@1.1.2: 565 + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 566 + 543 567 camelcase@8.0.0: 544 568 resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} 545 569 engines: {node: '>=16'} ··· 592 616 593 617 comma-separated-tokens@2.0.3: 594 618 resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} 619 + 620 + commander@2.20.3: 621 + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 595 622 596 623 common-ancestor-path@1.0.1: 597 624 resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} ··· 1216 1243 resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1217 1244 engines: {node: '>=0.10.0'} 1218 1245 1246 + source-map-support@0.5.21: 1247 + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 1248 + 1249 + source-map@0.6.1: 1250 + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 1251 + engines: {node: '>=0.10.0'} 1252 + 1219 1253 space-separated-tokens@2.0.2: 1220 1254 resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} 1221 1255 ··· 1244 1278 strip-bom@3.0.0: 1245 1279 resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} 1246 1280 engines: {node: '>=4'} 1281 + 1282 + terser@5.37.0: 1283 + resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} 1284 + engines: {node: '>=10'} 1285 + hasBin: true 1247 1286 1248 1287 tinyexec@0.3.2: 1249 1288 resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} ··· 1288 1327 1289 1328 uncrypto@0.1.3: 1290 1329 resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} 1330 + 1331 + undici-types@6.20.0: 1332 + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 1291 1333 1292 1334 unenv@1.10.0: 1293 1335 resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==} ··· 1707 1749 '@img/sharp-win32-x64@0.33.5': 1708 1750 optional: true 1709 1751 1752 + '@jridgewell/gen-mapping@0.3.8': 1753 + dependencies: 1754 + '@jridgewell/set-array': 1.2.1 1755 + '@jridgewell/sourcemap-codec': 1.5.0 1756 + '@jridgewell/trace-mapping': 0.3.25 1757 + optional: true 1758 + 1759 + '@jridgewell/resolve-uri@3.1.2': 1760 + optional: true 1761 + 1762 + '@jridgewell/set-array@1.2.1': 1763 + optional: true 1764 + 1765 + '@jridgewell/source-map@0.3.6': 1766 + dependencies: 1767 + '@jridgewell/gen-mapping': 0.3.8 1768 + '@jridgewell/trace-mapping': 0.3.25 1769 + optional: true 1770 + 1710 1771 '@jridgewell/sourcemap-codec@1.5.0': {} 1772 + 1773 + '@jridgewell/trace-mapping@0.3.25': 1774 + dependencies: 1775 + '@jridgewell/resolve-uri': 3.1.2 1776 + '@jridgewell/sourcemap-codec': 1.5.0 1777 + optional: true 1711 1778 1712 1779 '@nodelib/fs.scandir@2.1.5': 1713 1780 dependencies: ··· 1845 1912 dependencies: 1846 1913 '@types/unist': 3.0.3 1847 1914 1915 + '@types/node@22.10.10': 1916 + dependencies: 1917 + undici-types: 6.20.0 1918 + optional: true 1919 + 1848 1920 '@types/unist@3.0.3': {} 1849 1921 1850 1922 '@ungap/structured-clone@1.2.1': {} ··· 1876 1948 1877 1949 array-iterate@2.0.1: {} 1878 1950 1879 - astro@5.1.8(rollup@4.31.0)(typescript@5.7.3): 1951 + astro@5.1.8(@types/node@22.10.10)(rollup@4.31.0)(terser@5.37.0)(typescript@5.7.3): 1880 1952 dependencies: 1881 1953 '@astrojs/compiler': 2.10.3 1882 1954 '@astrojs/internal-helpers': 0.4.2 ··· 1928 2000 unist-util-visit: 5.0.0 1929 2001 unstorage: 1.14.4 1930 2002 vfile: 6.0.3 1931 - vite: 6.0.11 1932 - vitefu: 1.0.5(vite@6.0.11) 2003 + vite: 6.0.11(@types/node@22.10.10)(terser@5.37.0) 2004 + vitefu: 1.0.5(vite@6.0.11(@types/node@22.10.10)(terser@5.37.0)) 1933 2005 which-pm: 3.0.0 1934 2006 xxhash-wasm: 1.1.0 1935 2007 yargs-parser: 21.1.1 ··· 1996 2068 dependencies: 1997 2069 fill-range: 7.1.1 1998 2070 2071 + buffer-from@1.1.2: 2072 + optional: true 2073 + 1999 2074 camelcase@8.0.0: {} 2000 2075 2001 2076 ccount@2.0.1: {} ··· 2047 2122 optional: true 2048 2123 2049 2124 comma-separated-tokens@2.0.3: {} 2125 + 2126 + commander@2.20.3: 2127 + optional: true 2050 2128 2051 2129 common-ancestor-path@1.0.1: {} 2052 2130 ··· 2980 3058 2981 3059 source-map-js@1.2.1: {} 2982 3060 3061 + source-map-support@0.5.21: 3062 + dependencies: 3063 + buffer-from: 1.1.2 3064 + source-map: 0.6.1 3065 + optional: true 3066 + 3067 + source-map@0.6.1: 3068 + optional: true 3069 + 2983 3070 space-separated-tokens@2.0.2: {} 2984 3071 2985 3072 sprintf-js@1.0.3: {} ··· 3011 3098 3012 3099 strip-bom@3.0.0: {} 3013 3100 3101 + terser@5.37.0: 3102 + dependencies: 3103 + '@jridgewell/source-map': 0.3.6 3104 + acorn: 8.14.0 3105 + commander: 2.20.3 3106 + source-map-support: 0.5.21 3107 + optional: true 3108 + 3014 3109 tinyexec@0.3.2: {} 3015 3110 3016 3111 to-regex-range@5.0.1: ··· 3037 3132 ultrahtml@1.5.3: {} 3038 3133 3039 3134 uncrypto@0.1.3: {} 3135 + 3136 + undici-types@6.20.0: 3137 + optional: true 3040 3138 3041 3139 unenv@1.10.0: 3042 3140 dependencies: ··· 3124 3222 '@types/unist': 3.0.3 3125 3223 vfile-message: 4.0.2 3126 3224 3127 - vite@6.0.11: 3225 + vite@6.0.11(@types/node@22.10.10)(terser@5.37.0): 3128 3226 dependencies: 3129 3227 esbuild: 0.24.2 3130 3228 postcss: 8.5.1 3131 3229 rollup: 4.31.0 3132 3230 optionalDependencies: 3231 + '@types/node': 22.10.10 3133 3232 fsevents: 2.3.3 3233 + terser: 5.37.0 3134 3234 3135 - vitefu@1.0.5(vite@6.0.11): 3235 + vitefu@1.0.5(vite@6.0.11(@types/node@22.10.10)(terser@5.37.0)): 3136 3236 optionalDependencies: 3137 - vite: 6.0.11 3237 + vite: 6.0.11(@types/node@22.10.10)(terser@5.37.0) 3138 3238 3139 3239 web-namespaces@2.0.1: {} 3140 3240
+18
public/sw.js
··· 1 + // This is the "Offline copy of pages" service worker 2 + 3 + const CACHE = "pwabuilder-offline"; 4 + 5 + importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js'); 6 + 7 + self.addEventListener("message", (event) => { 8 + if (event.data && event.data.type === "SKIP_WAITING") { 9 + self.skipWaiting(); 10 + } 11 + }); 12 + 13 + workbox.routing.registerRoute( 14 + new RegExp('/*'), 15 + new workbox.strategies.StaleWhileRevalidate({ 16 + cacheName: CACHE 17 + }) 18 + );
+10 -44
src/dong-io.ts
··· 12 12 }); 13 13 }; 14 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 15 export const createDong = async ( 30 16 image: File, 31 17 audio: File 32 18 ): Promise<Blob | string> => { 33 - if (image.type === "" || audio.type === "") return "Mime types invalid"; 19 + if ( 20 + image.type === "" || 21 + !image.type.startsWith("image/") || 22 + audio.type === "" || 23 + !audio.type.startsWith("audio/") 24 + ) 25 + return "Mime types invalid"; 34 26 return new Blob([ 35 27 // version 36 28 (() => { ··· 66 58 ]); 67 59 }; 68 60 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< 61 + export async function readDong(dongFile: File): Promise< 85 62 | { 86 63 image: { data: Uint8Array; mime: string }; 87 64 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 65 } 100 66 | string 101 67 > { ··· 133 99 return { 134 100 image: { 135 101 mime: imgMimeType, 136 - data: opts?.b64 ? uint8array64(imageBytes) : imageBytes, 102 + data: imageBytes, 137 103 }, 138 104 audio: { 139 105 mime: audMimeType, 140 - data: opts?.b64 ? uint8array64(audioBytes) : audioBytes, 106 + data: audioBytes, 141 107 }, 142 108 }; 143 109 }
+7 -1
src/layouts/Base.astro
··· 16 16 <meta name="generator" content={Astro.generator} /> 17 17 <title>{title}</title> 18 18 19 + <script is:inline> 20 + if (typeof navigator.serviceWorker !== 'undefined') { 21 + navigator.serviceWorker.register('sw.js') 22 + } 23 + </script> 24 + 19 25 <style is:global> 20 26 /* instantly overwrite reset */ 21 27 @layer reset { ··· 37 43 padding: 0; 38 44 box-sizing: border-box; 39 45 appearance: none; 40 - color-scheme: dark; 41 46 } 42 47 43 48 img, ··· 80 85 } 81 86 } 82 87 </style> 88 + 83 89 <slot name="head" /> 84 90 </head> 85 91 <body>
+185 -59
src/pages/index.astro
··· 6 6 <script> 7 7 import { createDong, download, readDong } from "../dong-io"; 8 8 9 - declare global { 10 - interface Uint8Array { 11 - toBase64(): string; 12 - } 13 - } 14 - 15 - const blobBytes = async (blob: Blob) => { 16 - if ("bytes" in blob) return blob.bytes(); 17 - return new Response(blob).arrayBuffer().then((buffer) => { 18 - const uint = new Uint8Array(buffer); 19 - return uint; 20 - }); 21 - }; 22 - 23 - const uint8array64 = (arru8: Uint8Array) => { 24 - if ("toBase64" in arru8) return arru8.toBase64(); 25 - 26 - function _arrayBufferToBase64(bytes: Uint8Array) { 27 - var binary = ""; 28 - var len = bytes.byteLength; 29 - for (var i = 0; i < len; i++) { 30 - binary += String.fromCharCode(bytes[i]); 31 - } 32 - return btoa(binary); 33 - } 34 - return _arrayBufferToBase64(arru8); 35 - }; 36 - 37 9 class CreateDong extends HTMLElement { 38 10 connectedCallback() { 39 11 // create input ··· 41 13 42 14 const imageLabel = document.createElement("label"); 43 15 const audioLabel = document.createElement("label"); 16 + 17 + const imageFileName = document.createElement("span"); 18 + const audioFileName = document.createElement("span"); 19 + 44 20 const imageSelect = document.createElement("input"); 45 21 const audioSelect = document.createElement("input"); 46 22 47 23 const filename = document.createElement("input"); 24 + const fileLabel = document.createElement("label"); 48 25 const createButton = document.createElement("button"); 49 26 50 27 const errormsg = document.createElement("div"); ··· 62 39 audioLabel.innerText = "Audio"; 63 40 audioLabel.htmlFor = audioSelect.id; 64 41 42 + filename.id = "filename"; 43 + 44 + fileLabel.innerText = "Name: "; 45 + fileLabel.htmlFor = filename.id; 46 + 65 47 createButton.type = "submit"; 66 48 createButton.textContent = "Create"; 67 49 68 50 imageLabel.appendChild(imageSelect); 69 51 audioLabel.appendChild(audioSelect); 70 52 53 + imageLabel.appendChild(imageFileName); 54 + audioLabel.appendChild(audioFileName); 55 + fileLabel.appendChild(filename); 56 + 71 57 form.appendChild(imageLabel); 72 58 form.appendChild(audioLabel); 73 - form.appendChild(filename); 59 + form.appendChild(fileLabel); 74 60 form.appendChild(createButton); 75 61 form.appendChild(errormsg); 76 62 ··· 80 66 createButton.addEventListener("click", async (e) => { 81 67 // don't refresh 82 68 e.preventDefault(); 69 + errormsg.innerText = ""; 70 + 83 71 // quit early if no files 84 72 if ( 85 73 !imageSelect.files || ··· 91 79 return; 92 80 } 93 81 82 + if (filename.value === "") { 83 + errormsg.innerText = "Filename cannot be empty"; 84 + return; 85 + } 86 + 94 87 // get files 95 88 const image = imageSelect.files[0]; 96 89 const audio = audioSelect.files[0]; ··· 102 95 return; 103 96 } 104 97 105 - const dongFile = new File([res], `${filename.value}.dong`, { 106 - type: "application/prs.vielle.dong", 107 - }); 98 + const dongFile = new File( 99 + [res], 100 + `${filename.value === "" ? "dong" : filename.value}.dong`, 101 + { 102 + type: "application/prs.vielle.dong", 103 + } 104 + ); 108 105 109 106 // download the dong file 110 107 download(dongFile); 111 108 }); 109 + 110 + imageSelect.addEventListener("change", (e) => { 111 + if (!imageSelect.files || imageSelect.files.length === 0) return; 112 + imageFileName.innerText = imageSelect.files[0].name; 113 + }); 114 + 115 + audioSelect.addEventListener("change", (e) => { 116 + if (!audioSelect.files || audioSelect.files.length === 0) return; 117 + audioFileName.innerText = audioSelect.files[0].name; 118 + }); 112 119 } 113 120 } 114 121 115 122 class LoadDong extends HTMLElement { 123 + // do not append as this is only for playing audio 124 + // loaded here to prevent overlaying the sound 125 + image = document.createElement("img"); 126 + audio = new Audio(); 127 + 116 128 connectedCallback() { 117 129 // create input 118 130 const form = document.createElement("form"); 131 + const dongLabel = document.createElement("label"); 119 132 const dongSelect = document.createElement("input"); 120 - const loadButton = document.createElement("button"); 121 133 // image 122 134 const errormsg = document.createElement("div"); 123 - const image = document.createElement("img"); 124 - 125 - // do not append as this is only for playing audio 126 - // loaded here to prevent overlaying the sound 127 - const audio = document.createElement("audio"); 128 135 129 136 dongSelect.type = "file"; 130 137 dongSelect.accept = ".dong"; 131 - loadButton.type = "submit"; 132 - loadButton.textContent = "Load"; 133 - image.width = 256; 134 - image.height = 256; 138 + dongSelect.id = "dong-select"; 139 + 140 + dongLabel.innerText = "Upload .dong"; 141 + dongLabel.htmlFor = dongSelect.id; 142 + 143 + dongLabel.appendChild(dongSelect); 135 144 136 - form.appendChild(dongSelect); 137 - form.appendChild(loadButton); 145 + form.appendChild(dongLabel); 138 146 139 147 this.appendChild(form); 140 - this.appendChild(image); 148 + this.appendChild(this.image); 141 149 this.appendChild(errormsg); 142 150 143 151 // functionality 144 - loadButton.addEventListener("click", async (e) => { 152 + dongSelect.addEventListener("change", async (e) => { 145 153 // don't refresh 146 154 e.preventDefault(); 155 + errormsg.innerText = ""; 156 + 147 157 // quit early if no files 148 158 if (!dongSelect.files || dongSelect.files.length === 0) 149 159 return (errormsg.innerText = "No files selected"); ··· 151 161 // get files 152 162 const dongFile = dongSelect.files[0]; 153 163 154 - const res = await readDong(dongFile, { b64: true }); 164 + const res = await readDong(dongFile); 155 165 if (typeof res === "string") { 156 166 errormsg.innerText = res; 157 167 return; 158 168 } 159 169 160 - image.src = `data:${res.image.mime};base64,${res.image.data}`; 170 + this.image.src = URL.createObjectURL(new Blob([res.image.data])); 161 171 162 172 // audio play 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) 167 - audio.play(); 173 + this.audio.src = URL.createObjectURL(new Blob([res.audio.data])); 174 + this.audio.play(); 168 175 }); 169 176 } 170 177 } 171 178 172 179 customElements.define("create-dong", CreateDong); 173 180 customElements.define("load-dong", LoadDong); 181 + 182 + async function handleFile(file: File) { 183 + const load_dong = document.getElementById("load") as LoadDong; 184 + const res = await readDong(file); 185 + if (typeof res === "string") return; 186 + load_dong.image.src = URL.createObjectURL(new Blob([res.image.data])); 187 + load_dong.audio.src = URL.createObjectURL(new Blob([res.audio.data])); 188 + load_dong.audio.play(); 189 + } 190 + 191 + if ("launchQueue" in window && "LaunchParams" in window) { 192 + (window as any).launchQueue.setConsumer( 193 + async (launchParams: { files: any[] }) => { 194 + if (!launchParams.files.length) { 195 + return; 196 + } 197 + const fileHandle = launchParams.files[0]; 198 + const blob: Blob = await fileHandle.getFile(); 199 + handleFile(new File([blob], "", { type: blob.type })); 200 + } 201 + ); 202 + } 174 203 </script> 175 204 176 205 <style slot="head" is:inline> 206 + :root { 207 + --accent: #f80085; 208 + } 209 + * { 210 + color: white; 211 + } 212 + 177 213 button { 178 - background-color: #f80085; 214 + background-color: var(--accent); 215 + &:empty { 216 + display: none; 217 + } 218 + } 219 + 220 + body { 221 + display: flex; 222 + flex-direction: column; 223 + align-items: center; 224 + justify-content: start; 225 + gap: 1rem; 226 + min-height: 100vh; 227 + background-color: hsl(270, 50%, 10%); 228 + max-width: 60ch; 229 + margin: auto; 230 + padding: 1rem; 231 + margin-block-end: 5rem; 179 232 } 180 233 181 - label { 182 - background-color: #f80085; 234 + label:has(input[type="file"]), 235 + button { 236 + background-color: var(--accent); 237 + border: none; 238 + padding: 0.5rem; 239 + padding-inline: 1rem; 183 240 184 - & > input[type="file"] { 241 + display: flex; 242 + flex-direction: column; 243 + align-items: center; 244 + justify-content: center; 245 + 246 + &:is(label) > input[type="file"] { 185 247 display: none; 186 248 } 187 249 } 250 + 251 + :is(div, span):empty { 252 + display: none; 253 + } 254 + 255 + img:not([src]) { 256 + display: none; 257 + } 258 + 259 + img { 260 + max-width: 100%; 261 + } 262 + 263 + .logo, 264 + .logo img { 265 + width: 100%; 266 + } 267 + hr { 268 + width: 100%; 269 + } 270 + 271 + label:has(input:not([type])) { 272 + display: flex; 273 + flex-direction: row; 274 + flex-wrap: nowrap; 275 + align-items: center; 276 + gap: 0.5rem; 277 + } 278 + 279 + input:not([type]) { 280 + border: 0.2rem solid white; 281 + background-color: transparent; 282 + width: 100%; 283 + } 284 + 285 + label:has(input[type="file"]):has(span) { 286 + height: 3lh; 287 + gap: 0.5rem; 288 + } 289 + 290 + create-dong form { 291 + display: grid; 292 + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); 293 + gap: 1rem; 294 + } 295 + 296 + load-dong { 297 + display: flex; 298 + flex-direction: column; 299 + gap: 1rem; 300 + } 301 + 302 + create-dong span { 303 + width: 100%; 304 + height: 1lh; 305 + overflow: hidden; 306 + white-space: nowrap; 307 + text-overflow: ellipsis; 308 + } 188 309 </style> 189 310 190 311 <!-- logo --> 312 + <div class="logo"> 313 + <img src="/logo.svg" alt="logo" /> 314 + </div> 315 + 316 + <hr /> 191 317 192 318 <!-- creation --> 193 - <create-dong></create-dong> 319 + <create-dong id="create"></create-dong> 194 320 195 321 <hr /> 196 322 197 323 <!-- loading --> 198 - <load-dong></load-dong> 324 + <load-dong id="load"></load-dong> 199 325 </Base>
+23 -7
src/pages/manifest.json.ts
··· 16 16 JSON.stringify({ 17 17 name: "Dong Web", 18 18 short_name: "Dong", 19 - // icons generated by https://www.pwabuilder.com/imageGenerator from "logo.svg" 20 19 icons: [ 21 20 { 22 21 src: "windows11/SmallTile.scale-100.png", ··· 470 469 start_url: "/", 471 470 display: "standalone", 472 471 prefer_related_applications: false, 473 - }), 474 - { 475 - headers: { 476 - "Content-Type": "application/json", 477 - }, 478 - } 472 + file_handlers: [ 473 + { 474 + action: "/", 475 + accept: { 476 + "application/prs.vielle.dong": [".dong"], 477 + }, 478 + }, 479 + ], 480 + theme_color: "#f80085", 481 + background_color: "#1a0d26", 482 + id: "dev.vielle.dong", 483 + description: "A PWA to read and create .dong files", 484 + dir: "ltr", 485 + lang: "en", 486 + orientation: "portrait-primary", 487 + display_override: [ 488 + "standalone", 489 + "minimal-ui", 490 + "browser", 491 + "window-controls-overlay", 492 + ], 493 + categories: ["games", "music", "photo", "utilities"], 494 + }) 479 495 ); 480 496 };