+3
-2
astro.config.mjs
+3
-2
astro.config.mjs
+107
-7
pnpm-lock.yaml
+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
+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
+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
+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
+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
+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
};