js-dos 7.4.9 → 8.1.0

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 (155) hide show
  1. package/.github/workflows/build.yml +51 -0
  2. package/.gitmodules +4 -0
  3. package/README.deployment.md +11 -0
  4. package/README.md +29 -89
  5. package/index.html +106 -0
  6. package/package.json +46 -71
  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 +94 -0
  14. package/src/components/dos-option-select.tsx +108 -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 +156 -0
  23. package/src/frame/editor/defaults.ts +419 -0
  24. package/src/frame/editor/editor-conf-frame.tsx +226 -0
  25. package/src/frame/editor/editor-frame.css +55 -0
  26. package/src/frame/editor/editor-fs-frame.tsx +314 -0
  27. package/src/frame/fat-drives-frame.tsx +135 -0
  28. package/src/frame/frame.css +119 -0
  29. package/src/frame/frame.tsx +34 -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 +96 -0
  33. package/src/frame/settings-frame.tsx +14 -0
  34. package/src/frame/stats-frame.tsx +87 -0
  35. package/src/host/bundle-storage.ts +91 -0
  36. package/src/host/fullscreen.ts +35 -0
  37. package/src/host/lcache.ts +203 -0
  38. package/src/host/lstorage.ts +156 -0
  39. package/src/i18n.ts +179 -0
  40. package/src/index.css +7 -0
  41. package/src/load.ts +132 -0
  42. package/src/login/login.css +9 -0
  43. package/src/login/login.tsx +77 -0
  44. package/src/main.tsx +197 -0
  45. package/src/public/types.ts +41 -0
  46. package/src/sidebar/account-button.tsx +33 -0
  47. package/src/sidebar/cloud-save-button.tsx +89 -0
  48. package/src/sidebar/diskette-icon.tsx +14 -0
  49. package/src/sidebar/fullscreen-button.tsx +44 -0
  50. package/src/sidebar/network-button.tsx +39 -0
  51. package/src/sidebar/sidebar-button.tsx +244 -0
  52. package/src/sidebar/sidebar.css +47 -0
  53. package/src/sidebar/sidebar.tsx +30 -0
  54. package/src/sockdrive/LICENSE +674 -0
  55. package/src/sockdrive/README.md +45 -0
  56. package/src/sockdrive/include/sockdrive.h +23 -0
  57. package/src/sockdrive/js/.eslintrc.json +43 -0
  58. package/src/sockdrive/js/package.json +29 -0
  59. package/src/sockdrive/js/src/fatfs/chains.js +318 -0
  60. package/src/sockdrive/js/src/fatfs/dir.js +385 -0
  61. package/src/sockdrive/js/src/fatfs/helpers.js +210 -0
  62. package/src/sockdrive/js/src/fatfs/index.js +606 -0
  63. package/src/sockdrive/js/src/fatfs/structs.js +255 -0
  64. package/src/sockdrive/js/src/fatfs/vol.js +166 -0
  65. package/src/sockdrive/js/src/sockdrive/cache.ts +133 -0
  66. package/src/sockdrive/js/src/sockdrive/drive.ts +457 -0
  67. package/src/sockdrive/js/src/sockdrive/lru.ts +221 -0
  68. package/src/sockdrive/js/src/sockdrive/types.ts +14 -0
  69. package/src/sockdrive/js/src/sockdrive-fat.d.ts +64 -0
  70. package/src/sockdrive/js/src/sockdrive-fat.ts +226 -0
  71. package/src/sockdrive/js/src/sockdrive-native.ts +100 -0
  72. package/src/sockdrive/js/src/test/test.ts +288 -0
  73. package/src/sockdrive/js/tsconfig.json +11 -0
  74. package/src/sockdrive/js/webpack.config.js +68 -0
  75. package/src/sockdrive/js/yarn.lock +1808 -0
  76. package/src/store/auth.ts +115 -0
  77. package/src/store/dos.ts +305 -0
  78. package/src/store/editor.ts +61 -0
  79. package/src/store/init.ts +48 -0
  80. package/src/store/storage.ts +32 -0
  81. package/src/store/ui.ts +186 -0
  82. package/src/store.tsx +108 -0
  83. package/src/ui.tsx +104 -0
  84. package/src/v8/changes.ts +56 -0
  85. package/src/v8/config.ts +18 -0
  86. package/src/v8/subscriptions.ts +62 -0
  87. package/src/vite-env.d.ts +2 -0
  88. package/src/window/dos/controls/keyboard.ts +32 -0
  89. package/src/window/dos/controls/keys.ts +331 -0
  90. package/src/window/dos/controls/mouse/mount.ts +99 -0
  91. package/src/window/dos/controls/mouse/mouse-locked.ts +51 -0
  92. package/src/window/dos/controls/mouse/mouse-not-locked.ts +84 -0
  93. package/src/window/dos/controls/mouse/mouse-swipe.ts +57 -0
  94. package/src/window/dos/controls/mouse/pointer.ts +85 -0
  95. package/src/window/dos/controls/mouse.ts +21 -0
  96. package/src/window/dos/dos-dhry2.tsx +191 -0
  97. package/src/window/dos/dos-runtime.tsx +214 -0
  98. package/src/window/dos/dos-window.tsx +84 -0
  99. package/src/window/dos/render/canvas.ts +65 -0
  100. package/src/window/dos/render/resize.ts +31 -0
  101. package/src/window/dos/render/webgl.ts +173 -0
  102. package/src/window/dos/sound/audio-node.ts +148 -0
  103. package/src/window/error-window.tsx +9 -0
  104. package/src/window/loading-window.tsx +54 -0
  105. package/src/window/prerun-window.tsx +123 -0
  106. package/src/window/select-window.tsx +118 -0
  107. package/src/window/window.css +78 -0
  108. package/src/window/window.tsx +49 -0
  109. package/src/ws/ws-sockdrive.ts +101 -0
  110. package/src/ws/ws-transport-layer.ts +455 -0
  111. package/tailwind.config.cjs +48 -0
  112. package/tsconfig.json +32 -0
  113. package/tsconfig.node.json +9 -0
  114. package/vite.config.ts +22 -0
  115. package/LICENSE +0 -339
  116. package/dist/emulators-ui-loader.png +0 -0
  117. package/dist/js-dos.css +0 -1690
  118. package/dist/js-dos.js +0 -17
  119. package/dist/js-dos.js.map +0 -1
  120. package/dist/types/src/backend/v7/latency.d.ts +0 -8
  121. package/dist/types/src/backend/v7/personal.d.ts +0 -2
  122. package/dist/types/src/backend/v7/v7-config.d.ts +0 -16
  123. package/dist/types/src/components/action-bar.d.ts +0 -2
  124. package/dist/types/src/components/action-hide.d.ts +0 -4
  125. package/dist/types/src/components/action-save-or-exit.d.ts +0 -4
  126. package/dist/types/src/components/client.d.ts +0 -6
  127. package/dist/types/src/components/controls.d.ts +0 -8
  128. package/dist/types/src/components/cpu-control.d.ts +0 -2
  129. package/dist/types/src/components/horizontal-slider.d.ts +0 -13
  130. package/dist/types/src/components/region.d.ts +0 -6
  131. package/dist/types/src/components/scale-control.d.ts +0 -3
  132. package/dist/types/src/components/sensitivity-control.d.ts +0 -3
  133. package/dist/types/src/components/sidebar/latency-info.d.ts +0 -7
  134. package/dist/types/src/components/sidebar/main.d.ts +0 -2
  135. package/dist/types/src/components/sidebar/networking.d.ts +0 -2
  136. package/dist/types/src/components/sidebar/token/token-add-time.d.ts +0 -2
  137. package/dist/types/src/components/sidebar/token/token-select.d.ts +0 -2
  138. package/dist/types/src/components/sidebar/token/token.d.ts +0 -17
  139. package/dist/types/src/components/sidebar.d.ts +0 -2
  140. package/dist/types/src/components/sync-control.d.ts +0 -2
  141. package/dist/types/src/components/tip.d.ts +0 -2
  142. package/dist/types/src/components/vertical-slider.d.ts +0 -12
  143. package/dist/types/src/components/volume-control.d.ts +0 -3
  144. package/dist/types/src/dom.d.ts +0 -8
  145. package/dist/types/src/hardware-transport-layer.d.ts +0 -18
  146. package/dist/types/src/icons.d.ts +0 -95
  147. package/dist/types/src/player-app.d.ts +0 -50
  148. package/dist/types/src/player.d.ts +0 -29
  149. package/dist/types/src/request.d.ts +0 -1
  150. package/dist/types/src/xhr.d.ts +0 -6
  151. package/dist/types/v7-services/src/personal.d.ts +0 -3
  152. package/dist/wdosbox.js +0 -55
  153. package/dist/wdosbox.js.symbols +0 -3174
  154. package/dist/wdosbox.wasm +0 -0
  155. package/examples/digger/index.html +0 -71
@@ -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,123 @@
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, useNonSerializableStore } from "../store";
5
+ import { HardwareCheckbox, MouseCapture, SockdriveWrite, WorkerCheckbox } from "../components/dos-option-checkbox";
6
+ import { MouseSensitiviySlider, VolumeSlider } from "../components/dos-option-slider";
7
+ import { useT } from "../i18n";
8
+ import { dispatchLoginAction } from "../store/ui";
9
+ import { Emulators } from "emulators";
10
+
11
+ declare const emulators: Emulators;
12
+
13
+ export function PreRunWindow() {
14
+ const account = useSelector((state: State) => state.auth.account);
15
+ const emuVersion = useSelector((state: State) => state.dos.emuVersion);
16
+ const lang = useSelector((state: State) => state.i18n.lang);
17
+ const dispatch = useDispatch();
18
+ const t = useT();
19
+
20
+ function login() {
21
+ dispatchLoginAction(account, dispatch);
22
+ }
23
+
24
+ function openPremiumPage() {
25
+ window.open(lang === "ru" ?
26
+ "https://dos.zone/ru/blog/premium-subscription/" :
27
+ "https://dos.zone/blog/premium-subscription/", "_blank");
28
+ }
29
+
30
+ return <div class="pre-run-window">
31
+ <Play class="mb-8" />
32
+
33
+ {(account === null) &&
34
+ <div class="flex flex-row mb-4 -mt-4 items-center">
35
+ <div class="btn btn-accent" onClick={login}>
36
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
37
+ <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25
38
+ 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786
39
+ 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" />
40
+ </svg>
41
+
42
+ {t("please_login")}
43
+ </div>
44
+ <div class="ml-2 cursor-pointer" onClick={openPremiumPage}>
45
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
46
+ stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
47
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025
48
+ 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
49
+ 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
50
+ </svg>
51
+ </div>
52
+ </div>
53
+ }
54
+ {account &&
55
+ <div class="flex flex-row mb-4 -mt-4 items-center">
56
+ <div class="btn btn-ghost" onClick={login}>
57
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
58
+ <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25
59
+ 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786
60
+ 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" />
61
+ </svg>
62
+
63
+ {account.name ?? account.email}
64
+ </div>
65
+ </div>
66
+ }
67
+
68
+ <div class="flex flex-row flex-wrap justify-end">
69
+ <div class="flex flex-col items-end">
70
+ <BackendSelect />
71
+ <RenderSelect />
72
+ <SockdriveWrite />
73
+ </div>
74
+ <div class="ml-10 flex flex-col items-end">
75
+ <HardwareCheckbox />
76
+ <WorkerCheckbox />
77
+ <RenderAspectSelect />
78
+ <MouseCapture />
79
+ </div>
80
+ </div>
81
+
82
+ <MouseSensitiviySlider class="mt-4 w-full max-w-sm" />
83
+ <VolumeSlider class="w-full max-w-sm" />
84
+
85
+ <div class="text-center mt-8">emu-version: <span class="text-ellipsis overflow-hidden">{emuVersion}</span></div>
86
+ </div>;
87
+ }
88
+
89
+ function Play(props: { class?: string }) {
90
+ const configChanged = useSelector((state: State) => state.editor.configChanged);
91
+ const bundleConfig = useSelector((state: State) => state.editor.bundleConfig);
92
+ const nonSerializableStore = useNonSerializableStore();
93
+ const dispatch = useDispatch();
94
+
95
+ async function onPlay() {
96
+ if (configChanged) {
97
+ const config = bundleConfig;
98
+ const bundle = nonSerializableStore.loadedBundle?.bundle;
99
+ if (bundle === null || config === null || !ArrayBuffer.isView(bundle)) {
100
+ throw new Error("Unexpected behaviour (internal state is broken), bundle is null");
101
+ }
102
+
103
+ try {
104
+ nonSerializableStore.loadedBundle!.bundle =
105
+ await emulators.bundleUpdateConfig(bundle, config);
106
+ dispatch(dosSlice.actions.bndPlay({}));
107
+ } catch (e) {
108
+ dispatch(dosSlice.actions.bndError((e as Error).message ?? "unexpected error"));
109
+ }
110
+ } else {
111
+ dispatch(dosSlice.actions.bndPlay({}));
112
+ }
113
+ }
114
+
115
+ return <div class={props.class + " cursor-pointer"} onClick={onPlay}>
116
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
117
+ stroke-width="1.5" stroke="currentColor" class="w-48 h-48 play-button">
118
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
119
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 010
120
+ .656l-5.603 3.113a.375.375 0 01-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112z" />
121
+ </svg>
122
+ </div>;
123
+ }
@@ -0,0 +1,118 @@
1
+ import { useDispatch, useStore } from "react-redux";
2
+ import { dosSlice } from "../store/dos";
3
+ import { useT } from "../i18n";
4
+ import { loadBundleFromFile, loadBundleFromUrl, loadEmptyBundle } from "../load";
5
+ import { useState } from "preact/hooks";
6
+ import { uiSlice } from "../store/ui";
7
+ import { Store } from "../store";
8
+
9
+ const fileInput = document.createElement("input");
10
+ fileInput.type = "file";
11
+
12
+ export function SelectWindow() {
13
+ const t = useT();
14
+ const store = useStore() as Store;
15
+ const [useUrl, setUseUrl] = useState<boolean>(false);
16
+
17
+ if (useUrl) {
18
+ return <div class="select-window overflow-hidden flex-grow flex flex-col items-center justify-center px-8 py-8">
19
+ <Load />
20
+ </div>;
21
+ }
22
+
23
+ async function createEmpty() {
24
+ try {
25
+ await loadEmptyBundle(store).catch(console.error);
26
+ } catch (e: any) {
27
+ store.dispatch(dosSlice.actions.bndError(e.message ?? "unexpected error"));
28
+ }
29
+ }
30
+
31
+ return <div class="select-window overflow-hidden flex-grow flex flex-col items-center justify-center px-8">
32
+ <div class="mb-4 text-center underline cursor-pointer hover:text-accent"
33
+ onClick={() => setUseUrl(true)}>{t("load_by_url")}</div>
34
+ <Upload />
35
+ <div class="mt-4 text-center">{t("upload_file")}</div>
36
+ <div class="mt-4 text-center underline cursor-pointer hover:text-accent"
37
+ onClick={createEmpty}>{t("create_empty")}</div>
38
+ </div>;
39
+ }
40
+
41
+ function Load() {
42
+ const t = useT();
43
+ const store = useStore();
44
+ const dispatch = useDispatch();
45
+ const [url, setUrl] = useState<string>("");
46
+ const [error, setError] = useState<string | null>(null);
47
+
48
+ async function loadBundle(url: string) {
49
+ dispatch(uiSlice.actions.frameNone());
50
+
51
+ let validUrl;
52
+ try {
53
+ validUrl = new URL(url);
54
+ } catch (e: any) {
55
+ setError(e.message);
56
+ return;
57
+ }
58
+
59
+ try {
60
+ await loadBundleFromUrl(validUrl.toString(), store);
61
+ } catch (e: any) {
62
+ dispatch(dosSlice.actions.bndError(e.message ?? "unexpected error"));
63
+ }
64
+ }
65
+
66
+ return <>
67
+ <div class="form-control w-3/4 mb-4">
68
+ <label class="label">
69
+ <span class="label-text">{t("enter_url")}:</span>
70
+ </label>
71
+ <input type="text"
72
+ class="input w-full input-sm input-bordered"
73
+ onChange={(e) => setUrl(e.currentTarget.value ?? "")}
74
+ value={url}></input>
75
+ </div>
76
+ <div class="mt-4 text-center underline cursor-pointer hover:text-accent"
77
+ onClick={() => loadBundle(url)}>{t("load")}</div>
78
+ {error !== null &&
79
+ <div class="mt-8 text-center text-error">
80
+ {error}
81
+ </div>
82
+ }
83
+ </>;
84
+ }
85
+
86
+ function Upload() {
87
+ const store = useStore() as Store;
88
+
89
+ async function onFileChange() {
90
+ fileInput.removeEventListener("change", onFileChange);
91
+
92
+ if (fileInput.files === null || fileInput.files.length === 0) {
93
+ return;
94
+ }
95
+
96
+ const file = fileInput.files[0];
97
+ try {
98
+ await loadBundleFromFile(file, store).catch((e) => store.dispatch(dosSlice.actions.bndError(e.message)));
99
+ } catch (e: any) {
100
+ store.dispatch(dosSlice.actions.bndError(e.message ?? "unexpected error"));
101
+ }
102
+ }
103
+
104
+ function onUpload() {
105
+ fileInput.addEventListener("change", onFileChange);
106
+ fileInput.click();
107
+ }
108
+
109
+ return <div class="cursor-pointer" onClick={onUpload}>
110
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none"
111
+ viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-48 h-48 play-button">
112
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 13.5l3 3m0 0l3-3m-3
113
+ 3v-6m1.06-4.19l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25
114
+ 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5
115
+ 1.5 0 01-1.06-.44z" />
116
+ </svg>
117
+ </div>;
118
+ }