js-dos 0.80.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.eslintrc.json +43 -0
  2. package/.github/workflows/build.yml +43 -0
  3. package/.gitmodules +4 -0
  4. package/README.md +24 -0
  5. package/index.html +106 -0
  6. package/package.json +48 -0
  7. package/postcss.config.cjs +8 -0
  8. package/src/app.css +14 -0
  9. package/src/base.css +10 -0
  10. package/src/components/checkbox.tsx +41 -0
  11. package/src/components/close-button.tsx +17 -0
  12. package/src/components/components.css +28 -0
  13. package/src/components/dos-option-checkbox.tsx +72 -0
  14. package/src/components/dos-option-select.tsx +94 -0
  15. package/src/components/dos-option-slider.tsx +35 -0
  16. package/src/components/error.tsx +16 -0
  17. package/src/components/loading.tsx +26 -0
  18. package/src/components/lock.tsx +10 -0
  19. package/src/components/select.tsx +33 -0
  20. package/src/components/slider.tsx +69 -0
  21. package/src/download-file.ts +18 -0
  22. package/src/frame/account-frame.tsx +155 -0
  23. package/src/frame/editor/defaults.ts +344 -0
  24. package/src/frame/editor/editor-conf-frame.tsx +42 -0
  25. package/src/frame/editor/editor-frame.css +55 -0
  26. package/src/frame/editor/editor-fs-frame.tsx +315 -0
  27. package/src/frame/frame.css +127 -0
  28. package/src/frame/frame.tsx +34 -0
  29. package/src/frame/host-cache-frame.tsx +124 -0
  30. package/src/frame/image-rendering-frame.tsx +8 -0
  31. package/src/frame/network-frame.tsx +157 -0
  32. package/src/frame/quick-save-frame.tsx +95 -0
  33. package/src/frame/settings-frame.tsx +14 -0
  34. package/src/frame/stats-frame.tsx +82 -0
  35. package/src/host/bundle-storage.ts +91 -0
  36. package/src/host/fullscreen.ts +36 -0
  37. package/src/host/lcache.ts +203 -0
  38. package/src/host/lstorage.ts +156 -0
  39. package/src/i18n.ts +167 -0
  40. package/src/index.css +7 -0
  41. package/src/load.ts +139 -0
  42. package/src/login/login.css +9 -0
  43. package/src/login/login.tsx +77 -0
  44. package/src/main.tsx +195 -0
  45. package/src/non-serializable-store.ts +35 -0
  46. package/src/public/types.ts +39 -0
  47. package/src/sidebar/account-button.tsx +32 -0
  48. package/src/sidebar/cloud-save-button.tsx +89 -0
  49. package/src/sidebar/diskette-icon.tsx +14 -0
  50. package/src/sidebar/fullscreen-button.tsx +44 -0
  51. package/src/sidebar/network-button.tsx +39 -0
  52. package/src/sidebar/sidebar-button.tsx +243 -0
  53. package/src/sidebar/sidebar.css +47 -0
  54. package/src/sidebar/sidebar.tsx +30 -0
  55. package/src/sockdrive/LICENSE +674 -0
  56. package/src/sockdrive/README.md +22 -0
  57. package/src/sockdrive/SDL2/LRUCache11.hpp +240 -0
  58. package/src/sockdrive/SDL2/sockdrive.cpp +207 -0
  59. package/src/sockdrive/include/sockdrive.h +23 -0
  60. package/src/sockdrive/js/.eslintrc.json +43 -0
  61. package/src/sockdrive/js/README.md +70 -0
  62. package/src/sockdrive/js/package.json +29 -0
  63. package/src/sockdrive/js/src/fatfs/chains.js +318 -0
  64. package/src/sockdrive/js/src/fatfs/dir.js +385 -0
  65. package/src/sockdrive/js/src/fatfs/helpers.js +210 -0
  66. package/src/sockdrive/js/src/fatfs/index.js +606 -0
  67. package/src/sockdrive/js/src/fatfs/structs.js +255 -0
  68. package/src/sockdrive/js/src/fatfs/vol.js +166 -0
  69. package/src/sockdrive/js/src/sockdrive/cache.ts +133 -0
  70. package/src/sockdrive/js/src/sockdrive/drive.ts +364 -0
  71. package/src/sockdrive/js/src/sockdrive/lru.ts +221 -0
  72. package/src/sockdrive/js/src/sockdrive/types.ts +10 -0
  73. package/src/sockdrive/js/src/sockdrive-fat.d.ts +64 -0
  74. package/src/sockdrive/js/src/sockdrive-fat.ts +228 -0
  75. package/src/sockdrive/js/src/sockdrive-native.ts +98 -0
  76. package/src/sockdrive/js/src/test/test.ts +186 -0
  77. package/src/sockdrive/js/tsconfig.json +11 -0
  78. package/src/sockdrive/js/webpack.config.js +68 -0
  79. package/src/sockdrive/js/yarn.lock +1808 -0
  80. package/src/sockdrive/lz4/lz4.c +2773 -0
  81. package/src/sockdrive/lz4/lz4.h +862 -0
  82. package/src/store/auth.ts +104 -0
  83. package/src/store/dos.ts +284 -0
  84. package/src/store/editor.ts +59 -0
  85. package/src/store/storage.ts +30 -0
  86. package/src/store/ui.ts +183 -0
  87. package/src/store.ts +27 -0
  88. package/src/ui.tsx +103 -0
  89. package/src/v8/changes.ts +56 -0
  90. package/src/v8/config.ts +18 -0
  91. package/src/v8/subscriptions.ts +62 -0
  92. package/src/vite-env.d.ts +2 -0
  93. package/src/window/dos/controls/keyboard.ts +32 -0
  94. package/src/window/dos/controls/keys.ts +331 -0
  95. package/src/window/dos/controls/mouse/mount.ts +99 -0
  96. package/src/window/dos/controls/mouse/mouse-locked.ts +51 -0
  97. package/src/window/dos/controls/mouse/mouse-not-locked.ts +84 -0
  98. package/src/window/dos/controls/mouse/mouse-swipe.ts +57 -0
  99. package/src/window/dos/controls/mouse/pointer.ts +85 -0
  100. package/src/window/dos/controls/mouse.ts +21 -0
  101. package/src/window/dos/dos-dhry2.tsx +191 -0
  102. package/src/window/dos/dos-runtime.tsx +185 -0
  103. package/src/window/dos/dos-window.tsx +82 -0
  104. package/src/window/dos/render/canvas.ts +65 -0
  105. package/src/window/dos/render/resize.ts +31 -0
  106. package/src/window/dos/render/webgl.ts +173 -0
  107. package/src/window/dos/sound/audio-node.ts +148 -0
  108. package/src/window/error-window.tsx +9 -0
  109. package/src/window/loading-window.tsx +54 -0
  110. package/src/window/prerun-window.tsx +98 -0
  111. package/src/window/select-window.tsx +118 -0
  112. package/src/window/window.css +78 -0
  113. package/src/window/window.tsx +49 -0
  114. package/src/ws/ws-sockdrive.ts +102 -0
  115. package/src/ws/ws-transport-layer.ts +457 -0
  116. package/tailwind.config.cjs +48 -0
  117. package/tsconfig.json +32 -0
  118. package/tsconfig.node.json +9 -0
  119. package/vite.config.ts +22 -0
@@ -0,0 +1,82 @@
1
+ import { Emulators, CommandInterface } from "emulators";
2
+ import { useEffect, useRef, useState } from "preact/hooks";
3
+ import { useDispatch, useSelector } from "react-redux";
4
+ import { dosSlice } from "../../store/dos";
5
+ import { nonSerializableStore, postJsDosEvent } from "../../non-serializable-store";
6
+ import { State } from "../../store";
7
+ import { useDosRuntime } from "./dos-runtime";
8
+ import { dhry2Bundle, Dhry2Results } from "./dos-dhry2";
9
+ import { createWsTransportLayer } from "../../ws/ws-transport-layer";
10
+
11
+ declare const emulators: Emulators;
12
+
13
+ export function DosWindow(props: {
14
+ }) {
15
+ const canvasRef = useRef<HTMLCanvasElement>(null);
16
+ const [ci, setCi] = useState<CommandInterface | null>(null);
17
+ const token = useSelector((state: State) => state.auth.account?.token.access_token);
18
+ const worker = useSelector((state: State) => state.dos.worker);
19
+ const backend = useSelector((state: State) => state.dos.backend);
20
+ const backendHardware = useSelector((state: State) => state.dos.backendHardware);
21
+ const dispatch = useDispatch();
22
+
23
+ useEffect(() => {
24
+ try {
25
+ const bundles = nonSerializableStore.loadedBundle!.bundleChanges !== null ?
26
+ [nonSerializableStore.loadedBundle!.bundle, nonSerializableStore.loadedBundle!.bundleChanges] :
27
+ nonSerializableStore.loadedBundle!.bundle;
28
+
29
+ nonSerializableStore.loadedBundle!.bundle = null;
30
+ nonSerializableStore.loadedBundle!.bundleChanges = null;
31
+
32
+ const ci: Promise<CommandInterface> = (async () => {
33
+ if (backendHardware && nonSerializableStore.options.backendHardware) {
34
+ const ws = await nonSerializableStore.options.backendHardware(backend);
35
+ if (ws !== null) {
36
+ return emulators.backend(bundles as any, await createWsTransportLayer(ws), { token });
37
+ }
38
+ }
39
+
40
+ return (emulators as any)[backend + (worker ? "Worker" : "Direct")](bundles, {
41
+ token,
42
+ });
43
+ })();
44
+
45
+ ci
46
+ .then((ci) => {
47
+ setCi(ci);
48
+ dispatch(dosSlice.actions.ci(true));
49
+ nonSerializableStore.ci = ci;
50
+ postJsDosEvent("ci-ready", ci);
51
+ (window as any).ci = ci;
52
+ })
53
+ .catch((e) => dispatch(dosSlice.actions.emuError(e.message)));
54
+
55
+ return () => {
56
+ ci.then((ci) => {
57
+ dispatch(dosSlice.actions.ci(false));
58
+ nonSerializableStore.ci = null;
59
+ ci.exit();
60
+ });
61
+ };
62
+ } catch (e) {
63
+ dispatch(dosSlice.actions.emuError((e as any).message));
64
+ }
65
+ }, [worker, backend, token ?? null]);
66
+
67
+ return <div class="bg-black h-full flex-grow overflow-hidden relative">
68
+ <canvas ref={canvasRef} />
69
+ {canvasRef.current && ci && <DosRuntime canvas={canvasRef.current} ci={ci} />}
70
+ </div>;
71
+ }
72
+
73
+ function DosRuntime(props: { canvas: HTMLCanvasElement, ci: CommandInterface }) {
74
+ const { canvas, ci } = props;
75
+ const bundle = useSelector((state: State) => state.dos.bundle);
76
+
77
+ useDosRuntime(canvas, ci);
78
+
79
+ return <>
80
+ {bundle?.endsWith(dhry2Bundle) && <Dhry2Results ci={ci} />}
81
+ </>;
82
+ }
@@ -0,0 +1,65 @@
1
+ import { CommandInterface } from "emulators";
2
+ import { resizeCanvas } from "./resize";
3
+
4
+ export function canvas(canvas: HTMLCanvasElement,
5
+ ci: CommandInterface,
6
+ forceAspect?: number) {
7
+ const context = canvas.getContext("2d");
8
+ if (context === null) {
9
+ throw new Error("Unable to create 2d context on given canvas");
10
+ }
11
+
12
+ let frameWidth = 0;
13
+ let frameHeight = 0;
14
+
15
+ const onResize = () => {
16
+ resizeCanvas(canvas, frameWidth, frameHeight, forceAspect);
17
+ };
18
+
19
+ let rgba = new Uint8ClampedArray(0);
20
+ const onResizeFrame = (w: number, h: number) => {
21
+ frameWidth = w;
22
+ frameHeight = h;
23
+ canvas.width = frameWidth;
24
+ canvas.height = frameHeight;
25
+ rgba = new Uint8ClampedArray(w * h * 4);
26
+ onResize();
27
+ };
28
+ ci.events().onFrameSize(onResizeFrame);
29
+ ci.events().onFrame((frameRgb, frameRgba) => {
30
+ if (frameRgb === null && frameRgba === null) {
31
+ return;
32
+ }
33
+
34
+ const frame = (frameRgb !== null ? frameRgb : frameRgba) as Uint8Array;
35
+
36
+ let frameOffset = 0;
37
+ let rgbaOffset = 0;
38
+
39
+ while (rgbaOffset < rgba.length) {
40
+ rgba[rgbaOffset++] = frame[frameOffset++];
41
+ rgba[rgbaOffset++] = frame[frameOffset++];
42
+ rgba[rgbaOffset++] = frame[frameOffset++];
43
+ rgba[rgbaOffset++] = 255;
44
+
45
+ if (frame.length === rgba.length) {
46
+ frameOffset++;
47
+ }
48
+ }
49
+
50
+ context.putImageData(new ImageData(rgba, frameWidth, frameHeight), 0, 0);
51
+ });
52
+
53
+ onResizeFrame(ci.width(), ci.height());
54
+
55
+ const resizeObserver = new ResizeObserver(onResize);
56
+ resizeObserver.observe(canvas.parentElement!);
57
+ window.addEventListener("resize", onResize);
58
+
59
+ return () => {
60
+ ci.events().onFrameSize(() => {});
61
+ ci.events().onFrame(() => {});
62
+ resizeObserver.disconnect();
63
+ window.removeEventListener("resize", onResize);
64
+ };
65
+ }
@@ -0,0 +1,31 @@
1
+ import { FitConstant } from "../../../store/dos";
2
+
3
+ export function resizeCanvas(canvas: HTMLCanvasElement,
4
+ frameWidth: number,
5
+ frameHeight: number,
6
+ forceAspect?: number) {
7
+ const rect = canvas.parentElement!.getBoundingClientRect();
8
+ const containerWidth = rect.width;
9
+ const containerHeight = rect.height;
10
+
11
+ if (frameHeight === 0) {
12
+ return;
13
+ }
14
+ const aspect =
15
+ forceAspect === FitConstant ? (containerWidth / containerHeight) :
16
+ (forceAspect ?? (frameWidth / frameHeight));
17
+
18
+ let width = containerWidth;
19
+ let height = containerWidth / aspect;
20
+
21
+ if (height > containerHeight) {
22
+ height = containerHeight;
23
+ width = containerHeight * aspect;
24
+ }
25
+
26
+ canvas.style.position = "relative";
27
+ canvas.style.top = (containerHeight - height) / 2 + "px";
28
+ canvas.style.left = (containerWidth - width) / 2 + "px";
29
+ canvas.style.width = width + "px";
30
+ canvas.style.height = height + "px";
31
+ };
@@ -0,0 +1,173 @@
1
+ import { CommandInterface } from "emulators";
2
+ import { resizeCanvas } from "./resize";
3
+
4
+ const vsSource = `
5
+ attribute vec4 aVertexPosition;
6
+ attribute vec2 aTextureCoord;
7
+
8
+ varying highp vec2 vTextureCoord;
9
+
10
+ void main(void) {
11
+ gl_Position = aVertexPosition;
12
+ vTextureCoord = aTextureCoord;
13
+ }
14
+ `;
15
+
16
+ const fsSource = `
17
+ varying highp vec2 vTextureCoord;
18
+ uniform sampler2D uSampler;
19
+
20
+
21
+ void main(void) {
22
+ highp vec4 color = texture2D(uSampler, vTextureCoord);
23
+ gl_FragColor = vec4(color.r, color.g, color.b, 1.0);
24
+ }
25
+ `;
26
+
27
+ export function webGl(canvas: HTMLCanvasElement,
28
+ ci: CommandInterface,
29
+ forceAspect?: number) {
30
+ const gl = canvas.getContext("webgl");
31
+ if (gl === null) {
32
+ throw new Error("Unable to create webgl context on given canvas");
33
+ }
34
+
35
+ const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
36
+ const vertexPosition = gl.getAttribLocation(shaderProgram, "aVertexPosition");
37
+ const textureCoord = gl.getAttribLocation(shaderProgram, "aTextureCoord");
38
+ const uSampler = gl.getUniformLocation(shaderProgram, "uSampler");
39
+
40
+ initBuffers(gl, vertexPosition, textureCoord);
41
+
42
+ const texture = gl.createTexture();
43
+ gl.bindTexture(gl.TEXTURE_2D, texture);
44
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
45
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
46
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
47
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
48
+
49
+ const pixel = new Uint8Array([0, 0, 0]);
50
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB,
51
+ 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE,
52
+ pixel);
53
+
54
+ gl.useProgram(shaderProgram);
55
+ gl.activeTexture(gl.TEXTURE0);
56
+ gl.uniform1i(uSampler, 0);
57
+
58
+ let frameWidth = 0;
59
+ let frameHeight = 0;
60
+
61
+ let requestAnimationFrameId: number | null = null;
62
+ let frame: Uint8Array | null = null;
63
+ let frameFormat: number = 0;
64
+
65
+ const updateTexture = () => {
66
+ if (frame !== null) {
67
+ gl.texImage2D(gl.TEXTURE_2D, 0, frameFormat,
68
+ frameWidth, frameHeight, 0, frameFormat, gl.UNSIGNED_BYTE,
69
+ frame);
70
+ frame = null;
71
+ }
72
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
73
+
74
+ requestAnimationFrameId = null;
75
+ };
76
+
77
+ const onResize = () => {
78
+ resizeCanvas(canvas, frameWidth, frameHeight, forceAspect);
79
+ };
80
+
81
+ const onResizeFrame = (w: number, h: number) => {
82
+ frameWidth = w;
83
+ frameHeight = h;
84
+ canvas.width = frameWidth;
85
+ canvas.height = frameHeight;
86
+ frame = null;
87
+ gl.viewport(0, 0, frameWidth, frameHeight);
88
+ onResize();
89
+ };
90
+
91
+ ci.events().onFrameSize(onResizeFrame);
92
+ ci.events().onFrame((rgb, rgba) => {
93
+ frame = rgb != null ? rgb : rgba;
94
+ frameFormat = rgb != null ? gl.RGB : gl.RGBA;
95
+ if (requestAnimationFrameId === null) {
96
+ requestAnimationFrameId = requestAnimationFrame(updateTexture);
97
+ }
98
+ });
99
+
100
+ onResizeFrame(ci.width(), ci.height());
101
+
102
+ const resizeObserver = new ResizeObserver(onResize);
103
+ resizeObserver.observe(canvas.parentElement!);
104
+ window.addEventListener("resize", onResize);
105
+
106
+ return () => {
107
+ ci.events().onFrameSize(() => {});
108
+ ci.events().onFrame(() => {});
109
+ resizeObserver.disconnect();
110
+ window.removeEventListener("resize", onResize);
111
+ };
112
+ }
113
+
114
+ function initShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource: string) {
115
+ const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
116
+ const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
117
+
118
+ const shaderProgram = gl.createProgram() as WebGLShader;
119
+ gl.attachShader(shaderProgram, vertexShader);
120
+ gl.attachShader(shaderProgram, fragmentShader);
121
+ gl.linkProgram(shaderProgram);
122
+
123
+ if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
124
+ throw new Error("Unable to initialize the shader program: " + gl.getProgramInfoLog(shaderProgram));
125
+ }
126
+
127
+ return shaderProgram;
128
+ }
129
+
130
+ function loadShader(gl: WebGLRenderingContext, shaderType: GLenum, source: string) {
131
+ const shader = gl.createShader(shaderType) as WebGLShader;
132
+ gl.shaderSource(shader, source);
133
+ gl.compileShader(shader);
134
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
135
+ const info = gl.getShaderInfoLog(shader);
136
+ gl.deleteShader(shader);
137
+ throw new Error("An error occurred compiling the shaders: " + info);
138
+ }
139
+
140
+ return shader;
141
+ }
142
+
143
+ function initBuffers(gl: WebGLRenderingContext, vertexPosition: number, textureCoord: number) {
144
+ const positionBuffer = gl.createBuffer();
145
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
146
+ const positions = [
147
+ -1.0, -1.0, 0.0,
148
+ 1.0, -1.0, 0.0,
149
+ 1.0, 1.0, 0.0,
150
+ -1.0, -1.0, 0.0,
151
+ 1.0, 1.0, 0.0,
152
+ -1.0, 1.0, 0.0,
153
+ ];
154
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
155
+ gl.vertexAttribPointer(vertexPosition, 3, gl.FLOAT, false, 0, 0);
156
+ gl.enableVertexAttribArray(vertexPosition);
157
+
158
+ const textureCoordBuffer = gl.createBuffer();
159
+ gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
160
+ const textureCoordinates = [
161
+ 0.0, 1.0,
162
+ 1.0, 1.0,
163
+ 1.0, 0.0,
164
+ 0.0, 1.0,
165
+ 1.0, 0.0,
166
+ 0.0, 0.0,
167
+ ];
168
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates),
169
+ gl.STATIC_DRAW);
170
+
171
+ gl.vertexAttribPointer(textureCoord, 2, gl.FLOAT, false, 0, 0);
172
+ gl.enableVertexAttribArray(textureCoord);
173
+ }
@@ -0,0 +1,148 @@
1
+ import { CommandInterface } from "emulators";
2
+
3
+ class SamplesQueue {
4
+ private samplesQueue: Float32Array[] = [];
5
+
6
+ push(samples: Float32Array) {
7
+ this.samplesQueue.push(samples);
8
+ }
9
+
10
+ length() {
11
+ let total = 0;
12
+ for (const next of this.samplesQueue) {
13
+ total += next.length;
14
+ }
15
+ return total;
16
+ }
17
+
18
+ writeTo(dst: Float32Array, bufferSize: number) {
19
+ let writeIt = 0;
20
+ while (this.samplesQueue.length > 0) {
21
+ const src = this.samplesQueue[0];
22
+ const toRead = Math.min(bufferSize - writeIt, src.length);
23
+ if (toRead === src.length) {
24
+ dst.set(src, writeIt);
25
+ this.samplesQueue.shift();
26
+ } else {
27
+ dst.set(src.slice(0, toRead), writeIt);
28
+ this.samplesQueue[0] = src.slice(toRead);
29
+ }
30
+
31
+ writeIt += toRead;
32
+
33
+ if (writeIt === bufferSize) {
34
+ break;
35
+ }
36
+ }
37
+
38
+ if (writeIt < bufferSize) {
39
+ dst.fill(0, writeIt);
40
+ }
41
+ }
42
+ }
43
+
44
+ export function audioNode(ci: CommandInterface,
45
+ bindVolumeFn?: (fn: (volume: number) => void) => () => void) {
46
+ const sampleRate = ci.soundFrequency();
47
+ const channels = 1;
48
+
49
+ if (sampleRate === 0) {
50
+ console.warn("Can't create audio node with sampleRate === 0, ingnoring");
51
+ return () => {};
52
+ }
53
+
54
+ let audioContext: AudioContext | null = null;
55
+
56
+ if (typeof AudioContext !== "undefined") {
57
+ audioContext = new AudioContext({
58
+ sampleRate,
59
+ latencyHint: "interactive",
60
+ });
61
+ } else if (typeof (window as any).webkitAudioContext !== "undefined") {
62
+ // eslint-disable-next-line new-cap
63
+ audioContext = new (window as any).webkitAudioContext({
64
+ sampleRate,
65
+ latencyHint: "interactive",
66
+ });
67
+ }
68
+
69
+ if (audioContext == null) {
70
+ return () => {};
71
+ }
72
+
73
+ const samplesQueue = new SamplesQueue();
74
+ const bufferSize = 2048;
75
+ const preBufferSize = 2048;
76
+
77
+ ci.events().onSoundPush((samples) => {
78
+ if (samplesQueue.length() < bufferSize * 2 + preBufferSize) {
79
+ samplesQueue.push(samples);
80
+ }
81
+ });
82
+
83
+ const audioNode = audioContext.createScriptProcessor(bufferSize, 0, channels);
84
+ let started = false;
85
+
86
+ const onQueueProcess = (event: AudioProcessingEvent) => {
87
+ const numFrames = event.outputBuffer.length;
88
+ const numChannels = event.outputBuffer.numberOfChannels;
89
+ const samplesCount = samplesQueue.length();
90
+
91
+ if (!started) {
92
+ started = samplesCount >= preBufferSize;
93
+ }
94
+
95
+ if (!started) {
96
+ return;
97
+ }
98
+
99
+ for (let channel = 0; channel < numChannels; channel++) {
100
+ const channelData = event.outputBuffer.getChannelData(channel);
101
+ samplesQueue.writeTo(channelData, numFrames);
102
+ }
103
+ };
104
+
105
+ audioNode.onaudioprocess = onQueueProcess;
106
+
107
+ const gainNode = audioContext.createGain();
108
+ gainNode.connect(audioContext.destination);
109
+ audioNode.connect(gainNode);
110
+
111
+ gainNode.gain.value = 1.0;
112
+
113
+ let unbindVolumeFn: () => void;
114
+ if (bindVolumeFn) {
115
+ unbindVolumeFn = bindVolumeFn((volume: number) => {
116
+ gainNode.gain.value = volume;
117
+ });
118
+ }
119
+
120
+ const resumeWebAudio = () => {
121
+ if (audioContext !== null && audioContext.state === "suspended") {
122
+ audioContext.resume();
123
+ }
124
+ };
125
+
126
+ document.addEventListener("pointerdown", resumeWebAudio, { once: true });
127
+ document.addEventListener("keydown", resumeWebAudio, { once: true });
128
+
129
+ return () => {
130
+ ci.events().onSoundPush(() => {});
131
+
132
+ if (audioContext !== null) {
133
+ audioNode.disconnect();
134
+ gainNode.disconnect();
135
+ audioContext
136
+ .close()
137
+ .catch(console.error);
138
+ audioContext = null;
139
+ }
140
+
141
+ if (unbindVolumeFn !== undefined) {
142
+ unbindVolumeFn();
143
+ }
144
+
145
+ document.removeEventListener("pointerdown", resumeWebAudio);
146
+ document.removeEventListener("keydown", resumeWebAudio);
147
+ };
148
+ }
@@ -0,0 +1,9 @@
1
+ import { useSelector } from "react-redux";
2
+ import { Error } from "../components/error";
3
+ import { State } from "../store";
4
+
5
+ export function ErrorWindow() {
6
+ const error = useSelector((state: State) => state.dos.error);
7
+ return <Error error={error} />;
8
+ }
9
+
@@ -0,0 +1,54 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { formatSize, Loading } from "../components/loading";
3
+ import { useT } from "../i18n";
4
+ import { State } from "../store";
5
+ import { authSlice } from "../store/auth";
6
+
7
+ export function LoadingWindow() {
8
+ const t = useT();
9
+ const step = useSelector((state: State) => state.dos.step);
10
+ const received = useSelector((state: State) => state.storage.recived);
11
+ const total = useSelector((state: State) => state.storage.total);
12
+ const ready = useSelector((state: State) => state.storage.ready);
13
+
14
+ let head = t("loading");
15
+ let message = "100%";
16
+
17
+ switch (step) {
18
+ case "bnd-load": {
19
+ head = t("bundle_loading");
20
+ if (received > 0) {
21
+ message = `${formatSize(received)} / ${formatSize(total)}`;
22
+
23
+ if (total > 0) {
24
+ message += ` (${Math.round(received * 1000 / total) / 10}%)`;
25
+ }
26
+ }
27
+ } break;
28
+ case "bnd-config": {
29
+ head = t("bundle_config");
30
+ }
31
+
32
+ default: {
33
+ }
34
+ }
35
+
36
+ return <div class="flex flex-col w-full h-full items-center justify-center">
37
+ <Loading head={head} message={message} />
38
+ {step === "bnd-load" && ready && <AccountNotReady />}
39
+ </div>;
40
+ }
41
+
42
+ function AccountNotReady() {
43
+ const t = useT();
44
+ const dispatch = useDispatch();
45
+ function skip() {
46
+ dispatch(authSlice.actions.ready());
47
+ }
48
+ return <>
49
+ <div class="mt-16 text-2xl">{t("loading_saves")}</div>
50
+ <div onClick={skip} class="self-center cursor-pointer underline mt-4 text-lg animate-pulse">
51
+ {t("account_not_ready")}
52
+ </div>
53
+ </>;
54
+ }
@@ -0,0 +1,98 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { BackendSelect, RenderAspectSelect, RenderSelect } from "../components/dos-option-select";
3
+ import { dosSlice } from "../store/dos";
4
+ import { State } from "../store";
5
+ import { HardwareCheckbox, MouseCapture, WorkerCheckbox } from "../components/dos-option-checkbox";
6
+ import { MouseSensitiviySlider, VolumeSlider } from "../components/dos-option-slider";
7
+ import { updateBundleConf } from "../load";
8
+ import { useT } from "../i18n";
9
+ import { dispatchLoginAction } from "../store/ui";
10
+
11
+ export function PreRunWindow() {
12
+ const account = useSelector((state: State) => state.auth.account);
13
+ const emuVersion = useSelector((state: State) => state.dos.emuVersion);
14
+ const lang = useSelector((state: State) => state.i18n.lang);
15
+ const dispatch = useDispatch();
16
+ const t = useT();
17
+
18
+ function login() {
19
+ dispatchLoginAction(account, dispatch);
20
+ }
21
+
22
+ function openPremiumPage() {
23
+ window.open(lang === "ru" ?
24
+ "https://dos.zone/ru/blog/premium-subscription/" :
25
+ "https://dos.zone/blog/premium-subscription/", "_blank");
26
+ }
27
+
28
+ return <div class="pre-run-window">
29
+ <Play class="mb-8" />
30
+
31
+ {(account === null || !account.premium) &&
32
+ <div class="flex flex-row mb-4 -mt-4 items-center">
33
+ <div class="btn btn-accent" onClick={login}>
34
+ <svg xmlns="http://www.w3.org/2000/svg"
35
+ fill="red"
36
+ viewBox="0 0 24 24" stroke-width="1.5"
37
+ stroke="currentColor"
38
+ class="stroke-current flex-shrink-0 h-6 w-6">
39
+ <path stroke-linecap="round" stroke-linejoin="round"
40
+ d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597
41
+ 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75
42
+ 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
43
+ </svg>
44
+ {t("not_premium")}
45
+ </div>
46
+ <div class="ml-2 cursor-pointer" onClick={openPremiumPage}>
47
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
48
+ stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
49
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025
50
+ 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45
51
+ 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
52
+ </svg>
53
+ </div>
54
+ </div>
55
+ }
56
+
57
+ <div class="flex flex-row flex-wrap justify-end">
58
+ <div class="flex flex-col items-end">
59
+ <BackendSelect />
60
+ <RenderSelect />
61
+ </div>
62
+ <div class="ml-10 flex flex-col items-end">
63
+ <HardwareCheckbox />
64
+ <WorkerCheckbox />
65
+ <RenderAspectSelect />
66
+ <MouseCapture />
67
+ </div>
68
+ </div>
69
+
70
+ <MouseSensitiviySlider class="mt-4 w-full max-w-sm" />
71
+ <VolumeSlider class="w-full max-w-sm" />
72
+
73
+ <div class="text-center mt-8">emu-version: <span class="text-ellipsis overflow-hidden">{emuVersion}</span></div>
74
+ </div>;
75
+ }
76
+
77
+ function Play(props: { class?: string }) {
78
+ const configChanged = useSelector((state: State) => state.editor.configChanged);
79
+ const dispatch = useDispatch();
80
+ function onPlay() {
81
+ if (configChanged) {
82
+ updateBundleConf()
83
+ .then(() => dispatch(dosSlice.actions.bndPlay()))
84
+ .catch((e) => dispatch(dosSlice.actions.bndError(e.message ?? "unexpected error")));
85
+ } else {
86
+ dispatch(dosSlice.actions.bndPlay());
87
+ }
88
+ }
89
+
90
+ return <div class={props.class + " cursor-pointer"} onClick={onPlay}>
91
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
92
+ stroke-width="1.5" stroke="currentColor" class="w-48 h-48 play-button">
93
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
94
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 010
95
+ .656l-5.603 3.113a.375.375 0 01-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112z" />
96
+ </svg>
97
+ </div>;
98
+ }