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,157 @@
1
+ import { useEffect } from "preact/hooks";
2
+ import { useDispatch, useSelector } from "react-redux";
3
+ import { Checkbox } from "../components/checkbox";
4
+ import { useT } from "../i18n";
5
+ import { State } from "../store";
6
+ import { dosSlice } from "../store/dos";
7
+ import { Select } from "../components/select";
8
+ import { dispatchLoginAction, uiSlice } from "../store/ui";
9
+ import { LockBadge } from "../components/lock";
10
+ import { Dispatch } from "@reduxjs/toolkit";
11
+
12
+ export function NetworkFrame() {
13
+ const account = useSelector((state: State) => state.auth.account);
14
+ const network = useSelector((state: State) => state.dos.network);
15
+ const room = network.room;
16
+ const server = network.server;
17
+ const disabled = network.ipx !== "disconnected";
18
+ const t = useT();
19
+ const dispatch = useDispatch();
20
+ const premium = account?.premium === true;
21
+ const ipxLink =
22
+ network.ipx === "connected" ?
23
+ location.href + searchSeparator() +
24
+ "ipx=1&server=" + network.server + "&room=" + room :
25
+ null;
26
+
27
+ useEffect(() => {
28
+ if (server !== "netherlands" && account?.premium !== true) {
29
+ dispatch(dosSlice.actions.setServer("netherlands"));
30
+ }
31
+ }, [server, account]);
32
+
33
+ function setRoom(room: string) {
34
+ dispatch(dosSlice.actions.setRoom(room));
35
+ }
36
+
37
+ function setServer(server: string) {
38
+ dispatch(dosSlice.actions.setServer(server as any));
39
+ }
40
+
41
+ function toggleIpx() {
42
+ if (network.ipx === "connected") {
43
+ dispatch(dosSlice.actions.disconnectIpx({}));
44
+ } else {
45
+ dispatch(dosSlice.actions.connectIpx({
46
+ room,
47
+ address: "wss://" + (premium ? server : "netherlands") + ".dos.zone",
48
+ }) as any);
49
+ }
50
+ }
51
+
52
+ function copyAndClose() {
53
+ if (ipxLink) {
54
+ copyToClipBoard(ipxLink, t, dispatch);
55
+ dispatch(uiSlice.actions.frameNone());
56
+ }
57
+ }
58
+
59
+ function lockClick() {
60
+ dispatchLoginAction(account, dispatch);
61
+ }
62
+
63
+ function onServer(newServer: string) {
64
+ setServer(newServer);
65
+
66
+ if (!premium) {
67
+ dispatchLoginAction(account, dispatch);
68
+ return;
69
+ }
70
+ }
71
+
72
+ return <div class="network-frame frame-root items-start px-4 relative">
73
+ <div class="ml-1 mb-4 w-full flex flex-row items-center">
74
+ <Select
75
+ class="text-sm"
76
+ selectClass="w-full"
77
+ label={t("server") + ":"}
78
+ selected={server}
79
+ values={["netherlands", "newyork", "singapore"]}
80
+ disabled={disabled}
81
+ onSelect={onServer}
82
+ />
83
+ <div onClick={lockClick}>
84
+ <LockBadge class="cursor-pointer ml-2 w-4 h-4 text-error" />
85
+ </div>
86
+ </div>
87
+ <div class="form-control w-full">
88
+ <label class="label">
89
+ <span class="label-text">{t("room")}:</span>
90
+ </label>
91
+ <input type="text"
92
+ class="input w-full input-sm input-bordered"
93
+ disabled={disabled}
94
+ onChange={(e) => setRoom(e.currentTarget.value)}
95
+ value={room}></input>
96
+ </div>
97
+ <Checkbox
98
+ class={"mt-4 " + (network.ipx === "error" ? "error" : "")}
99
+ onChange={toggleIpx}
100
+ label="IPX"
101
+ checked={network.ipx === "connected"}
102
+ disabled={network.ipx === "connecting"}
103
+ intermediate={network.ipx === "connecting"}
104
+ />
105
+
106
+ {ipxLink !== null && <div class="mt-4 text-sm alert alert-success shadow-lg flex flex-col">
107
+ <div>{t("copy_net_link")}:</div>
108
+ <div class="flex flex-row">
109
+ <svg xmlns="http://www.w3.org/2000/svg"
110
+ class="w-6 h-6 mx-1 cursor-pointer hover:text-accent-focus flex-shrink-0"
111
+ fill="none" onClick={copyAndClose}
112
+ viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
113
+ <path stroke-linecap="round"
114
+ stroke-linejoin="round"
115
+ d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125
116
+ 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06
117
+ 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504
118
+ 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0
119
+ 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5
120
+ 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12
121
+ 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125
122
+ 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" />
123
+ </svg>
124
+ <div class="contextmenu underline break-all"
125
+ target="_blank">{ipxLink}</div>
126
+ </div>
127
+ </div>}
128
+ </div >;
129
+ }
130
+
131
+ function searchSeparator() {
132
+ if (location.href.endsWith("?") || location.href.endsWith("&")) {
133
+ return "";
134
+ }
135
+ return location.href.indexOf("?") > 0 ? "&" : "?";
136
+ }
137
+
138
+ async function copyToClipBoard(text: string,
139
+ t: (key: string) => string,
140
+ dispatch: Dispatch) {
141
+ if (!navigator.clipboard) {
142
+ return;
143
+ }
144
+
145
+ try {
146
+ await navigator.clipboard.writeText(text);
147
+ dispatch(uiSlice.actions.showToast({
148
+ message: t("copied"),
149
+ intent: "success",
150
+ }));
151
+ } catch (e: any) {
152
+ dispatch(uiSlice.actions.showToast({
153
+ message: t("error"),
154
+ intent: "error",
155
+ }));
156
+ }
157
+ }
@@ -0,0 +1,96 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { LockBadge } from "../components/lock";
3
+ import { CloudSaveButton } from "../sidebar/cloud-save-button";
4
+ import { DisketteIcon } from "../sidebar/diskette-icon";
5
+ import { State, useNonSerializableStore } from "../store";
6
+ import { dispatchLoginAction } from "../store/ui";
7
+
8
+ export function QuickSaveFrame() {
9
+ const premium = useSelector((state: State) => state.auth.account)?.premium;
10
+ return <div class="quick-save-frame flex flex-col justify-center items-center -my-2">
11
+ <CloudSaveButton />
12
+ <SaveButton label={1} bgcolor="bg-primary" textcolor="text-primary-content" />
13
+ <LoadButton label={1} bgcolor="bg-primary" />
14
+ <SaveButton disabled={!premium} label={2} bgcolor="bg-secondary" textcolor="text-secondary-content" />
15
+ <LoadButton disabled={!premium} label={2} bgcolor="bg-secondary" />
16
+ <SaveButton disabled={!premium} label={3} bgcolor="bg-accent" textcolor="text-accent-content" />
17
+ <LoadButton disabled={!premium} label={3} bgcolor="bg-accent" />
18
+ </div>;
19
+ }
20
+
21
+ function SaveButton(props: {
22
+ label: number,
23
+ bgcolor: string,
24
+ textcolor: string,
25
+ disabled?: boolean,
26
+ }) {
27
+ const account = useSelector((state: State) => state.auth.account);
28
+ const dispatch = useDispatch();
29
+ const nonSerializableStore = useNonSerializableStore();
30
+ function onClick() {
31
+ if (props.disabled === true) {
32
+ dispatchLoginAction(account, dispatch);
33
+ }
34
+
35
+ const ci = nonSerializableStore.ci;
36
+ if (ci === null) {
37
+ return;
38
+ }
39
+
40
+ ci.sendBackendEvent({
41
+ type: "wc-trigger-event",
42
+ event: "hand_savestate",
43
+ });
44
+ }
45
+
46
+ return <div class={"sidebar-button flex justify-center " + (props.disabled ? "opacity-50" : "")}
47
+ onClick={onClick}>
48
+ <DisketteIcon />
49
+ {props.disabled !== true &&
50
+ <div class={"text-badge " + props.textcolor + " " + props.bgcolor}>{props.label}</div>}
51
+ {props.disabled === true && <LockBadge />}
52
+
53
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none"
54
+ viewBox="0 0 24 24" stroke-width="1.5"
55
+ stroke="currentColor" class="absolute bottom-1 w-3 h-3">
56
+ <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
57
+ </svg>
58
+ </div>;
59
+ }
60
+
61
+ function LoadButton(props: {
62
+ label: number,
63
+ bgcolor: string,
64
+ disabled?: boolean,
65
+ }) {
66
+ const account = useSelector((state: State) => state.auth.account);
67
+ const dispatch = useDispatch();
68
+ const nonSerializableStore = useNonSerializableStore();
69
+ function onClick() {
70
+ if (props.disabled === true) {
71
+ dispatchLoginAction(account, dispatch);
72
+ }
73
+
74
+ const ci = nonSerializableStore.ci;
75
+ if (ci === null) {
76
+ return;
77
+ }
78
+
79
+ ci.sendBackendEvent({
80
+ type: "wc-trigger-event",
81
+ event: "hand_loadstate",
82
+ });
83
+ }
84
+
85
+ return <div class={"sidebar-button flex justify-center " + (props.disabled ? " opacity-50" : "")}
86
+ onClick={onClick}>
87
+ <DisketteIcon class="opacity-10"/>
88
+ {props.disabled === true && <LockBadge />}
89
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none"
90
+ viewBox="0 0 24 24" stroke-width="1.5"
91
+ stroke="currentColor" class="absolute bottom-1 w-6 h-6 -scale-y-100">
92
+ <path stroke-linecap="round" stroke-linejoin="round"
93
+ d="m15 15 6-6m0 0-6-6m6 6H9a6 6 0 0 0 0 12h3" />
94
+ </svg>
95
+ </div>;
96
+ }
@@ -0,0 +1,14 @@
1
+ import { Editor, MouseCapture, PauseCheckbox } from "../components/dos-option-checkbox";
2
+ import { ThemeSelect } from "../components/dos-option-select";
3
+ import { MouseSensitiviySlider, VolumeSlider } from "../components/dos-option-slider";
4
+
5
+ export function SettingsFrame(props: {}) {
6
+ return <div class="settings-frame frame-root items-start pl-4">
7
+ <PauseCheckbox />
8
+ <MouseCapture />
9
+ <MouseSensitiviySlider />
10
+ <VolumeSlider />
11
+ <Editor />
12
+ <ThemeSelect multiline={true} />
13
+ </div>;
14
+ }
@@ -0,0 +1,87 @@
1
+ import { useSelector } from "react-redux";
2
+ import { State } from "../store";
3
+
4
+ export function StatsFrame() {
5
+ const startedAt = useSelector((state: State) => state.dos.ciStartedAt);
6
+ const stats = useSelector((state: State) => state.dos.stats);
7
+ const cycles = Math.round(useSelector((state: State) => state.dos.stats.cyclesPerMs) / 1000);
8
+ const driveRecvMb = Math.round(stats.driveRecv / 1024 / 1024 * 100) / 100;
9
+ const driveSpeedMb = (stats.driveRecvTime / 1000) > 1 ?
10
+ Math.round(driveRecvMb / stats.driveRecvTime * 1000 * 100) / 100 : 0;
11
+ return <div class="stats-frame frame-root items-start px-4">
12
+ <div class="w-full overflow-x-auto">
13
+ <table class="table table-compact w-full">
14
+ <thead>
15
+ <tr>
16
+ <th>Metric</th>
17
+ <th>Value</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <tr>
22
+ <td>Uptime</td>
23
+ <td>{Math.round((Date.now() - startedAt) / 100) / 10} s</td>
24
+ </tr>
25
+ <tr>
26
+ <td>Cycles/ms</td>
27
+ { cycles <= 0 && <td>~ K</td> }
28
+ { cycles > 0 && cycles <= 1000 && <td>{cycles} K</td> }
29
+ { cycles > 1000 && <td>{Math.round(cycles / 1000)} KK</td> }
30
+ </tr>
31
+ <tr>
32
+ <td>NonSkipSleep COUNT/s</td>
33
+ <td>{stats.nonSkippableSleepPreSec}</td>
34
+ </tr>
35
+ <tr>
36
+ <td>Sleep COUNT/s</td>
37
+ <td>{stats.sleepPerSec}</td>
38
+ </tr>
39
+ <tr>
40
+ <td>Sleep TIME/s</td>
41
+ <td>{stats.sleepTimePerSec}</td>
42
+ </tr>
43
+ <tr>
44
+ <td>Msg FRAME/s</td>
45
+ <td>{stats.framePerSec}</td>
46
+ </tr>
47
+ <tr>
48
+ <td>Msg SOUND/s</td>
49
+ <td>{stats.soundPerSec}</td>
50
+ </tr>
51
+ <tr>
52
+ <td>Msg SENT/s</td>
53
+ <td>{stats.msgSentPerSec}</td>
54
+ </tr>
55
+ <tr>
56
+ <td>Msg RECV/s</td>
57
+ <td>{stats.msgRecvPerSec}</td>
58
+ </tr>
59
+ <tr>
60
+ <td>Net SENT/s</td>
61
+ <td>{Math.round(stats.netSent / 1024 * 100) / 100}Kb</td>
62
+ </tr>
63
+ <tr>
64
+ <td>Net RECV/s</td>
65
+ <td>{Math.round(stats.netRecv / 1024 * 100) / 100}Kb</td>
66
+ </tr>
67
+ <tr>
68
+ <td>HDD RECV/s</td>
69
+ <td>{driveRecvMb}Mb (~{driveSpeedMb}Mb/s)</td>
70
+ </tr>
71
+ <tr>
72
+ <td>HDD SENT/s</td>
73
+ <td>{Math.round(stats.driveSent / 1024 / 1024 * 100) / 100}Mb</td>
74
+ </tr>
75
+ <tr>
76
+ <td>HDD Cache</td>
77
+ <td>{Math.round(stats.driveCacheUsed / 1024 / 1024 * 100) / 100}Mb</td>
78
+ </tr>
79
+ <tr>
80
+ <td>HDD Cache Hit/Miss</td>
81
+ <td>{stats.driveCacheHit} / {stats.driveCacheMiss}</td>
82
+ </tr>
83
+ </tbody>
84
+ </table>
85
+ </div>
86
+ </div>;
87
+ }
@@ -0,0 +1,91 @@
1
+ import { storageSlice } from "../store/storage";
2
+ import { Account } from "../store/auth";
3
+ import { brCdn } from "../v8/config";
4
+ import { Store, getNonSerializableStore } from "../store";
5
+
6
+ export function bundleFromFile(file: File, store: Store): Promise<Uint8Array> {
7
+ return new Promise<Uint8Array>((resolve) => {
8
+ store.dispatch(storageSlice.actions.reset());
9
+ const reader = new FileReader();
10
+ reader.addEventListener("load", async (e) => {
11
+ resolve(new Uint8Array(reader.result as ArrayBuffer));
12
+ });
13
+ reader.addEventListener("progress", (e) => {
14
+ store.dispatch(storageSlice.actions.progress([e.loaded, e.total]));
15
+ });
16
+ reader.readAsArrayBuffer(file);
17
+ });
18
+ }
19
+
20
+
21
+ export async function bundleFromChanges(url: string, account: Account | null,
22
+ store: Store): Promise<Uint8Array | null> {
23
+ if (account === null || !account.premium) {
24
+ return await getNonSerializableStore(store).cache.get(url).catch(() => null);
25
+ }
26
+
27
+ try {
28
+ const response = await fetch(url, {
29
+ cache: "no-cache",
30
+ });
31
+
32
+ if (response.status !== 200) {
33
+ throw new Error("Resource not avalible (" + response.status + "): " + response.statusText);
34
+ }
35
+
36
+ return response.arrayBuffer().then((b) => new Uint8Array(b));
37
+ } catch (e: any) {
38
+ return await getNonSerializableStore(store).cache.get(url).catch(() => null);
39
+ }
40
+ }
41
+
42
+ export async function bundleFromUrl(url: string, store: Store): Promise<Uint8Array> {
43
+ try {
44
+ return await getNonSerializableStore(store).cache.get(url);
45
+ } catch (e: any) {
46
+ // ignore
47
+ }
48
+
49
+ store.dispatch(storageSlice.actions.reset());
50
+ const response = await fetch(url, {
51
+ cache: "no-store",
52
+ });
53
+
54
+ if (response.status !== 200) {
55
+ throw new Error("Resource not avalible (" + response.status + "): " + response.statusText);
56
+ }
57
+
58
+ const lenHeader = response.headers.get("Content-Length");
59
+ const length = lenHeader === null ? 0 :
60
+ Number.parseInt(lenHeader);
61
+ const reader = response.body!.getReader();
62
+
63
+ let received = 0;
64
+ const chunks: Uint8Array[] = [];
65
+ while (true) {
66
+ const { done, value } = await reader.read();
67
+
68
+ if (done) {
69
+ break;
70
+ }
71
+
72
+ chunks.push(value);
73
+ received += value.length;
74
+
75
+ const bytes = Math.min(url.startsWith(brCdn) ? received / 2 : received, length);
76
+ store.dispatch(storageSlice.actions.progress([bytes, length]));
77
+ }
78
+
79
+ let offset = 0;
80
+ const complete = new Uint8Array(received);
81
+ for (const next of chunks) {
82
+ complete.set(next, offset);
83
+ offset += next.length;
84
+ }
85
+
86
+ getNonSerializableStore(store).cache
87
+ .put(url, complete)
88
+ .catch(console.error);
89
+
90
+ return complete;
91
+ };
@@ -0,0 +1,35 @@
1
+ import { Store, getNonSerializableStore } from "../store";
2
+ import { uiSlice } from "../store/ui";
3
+
4
+ export function browserSetFullScreen(fullScreen: boolean, store: Store) {
5
+ const root = getNonSerializableStore(store).root as any;
6
+ if (fullScreen) {
7
+ if (root.requestFullscreen) {
8
+ root.requestFullscreen();
9
+ } else if (root.webkitRequestFullscreen) {
10
+ root.webkitRequestFullscreen();
11
+ } else if (root.mozRequestFullScreen) {
12
+ root.mozRequestFullScreen();
13
+ } else if (root.msRequestFullscreen) {
14
+ root.msRequestFullscreen();
15
+ } else if (root.webkitEnterFullscreen) {
16
+ root.webkitEnterFullscreen();
17
+ } else {
18
+ root.classList.add("jsdos-fullscreen-workaround");
19
+ }
20
+ } else {
21
+ if (root.classList.contains("jsdos-fullscreen-workaround")) {
22
+ root.classList.remove("jsdos-fullscreen-workaround");
23
+ } else if (document.exitFullscreen) {
24
+ document.exitFullscreen();
25
+ } else if ((document as any).webkitExitFullscreen) {
26
+ (document as any).webkitExitFullscreen();
27
+ } else if ((document as any).mozCancelFullScreen) {
28
+ (document as any).mozCancelFullScreen();
29
+ } else if ((document as any).msExitFullscreen) {
30
+ (document as any).msExitFullscreen();
31
+ }
32
+ }
33
+
34
+ store.dispatch(uiSlice.actions.setFullScreen(fullScreen));
35
+ }
@@ -0,0 +1,203 @@
1
+ /* eslint @typescript-eslint/no-unused-vars: 0 */
2
+
3
+ export interface Cache {
4
+ readonly owner: string;
5
+ put: (key: string, data: Uint8Array) => Promise<void>;
6
+ get: (key: string, defaultValue?: Uint8Array) => Promise<Uint8Array>;
7
+ del: (key: string) => Promise<void>;
8
+ keys: () => Promise<string[]>;
9
+ forEach: (each: (key: string, value: Uint8Array) => void) => Promise<void>;
10
+ close: () => void;
11
+ }
12
+
13
+ export class CacheNoop implements Cache {
14
+ public owner = "";
15
+
16
+ public close() {
17
+ }
18
+
19
+ public put(key: string, data: Uint8Array): Promise<void> {
20
+ return Promise.resolve();
21
+ }
22
+
23
+ public get(key: string, defaultValue?: Uint8Array): Promise<Uint8Array> {
24
+ if (defaultValue !== undefined) {
25
+ return Promise.resolve(defaultValue);
26
+ }
27
+ return Promise.reject(new Error("Cache is not supported on this host"));
28
+ }
29
+
30
+ public del(key: string): Promise<void> {
31
+ return Promise.resolve();
32
+ }
33
+
34
+ public keys(): Promise<string[]> {
35
+ return Promise.resolve([]);
36
+ }
37
+
38
+ public forEach(each: (key: string, value: Uint8Array) => void) {
39
+ return Promise.resolve();
40
+ }
41
+ }
42
+
43
+ class LCache implements Cache {
44
+ public owner: string;
45
+ private storeName = "files";
46
+ private indexedDB: IDBFactory;
47
+ private db: IDBDatabase | null = null;
48
+
49
+ constructor(owner: string,
50
+ onready: (cache: Cache) => void,
51
+ onerror: (msg: string) => void) {
52
+ this.owner = owner;
53
+ this.indexedDB = (typeof window === "undefined" ? undefined : window.indexedDB ||
54
+ (window as any).mozIndexedDB ||
55
+ (window as any).webkitIndexedDB || (window as any).msIndexedDB) as any;
56
+
57
+ if (!this.indexedDB) {
58
+ onerror("Indexed db is not supported on this host");
59
+ return;
60
+ }
61
+
62
+ try {
63
+ const openRequest = this.indexedDB.open("js-dos-cache (" + owner + ")", 1);
64
+ openRequest.onerror = (event) => {
65
+ onerror("Can't open cache database: " + openRequest.error?.message);
66
+ };
67
+ openRequest.onsuccess = (event) => {
68
+ this.db = openRequest.result;
69
+ onready(this);
70
+ };
71
+ openRequest.onupgradeneeded = (event) => {
72
+ try {
73
+ this.db = openRequest.result;
74
+ this.db.onerror = (event) => {
75
+ onerror("Can't upgrade cache database");
76
+ };
77
+
78
+ const objectStore = this.db.createObjectStore(this.storeName);
79
+ objectStore.createIndex("key", "", {
80
+ unique: true,
81
+ multiEntry: false,
82
+ });
83
+ } catch (e) {
84
+ onerror("Can't upgrade cache database");
85
+ }
86
+ };
87
+ } catch (e: any) {
88
+ onerror("Can't open cache database: " + e.message);
89
+ }
90
+ }
91
+
92
+ public close() {
93
+ if (this.db !== null) {
94
+ this.db.close();
95
+ this.db = null;
96
+ }
97
+ }
98
+
99
+ public put(key: string, data: Uint8Array): Promise<void> {
100
+ return new Promise<void>((resolve, reject) => {
101
+ if (this.db === null) {
102
+ resolve();
103
+ return;
104
+ }
105
+
106
+ const transaction = this.db.transaction(this.storeName, "readwrite");
107
+ const request = transaction.objectStore(this.storeName).put(data.buffer, key);
108
+ request.onerror = () => reject;
109
+ request.onsuccess = () => resolve();
110
+ });
111
+ }
112
+
113
+ public del(key: string): Promise<void> {
114
+ return new Promise<void>((resolve, reject) => {
115
+ if (this.db === null) {
116
+ resolve();
117
+ return;
118
+ }
119
+
120
+ const transaction = this.db.transaction(this.storeName, "readwrite");
121
+ const request = transaction.objectStore(this.storeName).delete(key);
122
+ request.onerror = () => reject;
123
+ request.onsuccess = () => resolve();
124
+ });
125
+ }
126
+
127
+ public get(key: string, defaultValue?: Uint8Array): Promise<Uint8Array> {
128
+ return new Promise<Uint8Array>((resolve, reject) => {
129
+ function rejectOrResolve(message: string) {
130
+ if (defaultValue === undefined) {
131
+ reject(new Error(message));
132
+ } else {
133
+ resolve(defaultValue);
134
+ }
135
+ }
136
+
137
+
138
+ if (this.db === null) {
139
+ rejectOrResolve("db is not initalized");
140
+ return;
141
+ }
142
+
143
+ const transaction = this.db.transaction(this.storeName, "readonly");
144
+ const request = transaction.objectStore(this.storeName).get(key) as IDBRequest<ArrayBuffer>;
145
+ request.onerror = () => reject(new Error("Can't read value for key '" + key + "'"));
146
+ request.onsuccess = () => {
147
+ if (request.result) {
148
+ resolve(new Uint8Array(request.result));
149
+ } else {
150
+ rejectOrResolve("Result is empty for key '" + key + "', result: " + request.result);
151
+ }
152
+ };
153
+ });
154
+ }
155
+
156
+ public keys(): Promise<string[]> {
157
+ return new Promise<string[]>((resolve, reject) => {
158
+ if (this.db === null) {
159
+ resolve([]);
160
+ return;
161
+ }
162
+
163
+ const transaction = this.db.transaction(this.storeName, "readonly");
164
+ const request = transaction.objectStore(this.storeName).index("key").getAllKeys();
165
+ request.onerror = () => reject;
166
+ request.onsuccess = (event) => {
167
+ const keys = (event.target as any).result as string[];
168
+ resolve(keys);
169
+ };
170
+ });
171
+ }
172
+
173
+ public forEach(each: (key: string, value: Uint8Array) => void): Promise<void> {
174
+ return new Promise<void>((resolve, reject) => {
175
+ if (this.db === null) {
176
+ resolve();
177
+ return;
178
+ }
179
+
180
+ const transaction = this.db.transaction(this.storeName, "readonly");
181
+ const request = transaction.objectStore(this.storeName).openCursor();
182
+ request.onerror = () => reject;
183
+ request.onsuccess = (event) => {
184
+ const cursor = (event.target as any).result as IDBCursorWithValue;
185
+ if (cursor) {
186
+ each(cursor.key.toString(), new Uint8Array(cursor.value));
187
+ cursor.continue();
188
+ } else {
189
+ resolve();
190
+ }
191
+ };
192
+ });
193
+ }
194
+ }
195
+
196
+ export function getCache(owner: string): Promise<Cache> {
197
+ return new Promise((resolve) => {
198
+ new LCache(owner, resolve, (msg: string) => {
199
+ console.error("Can't open IndexedDB cache", msg);
200
+ resolve(new CacheNoop());
201
+ });
202
+ });
203
+ }