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,55 @@
1
+ .jsdos-rso {
2
+ .editor-conf-frame {
3
+ @apply w-full overflow-hidden flex-grow flex flex-col items-center justify-center h-full px-4;
4
+
5
+ textarea {
6
+ @apply w-full textarea;
7
+ resize: none;
8
+ }
9
+ }
10
+
11
+ .editor-fs-frame {
12
+ @apply h-full;
13
+
14
+ .fs-tree-view {
15
+ @apply form-control bg-base-100 rounded w-full h-full;
16
+
17
+ .fs-tree {
18
+
19
+ ol {
20
+ @apply ml-2;
21
+ }
22
+
23
+ li {
24
+ @apply my-2;
25
+ }
26
+
27
+ button {
28
+ border: none;
29
+ background: none;
30
+ filter: none;
31
+ min-height: auto;
32
+ height: auto;
33
+ @apply m-0 p-0;
34
+ }
35
+
36
+ svg {
37
+ @apply text-accent;
38
+ }
39
+
40
+ input {
41
+ @apply checkbox checkbox-accent mr-2 w-4 h-4;
42
+ }
43
+
44
+ .rct-text,
45
+ .rct-bare-label,
46
+ label {
47
+ display: flex;
48
+ flex-direction: row;
49
+ justify-content: start;
50
+ align-items: center;
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,315 @@
1
+ import { FsNode } from "emulators/dist/types/protocol/protocol";
2
+ import { useEffect, useRef, useState } from "preact/hooks";
3
+ import CheckboxTree, { Node, OnCheckNode, OnExpandNode } from "react-checkbox-tree";
4
+ import { useDispatch, useSelector } from "react-redux";
5
+ import { useT } from "../../i18n";
6
+ import { loadBundle } from "../../load";
7
+ import { State } from "../../store";
8
+ import { nonSerializableStore } from "../../non-serializable-store";
9
+ import { downloadArrayToFs } from "../../download-file";
10
+
11
+ interface NodeExt extends Node {
12
+ fsNode: FsNode,
13
+ }
14
+
15
+ export function EditorFsFrame() {
16
+ const t = useT();
17
+ const haveCi = useSelector((state: State) => state.dos.ci);
18
+ const [fsSize, setFsSize] = useState<number>(0);
19
+ const [fsTree, _setFsTree] = useState<NodeExt[] | null>(null);
20
+ const [expanded, setExpanded] = useState<string[]>([]);
21
+ const [checked, setChecked] = useState<string[]>([]);
22
+ const [uploadingFile, setUploadingFile] = useState<string | null>(null);
23
+ const [makingBundle, setMakingBundle] = useState<boolean>(false);
24
+
25
+ function setFsTree(fsTree: NodeExt[] | null) {
26
+ _setFsTree(fsTree);
27
+ setFsSize(Math.round(calcFsSize(fsTree) / 1024 / 1024 * 100) / 100);
28
+ }
29
+
30
+ useEffect(() => {
31
+ const ci = nonSerializableStore.ci;
32
+ if (ci === null || !haveCi) {
33
+ return;
34
+ }
35
+
36
+ let cancled = false;
37
+ ci.fsTree()
38
+ .then((tree) => {
39
+ if (!cancled) {
40
+ setFsTree(toNodes(tree));
41
+ }
42
+ })
43
+ .catch((e) => {
44
+ console.error(e);
45
+ if (!cancled) {
46
+ setFsTree(null);
47
+ }
48
+ });
49
+
50
+ return () => {
51
+ cancled = true;
52
+ };
53
+ }, [haveCi]);
54
+
55
+
56
+ function onExpand(expanded: Array<string>, node: OnExpandNode) {
57
+ setExpanded(expanded);
58
+ }
59
+
60
+ function onCheck(checked: Array<string>, node: OnCheckNode) {
61
+ setChecked(checked);
62
+ }
63
+
64
+ async function onRefresh() {
65
+ setFsTree(null);
66
+ const ci = nonSerializableStore.ci;
67
+ if (ci === null || !haveCi) {
68
+ return;
69
+ }
70
+ const tree = await ci.fsTree();
71
+ setFsTree(toNodes(tree));
72
+ }
73
+
74
+ // const check = <input type="checkbox" class="checkbox" />;
75
+ return <div class="editor-fs-frame frame-root items-start px-4">
76
+ {(makingBundle || uploadingFile !== null) &&
77
+ <div class="card card-bordered bg-base-100 shadow-xl w-full">
78
+ <div class="card-body">
79
+ <div class="card-title">
80
+ {t(makingBundle ? "please_wait" : "uploading_file")}
81
+ </div>
82
+ <span class="break-words">
83
+ {makingBundle ? t("making_bundle") : uploadingFile}
84
+ </span>
85
+ </div>
86
+ </div>}
87
+ {!makingBundle && uploadingFile === null && fsTree &&
88
+ <div className="fs-tree-view">
89
+ <Actions
90
+ onRefresh={onRefresh}
91
+ onUploadingFile={setUploadingFile}
92
+ onMakingBundle={setMakingBundle} />
93
+ <div class="bg-base-200 px-2 py-1 text-right">
94
+ {t("size")}: {fsSize} Mb
95
+ </div>
96
+ <div class="fs-tree">
97
+ <CheckboxTree
98
+ icons={{
99
+ expandOpen: <FolderOpened /> as any,
100
+ expandClose: <FolderClosed /> as any,
101
+ // expandAll: <div class="bg-red-100 w-4 h-4"></div> as any,
102
+ // parentOpen: <div class="bg-blue-100 w-4 h-4"></div> as any,
103
+ // parentClose: <div class="bg-purple-100 w-4 h-4"></div> as any,
104
+ }}
105
+ iconsClass="icon"
106
+ nativeCheckboxes={false}
107
+ onlyLeafCheckboxes={true}
108
+ nodes={fsTree}
109
+ checked={checked}
110
+ onCheck={onCheck}
111
+ expanded={expanded}
112
+ onExpand={onExpand} />
113
+ </div>
114
+ </div>}
115
+ </div>;
116
+ }
117
+
118
+ function toNodes(fsTree: FsNode): NodeExt[] {
119
+ const fs: NodeExt[] = [];
120
+ for (const next of (fsTree.nodes?.sort(nodeSorter) || [])) {
121
+ fs.push(toNode(next, "."));
122
+ }
123
+ return fs;
124
+ }
125
+
126
+ function toNode(fsNode: FsNode, path: string): NodeExt {
127
+ const fullPath = path + "/" + fsNode.name;
128
+ const nodeExt: NodeExt = {
129
+ label: fsNode.name,
130
+ value: fullPath,
131
+ fsNode,
132
+ };
133
+
134
+ if (fsNode.nodes !== null) {
135
+ const children = fsNode.nodes.sort(nodeSorter)
136
+ .map((next) => toNode(next, fullPath));
137
+ nodeExt.children = children;
138
+ }
139
+
140
+ return nodeExt;
141
+ }
142
+
143
+ function Actions(props: {
144
+ onRefresh: () => void,
145
+ onUploadingFile: (file: string | null) => void,
146
+ onMakingBundle: (making: boolean) => void,
147
+ }) {
148
+ const t = useT();
149
+ const uploadDirRef = useRef<HTMLInputElement>(null);
150
+ const uploadFileRef = useRef<HTMLInputElement>(null);
151
+ const dispatch = useDispatch();
152
+ const { onRefresh, onUploadingFile, onMakingBundle } = props;
153
+
154
+ useEffect(() => {
155
+ if (uploadDirRef.current !== null) {
156
+ uploadDirRef.current.setAttribute("directory", "");
157
+ uploadDirRef.current.setAttribute("webkitdirectory", "");
158
+ }
159
+ }, [uploadDirRef]);
160
+
161
+ function onUploadClick(file: boolean) {
162
+ const input = (file ? uploadFileRef : uploadDirRef).current;
163
+ if (input === null) {
164
+ return;
165
+ }
166
+
167
+ input.click();
168
+ }
169
+
170
+ async function onUpload(event: any) {
171
+ const ci = nonSerializableStore.ci;
172
+ const files: FileList = event.target.files;
173
+ if (ci === null || files === null) {
174
+ return;
175
+ }
176
+
177
+ try {
178
+ for (const file of files) {
179
+ onUploadingFile(file.name);
180
+ const parts = (file.webkitRelativePath ?? "").split("/");
181
+ parts.shift();
182
+ const relPath = parts.join("/");
183
+ await ci.fsWriteFile(relPath.length == 0 ? file.name : relPath, file.stream());
184
+ }
185
+ } finally {
186
+ onUploadingFile(null);
187
+ }
188
+
189
+ await onRefresh();
190
+ }
191
+
192
+ async function onRestart() {
193
+ const ci = nonSerializableStore.ci;
194
+ if (ci === null || !window.confirm(t("fs_restart"))) {
195
+ return;
196
+ }
197
+
198
+ onMakingBundle(true);
199
+ try {
200
+ const bundle = await ci.persist(false);
201
+ if (bundle) {
202
+ loadBundle(bundle, true, dispatch);
203
+ }
204
+ } finally {
205
+ onMakingBundle(false);
206
+ }
207
+ }
208
+
209
+ async function onDownload() {
210
+ const ci = nonSerializableStore.ci;
211
+ if (ci === null) {
212
+ return;
213
+ }
214
+
215
+ onMakingBundle(true);
216
+ try {
217
+ const bundle = await ci.persist(false);
218
+ if (bundle) {
219
+ downloadArrayToFs("bundle.jsdos", bundle);
220
+ }
221
+ } finally {
222
+ onMakingBundle(false);
223
+ }
224
+ }
225
+
226
+ return <div class="h-6 flex flex-row">
227
+ <input class="hidden" type="file" multiple ref={uploadDirRef} onChange={onUpload} />
228
+ <input class="hidden" type="file" multiple ref={uploadFileRef} onChange={onUpload} />
229
+ <button class="refresh btn-xs rounded-none" onClick={onRefresh}>
230
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none"
231
+ viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
232
+ <path stroke-linecap="round" stroke-linejoin="round"
233
+ d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181
234
+ 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181
235
+ 3.182m0-4.991v4.99" />
236
+ </svg>
237
+ </button>
238
+ <button class="add-file btn-xs rounded-none" onClick={() => onUploadClick(true)}>
239
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none"
240
+ viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
241
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375
242
+ 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0
243
+ 00-3.375-3.375H8.25m3.75 9v6m3-3H9m1.5-12H5.625c-.621 0-1.125.504-1.125
244
+ 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9
245
+ 9 0 00-9-9z" />
246
+ </svg>
247
+ </button>
248
+ <button class="add-dir btn-xs rounded-none" onClick={() => onUploadClick(false)}>
249
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none"
250
+ viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
251
+ <path stroke-linecap="round" stroke-linejoin="round"
252
+ d="M12 10.5v6m3-3H9m4.06-7.19l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25
253
+ 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75
254
+ 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" />
255
+ </svg>
256
+ </button>
257
+ <div class="flex-grow"></div>
258
+ <button class="download btn-xs rounded-none" onClick={onDownload}>
259
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none"
260
+ viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
261
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 13.5l3 3m0 0l3-3m-3
262
+ 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 2.25
263
+ 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5
264
+ 0 01-1.06-.44z" />
265
+ </svg>
266
+ </button>
267
+ <button class="restart btn-xs rounded-none" onClick={onRestart}>
268
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none"
269
+ viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
270
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5V18M15 7.5V18M3
271
+ 16.811V8.69c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 010 1.954l-7.108
272
+ 4.061A1.125 1.125 0 013 16.811z" />
273
+ </svg>
274
+ </button>
275
+ </div>;
276
+ }
277
+
278
+ function nodeSorter(a: FsNode, b: FsNode) {
279
+ if (a.nodes !== null && b.nodes !== null) {
280
+ return b.name.localeCompare(a.name);
281
+ }
282
+
283
+ return a.nodes === null ? 1 : -1;
284
+ }
285
+
286
+ function FolderOpened() {
287
+ return <svg xmlns="http://www.w3.org/2000/svg" fill="none"
288
+ viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-2">
289
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
290
+ </svg>;
291
+ }
292
+
293
+ function FolderClosed() {
294
+ return <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
295
+ stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-2">
296
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
297
+ </svg>;
298
+ }
299
+
300
+ function calcFsSize(fsTree: NodeExt[] | null) {
301
+ if (!fsTree) {
302
+ return 0;
303
+ }
304
+
305
+ let size = 0;
306
+ for (const next of fsTree) {
307
+ if (next.fsNode.size !== null) {
308
+ size += next.fsNode.size!;
309
+ } else {
310
+ size += calcFsSize(next.children as NodeExt[]);
311
+ }
312
+ }
313
+
314
+ return size;
315
+ }
@@ -0,0 +1,127 @@
1
+ @import "./editor/editor-frame.css";
2
+
3
+ .jsdos-rso {
4
+ .frame-root {
5
+ @apply flex flex-col;
6
+ }
7
+
8
+ .frame {
9
+ @apply absolute left-0 top-0 pl-12 overflow-auto h-full w-96 py-4;
10
+ background-color: hsl(var(--b3));
11
+ }
12
+
13
+ .frame-md {
14
+ width: 100% !important;
15
+ }
16
+
17
+ .frame-xs {
18
+ width: calc(var(--sidebar-width) * 2) !important;
19
+ }
20
+
21
+ .premium-plan-root {
22
+ @apply bg-white rounded-xl px-4 pt-4 pb-2 w-64;
23
+ }
24
+
25
+ .premium-plan-root.have-premium {
26
+ @apply bg-green-200;
27
+ }
28
+
29
+ .premium-plan-head {
30
+ @apply text-blue-400;
31
+ }
32
+
33
+ .premium-plan-cost {
34
+ @apply text-blue-600 text-5xl;
35
+ }
36
+
37
+ .premium-plan-cost-expl {
38
+ @apply text-gray-600 ml-4 flex flex-col;
39
+ }
40
+
41
+ .premium-plan-highlight {
42
+ @apply flex flex-row text-xs items-center py-2 border-b border-b-gray-200;
43
+ @apply text-gray-600;
44
+ }
45
+
46
+ .premium-plan-root.have-premium .premium-plan-highlight {
47
+ @apply border-b-green-300;
48
+ }
49
+
50
+ .settings-frame {
51
+ @apply px-6 -mt-2;
52
+
53
+ .label {
54
+ @apply p-0;
55
+ }
56
+
57
+ .label-text {
58
+ font-size: inherit;
59
+ }
60
+
61
+ .option {
62
+ @apply w-full justify-between;
63
+ }
64
+
65
+ .slider {
66
+ .touch {
67
+ @apply w-40 flex-grow-0;
68
+ }
69
+ }
70
+ }
71
+
72
+ .image-rendering-frame {
73
+ @apply px-6 -mt-2;
74
+
75
+ .option {
76
+ @apply w-full justify-between;
77
+ }
78
+ }
79
+
80
+ .account-frame {
81
+ .account-img {
82
+ @apply w-24 h-24 my-8 rounded-full;
83
+ }
84
+
85
+ .account-email {
86
+ @apply text-xs mt-4;
87
+ }
88
+ }
89
+
90
+ .network-frame {
91
+ @apply w-full;
92
+
93
+ .option {
94
+ @apply w-full;
95
+ }
96
+
97
+ .error {
98
+ .label-text {
99
+ @apply text-error;
100
+ }
101
+
102
+ input {
103
+ border-color: hsl(var(--er) / var(--tw-border-opacity));
104
+ --tw-border-opacity: 0.1;
105
+ --tw-bg-opacity: 1;
106
+ background-color: hsl(var(--er) / var(--tw-bg-opacity));
107
+ --tw-text-opacity: 1;
108
+ color: hsl(var(--erc, var(--nc)) / var(--tw-text-opacity));
109
+ }
110
+ }
111
+ }
112
+
113
+ .host-cache-frame {
114
+ .url {
115
+ word-wrap: break-word;
116
+ word-break: break-all;
117
+ white-space: normal;
118
+ }
119
+ }
120
+
121
+ .quick-save-frame {
122
+ .text-badge {
123
+ @apply absolute left-0 top-0 w-3 h-3 font-bold rounded-full flex items-center justify-center;
124
+ font-size: 0.5rem;
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,34 @@
1
+ import { useSelector } from "react-redux";
2
+ import { State } from "../store";
3
+ import { AccountFrame } from "./account-frame";
4
+ import { EditorConf } from "./editor/editor-conf-frame";
5
+ import { EditorFsFrame } from "./editor/editor-fs-frame";
6
+ import { HostCacheFrame } from "./host-cache-frame";
7
+ import { NetworkFrame } from "./network-frame";
8
+ import { QuickSaveFrame } from "./quick-save-frame";
9
+ import { SettingsFrame } from "./settings-frame";
10
+ import { StatsFrame } from "./stats-frame";
11
+ import { ImageRenderingFrame } from "./image-rendering-frame";
12
+
13
+ export function Frame(props: {}) {
14
+ const frame = useSelector((state: State) => state.ui.frame);
15
+ const frameXs = useSelector((state: State) => state.ui.frameXs);
16
+ const wideScreen = useSelector((state: State) => state.ui.wideScreen);
17
+ if (frame === "none") {
18
+ return null;
19
+ }
20
+
21
+
22
+ return <div class={"frame " + (frameXs ? " frame-xs " : "") +
23
+ (frameXs || wideScreen ? "" : " frame-md")}>
24
+ { frame === "account" && <AccountFrame /> }
25
+ { frame === "settings" && <SettingsFrame /> }
26
+ { frame === "editor-conf" && <EditorConf /> }
27
+ { frame === "editor-fs" && <EditorFsFrame /> }
28
+ { frame === "network" && <NetworkFrame /> }
29
+ { frame === "stats" && <StatsFrame /> }
30
+ { frame === "host-cache" && <HostCacheFrame /> }
31
+ { frame === "quick-save" && <QuickSaveFrame /> }
32
+ { frame === "image-rendering" && <ImageRenderingFrame /> }
33
+ </div>;
34
+ };
@@ -0,0 +1,124 @@
1
+ import { useEffect, useState } from "preact/hooks";
2
+ import { useDispatch } from "react-redux";
3
+ import { getChangesUrlPrefix } from "../v8/changes";
4
+ import { downloadArrayToFs, downloadUrlToFs } from "../download-file";
5
+ import { useT } from "../i18n";
6
+ import { loadBundleFromUrl } from "../load";
7
+ import { nonSerializableStore } from "../non-serializable-store";
8
+ import { dosSlice } from "../store/dos";
9
+ import { uiSlice } from "../store/ui";
10
+
11
+ export function HostCacheFrame() {
12
+ const [cached, _setCached] = useState<string[] | null>(null);
13
+ const [saves, setSaves] = useState<{ [url: string]: string }>({});
14
+ const t = useT();
15
+ const dispatch = useDispatch();
16
+
17
+ function setCached(cached: string[], owner: string) {
18
+ const changesPrefix = getChangesUrlPrefix(owner);
19
+ const saves: { [url: string]: string } = {};
20
+ cached = cached.filter((next) => {
21
+ if (next.startsWith(changesPrefix)) {
22
+ const gameUrl = decodeURIComponent(next.substring(next.lastIndexOf("/") + 1));
23
+ saves[gameUrl.substring(0, gameUrl.length - ".zip".length)] = next;
24
+ return false;
25
+ } else {
26
+ return true;
27
+ }
28
+ });
29
+ _setCached(cached);
30
+ setSaves(saves);
31
+ }
32
+
33
+ useEffect(() => {
34
+ const cache = nonSerializableStore.cache;
35
+ cache.keys()
36
+ .then((cached) => setCached(cached, cache.owner))
37
+ .catch(console.error);
38
+ }, []);
39
+
40
+ if (cached === null) {
41
+ return <div class="host-cache-frame frame-root items-center my-4">
42
+ <div class="radial-progress animate-spin" style="--value:70;"></div>
43
+ </div>;
44
+ }
45
+
46
+ async function loadBundle(url: string) {
47
+ dispatch(uiSlice.actions.frameNone());
48
+
49
+ let validUrl;
50
+ try {
51
+ validUrl = new URL(url);
52
+ } catch (e: any) {
53
+ console.error(e);
54
+ return;
55
+ }
56
+
57
+ try {
58
+ await loadBundleFromUrl(validUrl.toString(), dispatch);
59
+ } catch (e: any) {
60
+ dispatch(dosSlice.actions.bndError(e.message ?? "unexpected error"));
61
+ }
62
+ }
63
+
64
+ async function saveBundle(url: string) {
65
+ try {
66
+ const content = await nonSerializableStore.cache
67
+ .get(url);
68
+ const name = url.substring(url.lastIndexOf("/") + 1);
69
+ downloadArrayToFs(name.length === 0 ? "bundle.jsdos" : name, content);
70
+ } catch (e) {
71
+ // ignore
72
+ }
73
+ }
74
+
75
+ async function saveChanges(url: string, gameUrl: string) {
76
+ try {
77
+ const name = gameUrl.substring(url.lastIndexOf("/") + 1);
78
+ downloadUrlToFs((name.length === 0 ? "bundle.jsdos" : name) +
79
+ "chages.zip", url);
80
+ } catch (e) {
81
+ // ignore
82
+ }
83
+ }
84
+
85
+ function deleteBundle(url: string) {
86
+ nonSerializableStore.cache
87
+ .del(url).catch(console.error);
88
+
89
+ _setCached(cached!.filter((v) => v !== url));
90
+ }
91
+
92
+ return <div class="host-cache-frame frame-root items-start px-4">
93
+ <div class="w-full">
94
+ <table class="table table w-full">
95
+ <thead>
96
+ <tr>
97
+ <th>{t("stored")}</th>
98
+ <th></th>
99
+ </tr>
100
+ </thead>
101
+ <tbody>
102
+ {cached.map((url) => {
103
+ return <>
104
+ <tr>
105
+ <td class="url cursor-pointer underline hover:text-accent"
106
+ onClick={() => loadBundle(url)}>{url}</td>
107
+ <td >
108
+ {saves[url] !== undefined &&
109
+ <div class="cursor-pointer text-primary underline hover:text-accent"
110
+ onClick={() => saveChanges(saves[url], url)}>{t("changes")}</div>
111
+ }
112
+ <div class="cursor-pointer hover:text-accent"
113
+ onClick={() => saveBundle(url)}>{t("download")}</div>
114
+ <div class="cursor-pointer hover:text-accent"
115
+ onClick={() => deleteBundle(url)}>{t("delete")}</div>
116
+ </td>
117
+ </tr>
118
+ </>;
119
+ })}
120
+ </tbody>
121
+ </table>
122
+ </div>
123
+ </div>;
124
+ }
@@ -0,0 +1,8 @@
1
+ import { ImageRenderingSelect, RenderAspectSelect } from "../components/dos-option-select";
2
+
3
+ export function ImageRenderingFrame(props: {}) {
4
+ return <div class="image-rendering-frame frame-root items-start pl-4">
5
+ <RenderAspectSelect multiline={true} />
6
+ <ImageRenderingSelect multiline={true} />
7
+ </div>;
8
+ }