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
package/src/main.tsx ADDED
@@ -0,0 +1,195 @@
1
+ import { render } from "preact";
2
+ import "./index.css";
3
+
4
+ import { Provider } from "react-redux";
5
+ import { Ui } from "./ui";
6
+ import { dosSlice } from "./store/dos";
7
+ import { store } from "./store";
8
+ import { initEmulators } from "./store/dos";
9
+ // eslint-disable-next-line
10
+ import { uiSlice } from "./store/ui";
11
+ import { i18nSlice } from "./i18n";
12
+ import { nonSerializableStore, postJsDosEvent } from "./non-serializable-store";
13
+ import { getCache } from "./host/lcache";
14
+ import { loadBundleFromConfg, loadBundleFromUrl } from "./load";
15
+
16
+ import { DosOptions, DosProps, DosFn } from "./public/types";
17
+ import { browserSetFullScreen } from "./host/fullscreen";
18
+
19
+ let pollStep = "none";
20
+
21
+ async function pollEvents() {
22
+ const state = store.getState();
23
+ const step = state.dos.step;
24
+
25
+ if (step === pollStep) {
26
+ return;
27
+ }
28
+ pollStep = step;
29
+
30
+ switch (state.dos.step) {
31
+ case "emu-ready": {
32
+ const cachedEmail = state.auth.account?.email;
33
+ nonSerializableStore.cache = await getCache(cachedEmail ?? "guest");
34
+
35
+ if (nonSerializableStore.options.url) {
36
+ try {
37
+ await loadBundleFromUrl(nonSerializableStore.options.url, store.dispatch);
38
+ } catch (e: any) {
39
+ store.dispatch(dosSlice.actions.bndError(e.message));
40
+ }
41
+ } else if (nonSerializableStore.options.dosboxConf) {
42
+ loadBundleFromConfg({
43
+ dosboxConf: nonSerializableStore.options.dosboxConf,
44
+ jsdosConf: {
45
+ version: "8",
46
+ },
47
+ }, store.dispatch);
48
+ } else {
49
+ store.dispatch(uiSlice.actions.windowSelect());
50
+ }
51
+
52
+ postJsDosEvent("emu-ready");
53
+ } break;
54
+ };
55
+ }
56
+
57
+ store.subscribe(pollEvents);
58
+
59
+
60
+ let skipEmulatorsInit = false;
61
+ export const Dos: DosFn = (element: HTMLDivElement,
62
+ options: Partial<DosOptions> = {}): DosProps => {
63
+ nonSerializableStore.options = options;
64
+ setupRootElement(element);
65
+
66
+ if (!skipEmulatorsInit) {
67
+ skipEmulatorsInit = true;
68
+ initEmulators(store, options.pathPrefix ?? "https://v8.js-dos.com/latest/emulators/");
69
+ }
70
+
71
+ function setTheme(theme: DosOptions["theme"]) {
72
+ store.dispatch(uiSlice.actions.theme(theme));
73
+ }
74
+
75
+ function setLang(lang: DosOptions["lang"]) {
76
+ store.dispatch(i18nSlice.actions.setLang(lang));
77
+ }
78
+
79
+ function setBackend(backend: DosOptions["backend"]) {
80
+ store.dispatch(dosSlice.actions.dosBackend(backend));
81
+ }
82
+
83
+ function setBackendLocked(locked: boolean) {
84
+ store.dispatch(dosSlice.actions.dosBackendLocked(locked));
85
+ }
86
+
87
+ function setWorkerThread(workerThread: DosOptions["workerThread"]) {
88
+ store.dispatch(dosSlice.actions.dosWorker(workerThread));
89
+ }
90
+
91
+ function setMouseCapture(capture: DosOptions["mouseCapture"]) {
92
+ store.dispatch(dosSlice.actions.mouseCapture(capture));
93
+ }
94
+
95
+ function setServer(server: DosOptions["server"]) {
96
+ store.dispatch(dosSlice.actions.setServer(server));
97
+ }
98
+
99
+ function setRoom(room: DosOptions["room"]) {
100
+ store.dispatch(dosSlice.actions.setRoom(room));
101
+ }
102
+
103
+ function setFrame(frame: "network") {
104
+ store.dispatch(uiSlice.actions.frameNetwork());
105
+ }
106
+
107
+ function setBackground(background: string) {
108
+ store.dispatch(uiSlice.actions.background(background));
109
+ }
110
+
111
+ function setFullScreen(fullScreen: boolean) {
112
+ browserSetFullScreen(fullScreen, store.dispatch);
113
+ }
114
+
115
+ if (options.theme) {
116
+ setTheme(options.theme);
117
+ }
118
+
119
+ if (options.lang) {
120
+ setLang(options.lang);
121
+ }
122
+
123
+ if (options.backend) {
124
+ setBackend(options.backend);
125
+ }
126
+
127
+ if (options.backendLocked) {
128
+ setBackendLocked(options.backendLocked);
129
+ }
130
+
131
+ if (options.workerThread !== undefined) {
132
+ setWorkerThread(options.workerThread);
133
+ }
134
+
135
+ if (options.mouseCapture !== undefined) {
136
+ setMouseCapture(options.mouseCapture);
137
+ }
138
+
139
+ if (options.server) {
140
+ setServer(options.server);
141
+ }
142
+
143
+ if (options.room) {
144
+ setRoom(options.room);
145
+ }
146
+
147
+ if (options.background) {
148
+ setBackground(options.background);
149
+ }
150
+
151
+ if (options.fullScreen !== undefined) {
152
+ setFullScreen(options.fullScreen);
153
+ }
154
+
155
+ render(
156
+ <Provider store={store}>
157
+ {<Ui /> as any}
158
+ </Provider>,
159
+ element,
160
+ );
161
+
162
+ return {
163
+ setTheme,
164
+ setLang,
165
+ setBackend,
166
+ setBackendLocked,
167
+ setWorkerThread,
168
+ setMouseCapture,
169
+ setServer,
170
+ setRoom,
171
+ setFrame,
172
+ setBackground,
173
+ setFullScreen,
174
+ };
175
+ };
176
+
177
+ function setupRootElement(root: HTMLDivElement) {
178
+ nonSerializableStore.root = root;
179
+ root.classList.add("jsdos-rso");
180
+ root.addEventListener("contextmenu", (e) => {
181
+ if (e.target !== null &&
182
+ (e.target as HTMLElement).classList?.contains("contextmenu")) {
183
+ return;
184
+ }
185
+ e.stopPropagation();
186
+ e.preventDefault();
187
+ return false;
188
+ });
189
+ document.addEventListener("fullscreenchange", () => {
190
+ const fullscreen = document.fullscreenElement === root;
191
+ store.dispatch(uiSlice.actions.setFullScreen(fullscreen));
192
+ });
193
+ }
194
+
195
+ (window as any).Dos = Dos;
@@ -0,0 +1,35 @@
1
+ import { CommandInterface, InitFs } from "emulators";
2
+ import { Cache, CacheNoop } from "./host/lcache";
3
+ import { Dispatch } from "@reduxjs/toolkit";
4
+ import { DosOptions } from "./public/types";
5
+
6
+ export interface LoadedBundle {
7
+ bundleUrl: string | null,
8
+ bundleChangesUrl: string | null,
9
+ bundle: InitFs | null,
10
+ bundleChanges: Uint8Array | null,
11
+ }
12
+
13
+ export const nonSerializableStore: {
14
+ root: HTMLDivElement,
15
+ dispatch: Dispatch | null,
16
+ loadedBundle: LoadedBundle | null,
17
+ ci: CommandInterface | null,
18
+ cache: Cache,
19
+ options: Partial<DosOptions>,
20
+ } = {
21
+ root: null as any,
22
+ dispatch: null,
23
+ loadedBundle: null,
24
+ ci: null,
25
+ cache: new CacheNoop(),
26
+ options: {},
27
+ };
28
+
29
+ export const postJsDosEvent: DosOptions["onEvent"] = (event, ci) => {
30
+ if (nonSerializableStore.options.onEvent) {
31
+ setTimeout(() => {
32
+ nonSerializableStore.options.onEvent?.(event, ci);
33
+ }, 4);
34
+ }
35
+ };
@@ -0,0 +1,39 @@
1
+ export interface DosOptions {
2
+ url: string,
3
+ dosboxConf: string,
4
+ background: string,
5
+ pathPrefix: string,
6
+ theme: "light" | "dark" | "cupcake" | "bumblebee" | "emerald" | "corporate" |
7
+ "synthwave" | "retro" | "cyberpunk" | "valentine" | "halloween" | "garden" |
8
+ "forest" | "aqua" | "lofi" | "pastel" | "fantasy" | "wireframe" | "black" |
9
+ "luxury" | "dracula" | "cmyk" | "autumn" | "business" | "acid" | "lemonade" |
10
+ "night" | "coffee" | "winter",
11
+ lang: "ru" | "en",
12
+ backend: "dosbox" | "dosboxX",
13
+ backendLocked: boolean,
14
+ backendHardware: ((backend: "dosbox" | "dosboxX") => Promise<string | null>),
15
+ workerThread: boolean,
16
+ mouseCapture: boolean,
17
+ onEvent: (event: "emu-ready" | "ci-ready" | "bnd-play", ci?: any) => void,
18
+ server: "netherlands" | "newyork" | "singapore",
19
+ room: string,
20
+ fullScreen: boolean,
21
+ }
22
+
23
+ export interface DosProps {
24
+ setTheme(theme: DosOptions["theme"]): void;
25
+ setLang(lang: DosOptions["lang"]): void;
26
+ setBackend(backend: DosOptions["backend"]): void;
27
+ setBackendLocked(locked: boolean): void;
28
+ setWorkerThread(capture: DosOptions["workerThread"]): void;
29
+ setMouseCapture(capture: DosOptions["mouseCapture"]): void;
30
+ setServer(server: DosOptions["server"]): void;
31
+ setRoom(room: DosOptions["room"]): void;
32
+ setFrame(frame: "network"): void;
33
+ setBackground(background: string | null): void;
34
+ setFullScreen(fullScreen: boolean): void;
35
+ }
36
+
37
+ export type DosFn = (element: HTMLDivElement, options: Partial<DosOptions>) => DosProps;
38
+
39
+ // declare const Dos: DosFn;
@@ -0,0 +1,32 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { dispatchLoginAction } from "../store/ui";
3
+ import { State } from "../store";
4
+
5
+ export function AccountButton(props: {
6
+ class?: string,
7
+ }) {
8
+ const account = useSelector((state: State) => state.auth.account);
9
+ const loggedIn = account !== null;
10
+ const hightlight = useSelector((state: State) => state.ui.frame) === "account";
11
+ const dispatch = useDispatch();
12
+
13
+ function onClick() {
14
+ dispatchLoginAction(account, dispatch);
15
+ }
16
+
17
+ return <div class={"sidebar-button " + (hightlight ? "sidebar-highlight " : "") +
18
+ props.class} onClick={onClick}>
19
+
20
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none"
21
+ viewBox="0 0 24 24" stroke-width="1.5"
22
+ stroke="currentColor" class="w-full h-full">
23
+ <path stroke-linecap="round"
24
+ stroke-linejoin="round"
25
+ d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963
26
+ 0m11.963 0A8.966 8.966 0 0112 21a8.966
27
+ 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
28
+ </svg>
29
+
30
+ {loggedIn === false && <div class="sidebar-badge" />}
31
+ </div>;
32
+ }
@@ -0,0 +1,89 @@
1
+ import { useState } from "preact/hooks";
2
+ import { useDispatch, useSelector } from "react-redux";
3
+ import { LockBadge } from "../components/lock";
4
+ import { nonSerializableStore } from "../non-serializable-store";
5
+ import { State } from "../store";
6
+ import { uiSlice } from "../store/ui";
7
+ import { DisketteIcon } from "./diskette-icon";
8
+ import { useT } from "../i18n";
9
+ import { putChanges } from "../v8/changes";
10
+
11
+ export function CloudSaveButton(props: {
12
+ class?: string,
13
+ }) {
14
+ const [busy, setBusy] = useState<boolean>(false);
15
+ const account = useSelector((state: State) => state.auth.account);
16
+ const disabled = account === null;
17
+ const dispatch = useDispatch();
18
+ const t = useT();
19
+ const cloudSaves = useSelector((state: State) => state.ui.cloudSaves);
20
+
21
+ if (!cloudSaves ||
22
+ nonSerializableStore.loadedBundle === null ||
23
+ nonSerializableStore.loadedBundle.bundleChangesUrl === null) {
24
+ return null;
25
+ }
26
+
27
+ async function onClick() {
28
+ if (disabled) {
29
+ dispatch(uiSlice.actions.modalLogin());
30
+ return;
31
+ }
32
+
33
+ if (busy) {
34
+ return;
35
+ }
36
+
37
+ const ci = nonSerializableStore.ci;
38
+ const changesUrl = nonSerializableStore.loadedBundle?.bundleChangesUrl;
39
+
40
+ if (ci === null || !changesUrl) {
41
+ return;
42
+ }
43
+
44
+ setBusy(true);
45
+ try {
46
+ const changes = await ci.persist(true);
47
+ if (changes !== null) {
48
+ if (account === null || !account.premium) {
49
+ await nonSerializableStore.cache.put(changesUrl, changes);
50
+ } else {
51
+ await Promise.all([
52
+ putChanges(changesUrl, changes),
53
+ nonSerializableStore.cache.put(changesUrl, changes),
54
+ ]);
55
+ }
56
+ }
57
+
58
+ dispatch(uiSlice.actions.showToast({
59
+ message: t("success"),
60
+ intent: "success",
61
+ }));
62
+ } catch (e: any) {
63
+ dispatch(uiSlice.actions.showToast({
64
+ message: t("unable_to_save"),
65
+ intent: "error",
66
+ }));
67
+ console.error(e);
68
+ } finally {
69
+ setBusy(false);
70
+ }
71
+ }
72
+
73
+ return <div class={"save-button sidebar-button overflow-hidden " +
74
+ (busy ? " sidebar-highlight " : "") + props.class +
75
+ (disabled ? " opacity-50" : "")} onClick={onClick}>
76
+ <div class="w-full h-full flex justify-center">
77
+ <DisketteIcon />
78
+ {busy && <div class="sidebar-badge" />}
79
+ {disabled && <LockBadge />}
80
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
81
+ stroke-width="1.5" stroke="currentColor"
82
+ class="absolute bottom-1 w-3 h-3 text-primary-content">
83
+ <path stroke-linecap="round" stroke-linejoin="round"
84
+ d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775
85
+ 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" />
86
+ </svg>
87
+ </div>
88
+ </div >;
89
+ }
@@ -0,0 +1,14 @@
1
+ export function DisketteIcon(props: {
2
+ class?: string,
3
+ }) {
4
+ return <svg xmlns="http://www.w3.org/2000/svg"
5
+ style="padding: 2px"
6
+ viewBox="0 0 16 16" enable-background="new 0 0 16 16"
7
+ fill="currentColor" class={ "w-full h-full rounded-lg " + props.class }>
8
+ <path fill-rule="evenodd" clip-rule="evenodd"
9
+ d="M15.71,2.29l-2-2C13.53,0.11,13.28,0,13,0h-1v6H4V0H1C0.45,0,0,0.45,0,1v14
10
+ c0,0.55,0.45,1,1,1h14c0.55,0,1-0.45,1-1V3C16,2.72,15.89,2.47,15.71,2.29z
11
+ M14,15H2V9c0-0.55,0.45-1,1-1h10c0.55,0,1,0.45,1,1V15
12
+ z M11,1H9v4h2V1z"/>
13
+ </svg>;
14
+ }
@@ -0,0 +1,44 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { State } from "../store";
3
+ import { browserSetFullScreen } from "../host/fullscreen";
4
+
5
+ export function FullscreenButton(props: {
6
+ class?: string,
7
+ }) {
8
+ const fullScreen = useSelector((state: State) => state.ui.fullScreen);
9
+ const dispatch = useDispatch();
10
+
11
+ function onClick() {
12
+ browserSetFullScreen(!fullScreen, dispatch);
13
+ }
14
+ /* eslint-disable max-len */
15
+ return <div class={"fullscreen-button sidebar-button " + props.class} onClick={onClick}>
16
+ <div class={"w-full h-full scale-75 hover:scale-90"}>
17
+ { !fullScreen &&
18
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
19
+ viewBox="0 0 16 16" fill="currentColor" stroke="none" enable-background="new 0 0 16 16" >
20
+ <g>
21
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M5.99,8.99c-0.28,0-0.53,0.11-0.71,0.29l-3.29,3.29v-1.59c0-0.55-0.45-1-1-1
22
+ s-1,0.45-1,1v4c0,0.55,0.45,1,1,1h4c0.55,0,1-0.45,1-1s-0.45-1-1-1H3.41L6.7,10.7c0.18-0.18,0.29-0.43,0.29-0.71
23
+ C6.99,9.44,6.54,8.99,5.99,8.99z M14.99-0.01h-4c-0.55,0-1,0.45-1,1s0.45,1,1,1h1.59L9.28,5.29C9.1,5.47,8.99,5.72,8.99,5.99
24
+ c0,0.55,0.45,1,1,1c0.28,0,0.53-0.11,0.71-0.29l3.29-3.29v1.59c0,0.55,0.45,1,1,1s1-0.45,1-1v-4C15.99,0.44,15.54-0.01,14.99-0.01
25
+ z"/>
26
+ </g>
27
+ </svg>
28
+ }
29
+ { fullScreen &&
30
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
31
+ viewBox="0 0 16 16" fill="currentColor" stroke="none" enable-background="new 0 0 16 16">
32
+ <g>
33
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M15.99,0.99c0-0.55-0.45-1-1-1c-0.28,0-0.53,0.11-0.71,0.29l-3.29,3.29V1.99
34
+ c0-0.55-0.45-1-1-1s-1,0.45-1,1v4c0,0.55,0.45,1,1,1h4c0.55,0,1-0.45,1-1s-0.45-1-1-1h-1.59L15.7,1.7
35
+ C15.88,1.52,15.99,1.27,15.99,0.99z M5.99,8.99h-4c-0.55,0-1,0.45-1,1s0.45,1,1,1h1.59l-3.29,3.29c-0.18,0.18-0.29,0.43-0.29,0.71
36
+ c0,0.55,0.45,1,1,1c0.28,0,0.53-0.11,0.71-0.29l3.29-3.29v1.59c0,0.55,0.45,1,1,1s1-0.45,1-1v-4C6.99,9.44,6.54,8.99,5.99,8.99z"
37
+ />
38
+ </g>
39
+ </svg>
40
+ }
41
+ </div>
42
+ </div >;
43
+ /* eslint-enable max-len */
44
+ }
@@ -0,0 +1,39 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { State } from "../store";
3
+ import { uiSlice } from "../store/ui";
4
+
5
+ export function NetworkButton(props: {
6
+ class?: string,
7
+ }) {
8
+ const hightlight = useSelector((state: State) => state.ui.frame) === "network";
9
+ const inactive = useSelector((state: State) => state.dos.network.ipx !== "connected");
10
+ const dispatch = useDispatch();
11
+
12
+ function onClick() {
13
+ if (hightlight) {
14
+ dispatch(uiSlice.actions.frameNone());
15
+ } else {
16
+ dispatch(uiSlice.actions.frameNetwork());
17
+ }
18
+ }
19
+
20
+ return <div class={"network-button sidebar-button " +
21
+ (hightlight ? " sidebar-highlight " : "") +
22
+ (inactive ? " inactive " : "") +
23
+ props.class} onClick={onClick}>
24
+ <div class="w-full h-full">
25
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
26
+ stroke-width="1.5" stroke="currentColor" class="w-full h-full">
27
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.288 15.038a5.25 5.25 0
28
+ 017.424 0M5.106 11.856c3.807-3.808 9.98-3.808 13.788 0M1.924 8.674c5.565-5.565
29
+ 14.587-5.565 20.152 0M12.53 18.22l-.53.53-.53-.53a.75.75 0 011.06 0z" />
30
+ </svg>
31
+ { inactive && <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
32
+ stroke="currentColor" class="w-full text-error h-full absolute top-0 left-0">
33
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 4L18 18" />
34
+ </svg> }
35
+ </div>
36
+
37
+
38
+ </div >;
39
+ }