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,104 @@
1
+ import { createSlice } from "@reduxjs/toolkit";
2
+ import { nonSerializableStore } from "../non-serializable-store";
3
+ import { getCache } from "../host/lcache";
4
+ import { lStorage } from "../host/lstorage";
5
+
6
+ const cachedAccount = "cached.account";
7
+
8
+ export interface Token {
9
+ access_token: string,
10
+ refresh_token: string,
11
+ scope: string,
12
+ expires_in: number,
13
+ validUntilMs: number;
14
+ };
15
+
16
+ export interface Account {
17
+ email: string,
18
+ name: null | string,
19
+ picture: null | string,
20
+ token: Token,
21
+ premium: boolean,
22
+ };
23
+
24
+ const initAccount = (() => {
25
+ const params = new URLSearchParams(location.search);
26
+ const refreshToken = params.get("jsdos_token");
27
+ if (refreshToken !== null) {
28
+ setRefreshToken(refreshToken);
29
+ history.replaceState(null, "", location.href.substring(0, location.href.indexOf("?")));
30
+ return null;
31
+ } else {
32
+ const json = lStorage.getItem(cachedAccount);
33
+ const account = json !== null ? JSON.parse(json) : null;
34
+ return account !== null && account.token.validUntilMs - Date.now() > 1 * 60 * 60 * 1000 ?
35
+ account : null;
36
+ }
37
+ })();
38
+
39
+ const initialState: {
40
+ account: Account | null,
41
+ ready: boolean,
42
+ } = {
43
+ account: initAccount,
44
+ ready: initAccount !== null,
45
+ };
46
+
47
+ export const authSlice = createSlice({
48
+ name: "auth",
49
+ initialState,
50
+ reducers: {
51
+ login: (state, action: { payload: Account }) => {
52
+ setRefreshToken(action.payload.token.refresh_token);
53
+ state.account = action.payload;
54
+ state.account.premium = state.account.premium ||
55
+ state.account.email === "dz.caiiiycuk@gmail.com" ||
56
+ state.account.email === "caiiiycuk@gmail.com";
57
+ lStorage.setItem(cachedAccount, JSON.stringify(action.payload));
58
+ getCache(state.account.email)
59
+ .then((cache) => nonSerializableStore.cache = cache)
60
+ .catch(console.error)
61
+ .finally(() => {
62
+ nonSerializableStore.dispatch!(authSlice.actions.ready());
63
+ });
64
+ },
65
+ logout: (state) => {
66
+ clearRefreshToken();
67
+ lStorage.removeItem(cachedAccount);
68
+ getCache("guest")
69
+ .then((cache) => nonSerializableStore.cache = cache)
70
+ .catch(console.error);
71
+ state.account = null;
72
+ },
73
+ ready: (state) => {
74
+ state.ready = true;
75
+ },
76
+ },
77
+ });
78
+
79
+ export function postAuthMessage(action: "auth/login" | "auth/authenicate") {
80
+ const message = {
81
+ action,
82
+ refresh_token: action === "auth/authenicate" ? getRefreshToken() : undefined,
83
+ url: action === "auth/login" ? location.href : undefined,
84
+ };
85
+ document.querySelector<HTMLIFrameElement>("#authentificator")!
86
+ .contentWindow?.postMessage(message, "*");
87
+ }
88
+
89
+ function getRefreshToken(): string | null {
90
+ return lStorage.getItem("cached.rt");
91
+ }
92
+
93
+ function setRefreshToken(refreshToken: string | null) {
94
+ if (refreshToken === null) {
95
+ clearRefreshToken();
96
+ return;
97
+ }
98
+
99
+ return lStorage.setItem("cached.rt", refreshToken);
100
+ }
101
+
102
+ function clearRefreshToken() {
103
+ lStorage.removeItem("cached.rt");
104
+ }
@@ -0,0 +1,284 @@
1
+ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
2
+ import { makeStore, State } from "../store";
3
+ import { Emulators } from "emulators";
4
+ import { lStorage } from "../host/lstorage";
5
+ import { nonSerializableStore, postJsDosEvent } from "../non-serializable-store";
6
+
7
+ const alphabet = "qwertyuiopasdfghjklzxcvbnm1234567890";
8
+ declare const emulators: Emulators;
9
+ export interface BundleConfig {
10
+ name?: string,
11
+ version?: string,
12
+ backend?: string,
13
+ render?: string,
14
+ };
15
+
16
+ export const BackendValues = <const>["dosbox", "dosboxX"];
17
+ export type Backend = typeof BackendValues[number];
18
+
19
+ export const RenderBackendValues = <const>["webgl", "canvas"];
20
+ export type RenderBackend = typeof RenderBackendValues[number];
21
+
22
+ export const RenderAspectValues = <const>["AsIs", "1/1", "5/4", "4/3", "16/10", "16/9", "Fit"];
23
+ export type RenderAspect = typeof RenderAspectValues[number];
24
+ export const FitConstant = 65535;
25
+
26
+ export const ImageRenderingValues = <const>["pixelated", "smooth"];
27
+ export type ImageRendering = typeof ImageRenderingValues[number];
28
+
29
+ export interface EmulatorStats {
30
+ cyclesPerMs: number,
31
+ nonSkippableSleepPreSec: number,
32
+ sleepPerSec: number,
33
+ sleepTimePerSec: number,
34
+ framePerSec: number,
35
+ soundPerSec: number,
36
+ msgSentPerSec: number,
37
+ msgRecvPerSec: number,
38
+ netSent: number,
39
+ netRecv: number,
40
+ driveSent: number,
41
+ driveRecv: number,
42
+ driveRecvTime: number,
43
+ driveCacheHit: number,
44
+ driveCacheMiss: number,
45
+ driveCacheUsed: number,
46
+ };
47
+
48
+ const initialState: {
49
+ step:
50
+ "emu-init" | "emu-error" | "emu-ready" |
51
+ "bnd-load" | "bnd-error" | "bnd-config" | "bnd-ready" |
52
+ "bnd-play",
53
+ emuVersion: string,
54
+ worker: boolean,
55
+ backend: Backend,
56
+ backendLocked: boolean,
57
+ backendHardware: boolean,
58
+ renderBackend: RenderBackend,
59
+ renderAspect: RenderAspect,
60
+ volume: number,
61
+ mouseSensitivity: number,
62
+ mouseCapture: boolean,
63
+ paused: boolean,
64
+ error: null | undefined | string,
65
+ bundle: string | null,
66
+ config: BundleConfig,
67
+ stats: EmulatorStats,
68
+ ci: boolean,
69
+ network: {
70
+ server: "netherlands" | "newyork" | "singapore",
71
+ room: string,
72
+ ipx: "connecting" | "connected" | "disconnected" | "error",
73
+ },
74
+ imageRendering: ImageRendering,
75
+ } = {
76
+ step: "emu-init",
77
+ emuVersion: "-",
78
+ error: null,
79
+ bundle: null,
80
+ config: {},
81
+ worker: lStorage.getItem("worker") !== "false",
82
+ backend: (lStorage.getItem("backend") ?? "dosbox") as Backend,
83
+ backendLocked: false,
84
+ backendHardware: (lStorage.getItem("backendHardware") !== "false"),
85
+ renderBackend: (lStorage.getItem("renderBackend") ?? "webgl") as RenderBackend,
86
+ renderAspect: (lStorage.getItem("renderAspect") ?? "AsIs") as RenderAspect,
87
+ volume: (Number.parseFloat(lStorage.getItem("volume") ?? "1.0")),
88
+ mouseSensitivity: (Number.parseFloat(lStorage.getItem("mouse_sensitivity") ?? "1.0")),
89
+ mouseCapture: false,
90
+ paused: false,
91
+ stats: {
92
+ cyclesPerMs: 0,
93
+ nonSkippableSleepPreSec: 0,
94
+ sleepPerSec: 0,
95
+ sleepTimePerSec: 0,
96
+ framePerSec: 0,
97
+ soundPerSec: 0,
98
+ msgSentPerSec: 0,
99
+ msgRecvPerSec: 0,
100
+ netRecv: 0,
101
+ netSent: 0,
102
+ driveSent: 0,
103
+ driveRecv: 0,
104
+ driveRecvTime: 0,
105
+ driveCacheHit: 0,
106
+ driveCacheMiss: 0,
107
+ driveCacheUsed: 0,
108
+ },
109
+ network: {
110
+ server: (lStorage.getItem("net.server") ?? "netherlands") as any,
111
+ room: randomRoom(),
112
+ ipx: "disconnected",
113
+ },
114
+ ci: false,
115
+ imageRendering: (lStorage.getItem("imageRendering") ?? "pixelated") as any,
116
+ };
117
+
118
+ const connectIpx = createAsyncThunk("dos/connectIpx",
119
+ async (payload: { room: string, address: string }, thunkApi) => {
120
+ const dos = (thunkApi.getState() as State).dos;
121
+ if (dos.network.ipx === "connected") {
122
+ throw new Error("Already connected");
123
+ }
124
+ if (dos.ci === false || !nonSerializableStore.ci) {
125
+ throw new Error("DOS is not started");
126
+ }
127
+
128
+ const canonicalAddress = payload.address.endsWith("/") ?
129
+ payload.address.substring(0, payload.address.length - 1) :
130
+ payload.address;
131
+
132
+ return nonSerializableStore.ci.networkConnect(0 /* NetworkType.NETWORK_DOSBOX_IPX */,
133
+ canonicalAddress + ":1900/ipx/" + payload.room.replaceAll("@", "_"));
134
+ });
135
+
136
+ export const dosSlice = createSlice({
137
+ name: "dos",
138
+ initialState,
139
+ reducers: {
140
+ emuReady: (s, a: { payload: string }) => {
141
+ s.step = "emu-ready";
142
+ s.emuVersion = a.payload;
143
+ },
144
+ emuError: (s, a: { payload: string }) => {
145
+ s.step = "emu-error";
146
+ s.error = a.payload ?? "Unexpeceted error";
147
+ },
148
+ bndLoad: (s, a: { payload: string }) => {
149
+ s.step = "bnd-load";
150
+ s.bundle = a.payload;
151
+ },
152
+ bndError: (s, a: { payload: string }) => {
153
+ s.step = "bnd-error";
154
+ s.error = a.payload ?? "Unexpeceted error";
155
+ },
156
+ bndConfig: (s) => {
157
+ s.step = "bnd-config";
158
+ },
159
+ bndReady: (s, a: { payload: BundleConfig }) => {
160
+ s.step = "bnd-ready";
161
+ s.config = a.payload;
162
+ },
163
+ bndPlay: (s) => {
164
+ s.step = "bnd-play";
165
+ postJsDosEvent("bnd-play");
166
+ },
167
+ dosWorker: (s, a: { payload: boolean }) => {
168
+ s.worker = a.payload;
169
+ lStorage.setItem("worker", s.worker ? "true" : "false");
170
+ },
171
+ dosBackend: (s, a: { payload: Backend }) => {
172
+ s.backend = a.payload as Backend;
173
+ },
174
+ dosBackendLocked: (s, a: { payload: boolean }) => {
175
+ s.backendLocked = a.payload;
176
+ },
177
+ dosBackendHardware: (s, a: { payload: boolean }) => {
178
+ s.backendHardware = a.payload;
179
+ lStorage.setItem("backendHardware", s.backendHardware ? "true" : "false");
180
+ },
181
+ renderBackend: (s, a: { payload: RenderBackend }) => {
182
+ s.renderBackend = a.payload;
183
+ lStorage.setItem("renderBackend", s.renderBackend);
184
+ },
185
+ renderAspect: (s, a: { payload: RenderAspect }) => {
186
+ s.renderAspect = a.payload;
187
+ lStorage.setItem("renderAspect", s.renderAspect);
188
+ },
189
+ imageRendering: (s, a: { payload: ImageRendering }) => {
190
+ s.imageRendering = a.payload;
191
+ lStorage.setItem("imageRendering", s.imageRendering);
192
+ },
193
+ volume: (s, a: { payload: number }) => {
194
+ s.volume = a.payload;
195
+ lStorage.setItem("volume", s.volume + "");
196
+ },
197
+ mouseSensitivity: (s, a: { payload: number }) => {
198
+ s.mouseSensitivity = a.payload;
199
+ lStorage.setItem("mouse_sensitivity", s.mouseSensitivity + "");
200
+ },
201
+ mouseCapture: (s, a: { payload: boolean }) => {
202
+ s.mouseCapture = a.payload;
203
+ },
204
+ paused: (s, a: { payload: boolean }) => {
205
+ s.paused = a.payload;
206
+ },
207
+ stats: (s, a: { payload: EmulatorStats }) => {
208
+ s.stats = a.payload;
209
+ },
210
+ ci: (s, a: { payload: boolean }) => {
211
+ s.ci = a.payload;
212
+ },
213
+ disconnectIpx: (s) => {
214
+ s.network.ipx = "disconnected";
215
+ nonSerializableStore.ci?.networkDisconnect(0 /* IPX */);
216
+ },
217
+ setRoom: (s, a: { payload: string }) => {
218
+ s.network.room = a.payload;
219
+ },
220
+ setServer: (s, a: { payload: typeof initialState.network.server }) => {
221
+ s.network.server = a.payload;
222
+ lStorage.setItem("net.server", a.payload);
223
+ },
224
+ },
225
+ extraReducers: {
226
+ [connectIpx.fulfilled.type]: (state) => {
227
+ state.network.ipx = "connected";
228
+ },
229
+ [connectIpx.pending.type]: (state) => {
230
+ state.network.ipx = "connecting";
231
+ },
232
+ [connectIpx.rejected.type]: (state) => {
233
+ state.network.ipx = "error";
234
+ },
235
+ },
236
+ });
237
+
238
+ export const dosExtraActions = {
239
+ connectIpx,
240
+ };
241
+
242
+ export function initEmulators(store: ReturnType<typeof makeStore>, pathPrefix: string) {
243
+ store.dispatch(async (dispatch) => {
244
+ try {
245
+ await initEmulatorsJs(pathPrefix);
246
+ dispatch(dosSlice.actions.emuReady(emulators.version));
247
+ } catch (e) {
248
+ console.error("Unable to init emulators.js", e);
249
+ dispatch(dosSlice.actions.emuError((e as any).message));
250
+ }
251
+ });
252
+ }
253
+
254
+ function initEmulatorsJs(pathPrefix: string) {
255
+ const el = document.querySelector("#emulators.js");
256
+ if (el !== null) {
257
+ return Promise.resolve();
258
+ }
259
+
260
+ return new Promise<void>((resolve, reject) => {
261
+ const script = document.createElement("script");
262
+ script.async = true;
263
+ script.type = "text/javascript";
264
+ script.src = pathPrefix + "emulators.js";
265
+ script.onload = () => {
266
+ emulators.pathPrefix = pathPrefix;
267
+ resolve();
268
+ };
269
+ script.onerror = (err) => {
270
+ reject(new Error("Unable to add emulators.js. Probably you should set the " +
271
+ "'pathPrefix' option to point to the js-dos folder."));
272
+ };
273
+
274
+ document.head.appendChild(script);
275
+ });
276
+ };
277
+
278
+ function randomSymbol() {
279
+ return alphabet[Math.round(Math.random() * (alphabet.length - 1))];
280
+ }
281
+
282
+ function randomRoom() {
283
+ return randomSymbol() + randomSymbol() + randomSymbol();
284
+ }
@@ -0,0 +1,59 @@
1
+ import { createSlice } from "@reduxjs/toolkit";
2
+ import { DosConfig } from "emulators";
3
+ import { Node } from "react-checkbox-tree";
4
+ import { dosboxconf } from "../frame/editor/defaults";
5
+
6
+ const initialState: {
7
+ // step: "empty" | "extract" | "ready" | "error",
8
+ configChanged: boolean,
9
+ bundleConfig: DosConfig | null,
10
+ errorMessage: string | null,
11
+ fs: Node[],
12
+ } = {
13
+ // step: "empty",
14
+ configChanged: false,
15
+ bundleConfig: null,
16
+ errorMessage: null,
17
+ fs: [],
18
+ };
19
+
20
+ export const editorSlice = createSlice({
21
+ name: "editor",
22
+ initialState,
23
+ reducers: {
24
+ init: (s, a: { payload: DosConfig | null }) => {
25
+ if (a.payload === null) {
26
+ s.configChanged = true;
27
+ s.bundleConfig = {
28
+ dosboxConf: dosboxconf[0].contents,
29
+ jsdosConf: {
30
+ version: "js-dos-v8",
31
+ },
32
+ };
33
+ } else {
34
+ s.configChanged = false;
35
+ s.bundleConfig = a.payload;
36
+ }
37
+ },
38
+ dosboxConf: (s, a: { payload: string }) => {
39
+ s.configChanged = true;
40
+ s.bundleConfig!.dosboxConf = a.payload;
41
+ },
42
+ // empty: (s) => {
43
+ // s.step = "empty";
44
+ // },
45
+ // extract: (s) => {
46
+ // s.step = "extract";
47
+ // },
48
+ // ready: (s) => {
49
+ // s.step = "ready";
50
+ // },
51
+ // config: (s, a: { payload: DosConfig | null }) => {
52
+ // s.bundleConfig = a.payload;
53
+ // },
54
+ // error: (s, a: { payload: string }) => {
55
+ // s.step = "error";
56
+ // s.errorMessage = a.payload;
57
+ // },
58
+ },
59
+ });
@@ -0,0 +1,30 @@
1
+ import { createSlice } from "@reduxjs/toolkit";
2
+
3
+ const initialState: {
4
+ recived: number,
5
+ total: number,
6
+ ready: boolean,
7
+ } = {
8
+ recived: 0,
9
+ total: 0,
10
+ ready: false,
11
+ };
12
+
13
+ export const storageSlice = createSlice({
14
+ name: "storage",
15
+ initialState,
16
+ reducers: {
17
+ reset: (s) => {
18
+ s.recived = -1;
19
+ s.total = 0;
20
+ s.ready = false;
21
+ },
22
+ progress: (s, a: { payload: [number, number ] }) => {
23
+ s.recived = a.payload[0];
24
+ s.total = a.payload[1];
25
+ },
26
+ ready: (s) => {
27
+ s.ready = true;
28
+ },
29
+ },
30
+ });
@@ -0,0 +1,183 @@
1
+ import { Dispatch, createAction, createSlice } from "@reduxjs/toolkit";
2
+ import { lStorage } from "../host/lstorage";
3
+ import { nonSerializableStore } from "../non-serializable-store";
4
+ import { Account } from "./auth";
5
+
6
+ export const ThemeValues = <const>["light", "dark", "cupcake", "bumblebee", "emerald", "corporate",
7
+ "synthwave", "retro", "cyberpunk", "valentine", "halloween", "garden", "forest", "aqua", "lofi",
8
+ "pastel", "fantasy", "wireframe", "black", "luxury", "dracula", "cmyk", "autumn", "business",
9
+ "acid", "lemonade", "night", "coffee", "winter"];
10
+ export type Theme = typeof ThemeValues[number];
11
+
12
+ export type Frame = "none" | "account" | "settings" |
13
+ "editor-conf" | "editor-fs" | "network" |
14
+ "stats" | "host-cache" | "quick-save" | "image-rendering";
15
+
16
+ const initialState: {
17
+ modal: "none" | "login",
18
+ frame: Frame,
19
+ frameXs: boolean,
20
+ window: "none" | "error" | "loading" | "prerun" | "run" | "select",
21
+ theme: Theme,
22
+ editor: boolean,
23
+ wideScreen: boolean,
24
+ fullScreen: boolean,
25
+ toast: string | null,
26
+ toastIntent: "none" | "error" | "success" | "panic",
27
+ toastTimeoutId: number,
28
+ background: string | null,
29
+ readOnlyWarning: boolean,
30
+ cloudSaves: boolean,
31
+ } = {
32
+ modal: "none",
33
+ frame: "none",
34
+ frameXs: false,
35
+ window: "none",
36
+ theme: (lStorage.getItem("theme") ?? "dark") as Theme,
37
+ editor: false,
38
+ wideScreen: true,
39
+ fullScreen: false,
40
+ toast: null,
41
+ toastIntent: "none",
42
+ toastTimeoutId: 0,
43
+ background: null,
44
+ readOnlyWarning: false,
45
+ cloudSaves: true,
46
+ };
47
+
48
+ export const uiSlice = createSlice({
49
+ name: "js-dos v8s",
50
+ initialState,
51
+ reducers: {
52
+ theme: (state, a: { payload: Theme }) => {
53
+ lStorage.setItem("theme", a.payload);
54
+ state.theme = a.payload;
55
+ },
56
+ modalLogin: (state) => {
57
+ state.modal = "login";
58
+ },
59
+ modalNone: (state) => {
60
+ state.modal = "none";
61
+ },
62
+ frameNone: (state) => {
63
+ state.frame = "none";
64
+ state.frameXs = false;
65
+ },
66
+ frameAccount: (state) => {
67
+ toggleFrameIfNeeded(state, "account");
68
+ state.frameXs = false;
69
+ },
70
+ frameSettings: (state) => {
71
+ state.frame = "settings";
72
+ state.frameXs = false;
73
+ },
74
+ frameNetwork: (state) => {
75
+ state.frame = "network";
76
+ state.frameXs = false;
77
+ },
78
+ frameStats: (state) => {
79
+ state.frame = "stats";
80
+ state.frameXs = false;
81
+ },
82
+ frameConf: (state) => {
83
+ state.frame = "editor-conf";
84
+ state.frameXs = false;
85
+ },
86
+ frameFs: (state) => {
87
+ state.frame = "editor-fs";
88
+ state.frameXs = false;
89
+ },
90
+ frameHostCache: (state) => {
91
+ state.frame = "host-cache";
92
+ state.frameXs = false;
93
+ },
94
+ frameQuickSave: (state) => {
95
+ state.frame = "quick-save";
96
+ state.frameXs = true;
97
+ },
98
+ frameImageRendering: (state) => {
99
+ state.frame = "image-rendering";
100
+ state.frameXs = false;
101
+ },
102
+ windowSelect: (state) => {
103
+ state.window = "select";
104
+ },
105
+ background: (state, a: { payload: string }) => {
106
+ state.background = a.payload;
107
+ },
108
+ setEditor: (state, a: { payload: boolean }) => {
109
+ state.editor = a.payload;
110
+ },
111
+ setWideScreen: (state, a: { payload: boolean }) => {
112
+ state.wideScreen = a.payload;
113
+ },
114
+ setFullScreen: (state, a: { payload: boolean }) => {
115
+ state.fullScreen = a.payload;
116
+ },
117
+ showToast: (state, a: { payload: { message: string, intent?: typeof initialState.toastIntent } }) => {
118
+ if (state.toastTimeoutId !== 0) {
119
+ clearInterval(state.toastTimeoutId);
120
+ }
121
+ state.toast = a.payload.message;
122
+ state.toastIntent = a.payload.intent ?? "none";
123
+ if (a.payload.intent !== "panic") {
124
+ state.toastTimeoutId = setTimeout(() => {
125
+ nonSerializableStore.dispatch!(uiSlice.actions.hideToast());
126
+ }, 1500);
127
+ }
128
+ },
129
+ hideToast: (state) => {
130
+ state.toast = null;
131
+ state.toastTimeoutId = 0;
132
+ },
133
+ readOnlyWarning: (state, a: { payload: boolean }) => {
134
+ state.readOnlyWarning = a.payload;
135
+ },
136
+ cloudSaves: (state, a: { payload: boolean }) => {
137
+ state.cloudSaves = a.payload;
138
+ },
139
+ },
140
+ extraReducers: (builder) => {
141
+ builder
142
+ .addCase(createAction<string>("dos/bndLoad"), (s, a) => {
143
+ s.window = "loading";
144
+ })
145
+ .addCase(createAction<string>("editor/extract"), (s, a) => {
146
+ s.window = "loading";
147
+ })
148
+ .addCase(createAction<string>("editor/ready"), (s, a) => {
149
+ s.window = "prerun";
150
+ })
151
+ .addCase(createAction<string>("dos/bndReady"), (s, a) => {
152
+ s.window = "prerun";
153
+ })
154
+ .addCase(createAction<string>("dos/bndPlay"), (s, a) => {
155
+ s.window = "run";
156
+ s.frame = "none";
157
+ })
158
+ .addMatcher((action: { type: string }) => {
159
+ return action.type.startsWith("dos/") &&
160
+ action.type.endsWith("Error");
161
+ }, (s, a) => {
162
+ s.window = "error";
163
+ });
164
+ },
165
+ });
166
+
167
+ function toggleFrameIfNeeded(
168
+ state: typeof initialState,
169
+ newFrame: typeof initialState.frame) {
170
+ if (state.frame === newFrame) {
171
+ state.frame = "none";
172
+ } else {
173
+ state.frame = newFrame;
174
+ }
175
+ }
176
+
177
+ export function dispatchLoginAction(account: Account | null, dispatch: Dispatch) {
178
+ if (account === null) {
179
+ dispatch(uiSlice.actions.modalLogin());
180
+ } else {
181
+ dispatch(uiSlice.actions.frameAccount());
182
+ }
183
+ }
package/src/store.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { configureStore } from "@reduxjs/toolkit";
2
+ import { uiSlice } from "./store/ui";
3
+ import { authSlice } from "./store/auth";
4
+ import { dosSlice } from "./store/dos";
5
+ import { i18nSlice } from "./i18n";
6
+ import { storageSlice } from "./store/storage";
7
+ import { editorSlice } from "./store/editor";
8
+ import { nonSerializableStore } from "./non-serializable-store";
9
+
10
+ export function makeStore() {
11
+ return configureStore({
12
+ reducer: {
13
+ i18n: i18nSlice.reducer,
14
+ auth: authSlice.reducer,
15
+ ui: uiSlice.reducer,
16
+ dos: dosSlice.reducer,
17
+ storage: storageSlice.reducer,
18
+ editor: editorSlice.reducer,
19
+ },
20
+ });
21
+ };
22
+
23
+ export const store = makeStore();
24
+
25
+ export type State = ReturnType<typeof store.getState>;
26
+
27
+ nonSerializableStore.dispatch = store.dispatch;