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,108 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { useT } from "../i18n";
3
+ import { State } from "../store";
4
+ import { Select } from "./select";
5
+ import { AnyAction } from "@reduxjs/toolkit";
6
+ import {
7
+ Backend, BackendValues, dosSlice, ImageRendering, ImageRenderingValues, RenderAspect, RenderAspectValues,
8
+ RenderBackend, RenderBackendValues,
9
+ } from "../store/dos";
10
+ import { ThemeValues, Theme, uiSlice } from "../store/ui";
11
+ import { lStorage } from "../host/lstorage";
12
+ import { SockdriveBackendName, initSlice, sockdriveBackendNames } from "../store/init";
13
+
14
+ export function BackendSelect() {
15
+ const locked = useSelector((state: State) => state.dos.backendLocked);
16
+ return <OptionSelect
17
+ label="emulation_backend"
18
+ values={[...BackendValues]}
19
+ disabled={locked}
20
+ selector={(state: State) => state.dos.backend}
21
+ dispatch={(newValue: Backend) => {
22
+ lStorage.setItem("backend", newValue);
23
+ return dosSlice.actions.dosBackend(newValue);
24
+ }}
25
+ />;
26
+ }
27
+
28
+ export function RenderSelect() {
29
+ const disabled = useSelector((state: State) => state.ui.window) === "run";
30
+ return <OptionSelect
31
+ label="render_backend"
32
+ values={[...RenderBackendValues]}
33
+ disabled={disabled}
34
+ selector={(state: State) => state.dos.renderBackend}
35
+ dispatch={(newValue: RenderBackend) => dosSlice.actions.renderBackend(newValue)}
36
+ />;
37
+ }
38
+
39
+ export function RenderAspectSelect(props: { multiline?: boolean }) {
40
+ return <OptionSelect
41
+ label="render_aspect"
42
+ multiline={props.multiline}
43
+ values={[...RenderAspectValues]}
44
+ selector={(state: State) => state.dos.renderAspect}
45
+ dispatch={(newValue: RenderAspect) => dosSlice.actions.renderAspect(newValue)}
46
+ />;
47
+ }
48
+
49
+ export function ImageRenderingSelect(props: { multiline?: boolean }) {
50
+ return <OptionSelect
51
+ label="image_rendering"
52
+ multiline={props.multiline}
53
+ values={[...ImageRenderingValues]}
54
+ selector={(state: State) => state.dos.imageRendering}
55
+ dispatch={(newValue: ImageRendering) => dosSlice.actions.imageRendering(newValue)}
56
+ />;
57
+ }
58
+
59
+ export function ThemeSelect(props: { class?: string, multiline?: boolean }) {
60
+ return <OptionSelect
61
+ class={props.class}
62
+ label="theme"
63
+ values={[...ThemeValues]}
64
+ selector={(state: State) => state.ui.theme}
65
+ dispatch={(newValue: Theme) => uiSlice.actions.theme(newValue)}
66
+ multiline={props.multiline}
67
+ />;
68
+ }
69
+
70
+ export function SockdriveBackend(props: { class?: string, multiline?: boolean }) {
71
+ return <OptionSelect<SockdriveBackendName>
72
+ class={props.class}
73
+ label="FAT16/32 Backend"
74
+ values={sockdriveBackendNames}
75
+ selector={(state: State) => state.init.sockdriveBackendName}
76
+ dispatch={(newValue) => initSlice.actions.setSockdriveBackendName(newValue)}
77
+ multiline={props.multiline}
78
+ />;
79
+ }
80
+
81
+ function OptionSelect<T>(props: {
82
+ class?: string,
83
+ selectClass?: string,
84
+ label: string,
85
+ values: string[]
86
+ selector: (state: State) => T,
87
+ dispatch: (newValue: T) => AnyAction;
88
+ disabled?: boolean,
89
+ multiline?: boolean,
90
+ }) {
91
+ const t = useT();
92
+ const value = useSelector(props.selector);
93
+ const dispatch = useDispatch();
94
+
95
+ function onBackend(newValue: T) {
96
+ dispatch(props.dispatch(newValue));
97
+ }
98
+ return <Select
99
+ class={"mt-4 " + props.class}
100
+ selectClass={props.selectClass}
101
+ disabled={props.disabled}
102
+ multiline={props.multiline}
103
+ label={t(props.label)}
104
+ selected={value as string}
105
+ values={props.values}
106
+ onSelect={onBackend as any} />;
107
+ }
108
+
@@ -0,0 +1,35 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { useT } from "../i18n";
3
+ import { dosSlice } from "../store/dos";
4
+ import { Slider } from "./slider";
5
+ import { State } from "../store";
6
+
7
+ export function MouseSensitiviySlider(props: {
8
+ class?: string,
9
+ }) {
10
+ const t = useT();
11
+ const sensitivity = useSelector((state: State) => state.dos.mouseSensitivity);
12
+ const dispatch = useDispatch();
13
+
14
+ return <Slider
15
+ class={"mt-4 " + props.class}
16
+ label={t("mouse_sensitivity")}
17
+ value={sensitivity}
18
+ onChange={(value) => dispatch(dosSlice.actions.mouseSensitivity(value)) }
19
+ />;
20
+ }
21
+
22
+ export function VolumeSlider(props: {
23
+ class?: string,
24
+ }) {
25
+ const t = useT();
26
+ const volume = useSelector((state: State) => state.dos.volume);
27
+ const dispatch = useDispatch();
28
+
29
+ return <Slider
30
+ class={props.class}
31
+ label={t("volume")}
32
+ value={volume}
33
+ onChange={(value) => dispatch(dosSlice.actions.volume(value)) }
34
+ />;
35
+ }
@@ -0,0 +1,16 @@
1
+ import { useT } from "../i18n";
2
+
3
+ export function Error(props: {
4
+ error?: string | null,
5
+ onSkip?: () => void,
6
+ }) {
7
+ const t = useT();
8
+ const error = props.error ?? "Unexpected error";
9
+
10
+ return <div class="flex-grow flex flex-col items-center justify-center px-8 m-auto">
11
+ <div class="text-2xl text-center">{t("error")}</div>
12
+ <div class="text-red-400 mt-8 text-2xl text-center">"{error}"</div>
13
+ <div class="mt-8 text-sm text-center">{t("consult_logs")}</div>
14
+ { props.onSkip && <button class="mt-8 btn-primary" onClick={props.onSkip}>{t("skip")}</button> }
15
+ </div>;
16
+ }
@@ -0,0 +1,26 @@
1
+ export function Loading(props: {
2
+ head: string,
3
+ message: string,
4
+ }) {
5
+ const { head, message } = props;
6
+
7
+ return <div class="flex flex-col items-center justify-center frame-color px-8">
8
+ <div class="text-2xl text-center">{head}</div>
9
+ <div class="mt-2 text-center">{message}</div>
10
+ </div>;
11
+ }
12
+
13
+ export function formatSize(size: number) {
14
+ if (size < 1024) {
15
+ return size + "b";
16
+ }
17
+
18
+ size /= 1024;
19
+
20
+ if (size < 1024) {
21
+ return Math.round(size) + "kb";
22
+ }
23
+
24
+ size /= 1024;
25
+ return Math.round(size * 10) / 10 + "mb";
26
+ }
@@ -0,0 +1,10 @@
1
+ export function LockBadge(props: {
2
+ class?: string,
3
+ }) {
4
+ return <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
5
+ class={ props.class ?? "absolute left-0 top-0 -ml-1 -mt-1 text-error w-4 h-4" }>
6
+ <path fill-rule="evenodd" d="M12 1.5a5.25 5.25 0 00-5.25 5.25v3a3 3 0 00-3 3v6.75a3 3 0 003
7
+ 3h10.5a3 3 0 003-3v-6.75a3 3 0 00-3-3v-3c0-2.9-2.35-5.25-5.25-5.25zm3.75
8
+ 8.25v-3a3.75 3.75 0 10-7.5 0v3h7.5z" clip-rule="evenodd" />
9
+ </svg>;
10
+ }
@@ -0,0 +1,33 @@
1
+ import { useT } from "../i18n";
2
+
3
+ export function Select(props: {
4
+ class?: string,
5
+ selectClass?: string,
6
+ label: string,
7
+ selected: string,
8
+ values: string[],
9
+ onSelect?: (value: string) => void,
10
+ disabled?: boolean,
11
+ multiline?: boolean,
12
+ }) {
13
+ const t = useT();
14
+ const multiline = props.multiline === true;
15
+ function onSelect(e: any) {
16
+ if (props.onSelect !== undefined) {
17
+ props.onSelect(e.currentTarget.value);
18
+ }
19
+ }
20
+ return <div class={props.class + " option flex " +
21
+ (multiline ? "flex-col" : "flex-row items-center")}>
22
+ <div class={ multiline ? "mb-2" : "mr-4" }>{props.label}</div>
23
+ <div class="flex-grow">
24
+ <select class={ props.selectClass ? props.selectClass :
25
+ (multiline ? "w-full" : "w-28") }
26
+ onChange={onSelect} disabled={props.disabled === true}>
27
+ {props.values.map((v) => {
28
+ return <option selected={v === props.selected} value={v}>{t(v)}</option>;
29
+ })}
30
+ </select>
31
+ </div>
32
+ </div>;
33
+ }
@@ -0,0 +1,69 @@
1
+ import { useEffect, useRef } from "preact/hooks";
2
+
3
+ export function Slider(props: {
4
+ class?: string,
5
+ label: string,
6
+ value: number,
7
+ onChange: (value: number) => void,
8
+ }) {
9
+ const sliderRef = useRef<HTMLDivElement>(null);
10
+ const pointRef = useRef<HTMLDivElement>(null);
11
+
12
+ useEffect(() => {
13
+ if (sliderRef?.current === null) {
14
+ return;
15
+ }
16
+
17
+ const sliderEl = sliderRef.current;
18
+
19
+ let pressed = false;
20
+ function updatePercent(e: PointerEvent) {
21
+ const boundingRect = sliderEl.getBoundingClientRect();
22
+ const percent = Math.min(1, Math.max(0,
23
+ (e.clientX - boundingRect.left) / boundingRect.width));
24
+ props.onChange(percent);
25
+ }
26
+
27
+ function onPointerDown(e: PointerEvent) {
28
+ pressed = true;
29
+ updatePercent(e);
30
+ }
31
+ function onPointerMove(e: PointerEvent) {
32
+ if (!pressed) {
33
+ return;
34
+ }
35
+
36
+ updatePercent(e);
37
+ }
38
+
39
+ function onPointerUp(e: PointerEvent) {
40
+ pressed = false;
41
+ }
42
+
43
+
44
+ sliderEl.addEventListener("pointerdown", onPointerDown);
45
+ sliderEl.addEventListener("pointermove", onPointerMove);
46
+ sliderEl.addEventListener("pointerup", onPointerUp);
47
+ sliderEl.addEventListener("pointercancel", onPointerUp);
48
+ sliderEl.addEventListener("pointerleave", onPointerUp);
49
+
50
+ return () => {
51
+ sliderEl.removeEventListener("pointerdown", onPointerDown);
52
+ sliderEl.removeEventListener("pointermove", onPointerMove);
53
+ sliderEl.removeEventListener("pointerup", onPointerUp);
54
+ sliderEl.removeEventListener("pointercancel", onPointerUp);
55
+ sliderEl.removeEventListener("pointerleave", onPointerUp);
56
+ };
57
+ }, [sliderRef]);
58
+
59
+ const percent = Math.min(Math.max(0, props.value * 100), 100);
60
+
61
+ return <div class={props.class + " slider option"}>
62
+ <div class="label">{props.label}</div>
63
+ <div class="touch" ref={sliderRef}>
64
+ <div class="bg"></div>
65
+ <div class="point" ref={pointRef}
66
+ style={{ left: "calc(" + percent + "%" + " - 12px)" }}></div>
67
+ </div>
68
+ </div>;
69
+ }
@@ -0,0 +1,18 @@
1
+ export function downloadUrlToFs(fileName: string, url: string, targetBlank = true) {
2
+ const a = document.createElement("a");
3
+ a.href = url;
4
+ a.target = targetBlank ? "_blank" : "_self";
5
+ a.download = fileName;
6
+ a.style.display = "none";
7
+ document.body.appendChild(a);
8
+
9
+ a.click();
10
+ a.remove();
11
+ }
12
+
13
+ export function downloadArrayToFs(fileName: string, data: Uint8Array) {
14
+ const blob = new Blob([data], {
15
+ type: "application/zip",
16
+ });
17
+ downloadUrlToFs(fileName, URL.createObjectURL(blob));
18
+ }
@@ -0,0 +1,156 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { useState } from "preact/hooks";
3
+ import { uiSlice } from "../store/ui";
4
+ import { authSlice } from "../store/auth";
5
+ import { useT } from "../i18n";
6
+ import { State } from "../store";
7
+ import { linkToBuy, cancle as cancelSubscription } from "../v8/subscriptions";
8
+ import { cancelSubscriptionPage } from "../v8/config";
9
+
10
+ export function AccountFrame(props: {}) {
11
+ const t = useT();
12
+ const account = useSelector((state: State) => state.auth.account);
13
+ const dispatch = useDispatch();
14
+
15
+ if (account === null) {
16
+ return null;
17
+ }
18
+
19
+ function logout() {
20
+ dispatch(authSlice.actions.logout({}));
21
+ dispatch(uiSlice.actions.frameNone());
22
+ }
23
+
24
+ return <div class="account-frame frame-root items-center">
25
+ {account.picture !== null &&
26
+ <img class="account-img" src={account.picture} />}
27
+ <div>{account.name ?? account.email}</div>
28
+ {account.name !== null && <div class="account-email">{account.email}</div>}
29
+ <button class="btn mt-8" onClick={logout}>{t("logout")}</button>
30
+
31
+ <div class="mt-8 mb-4 text-2xl">{t("features")}</div>
32
+ <PremiumPlan />
33
+ </div>;
34
+ }
35
+
36
+ function PremiumPlan(props: {}) {
37
+ const t = useT();
38
+ const lang = useSelector((state: State) => state.i18n.lang);
39
+ const account = useSelector((state: State) => state.auth.account);
40
+ const [locked, setLocked] = useState<boolean>(false);
41
+ const dispatch = useDispatch();
42
+
43
+ if (account === null) {
44
+ return null;
45
+ }
46
+
47
+ const token = account.token.access_token;
48
+ function onBuy() {
49
+ if (locked) {
50
+ return;
51
+ }
52
+
53
+ setLocked(true);
54
+ linkToBuy(token)
55
+ .then((link) => {
56
+ if (link !== null) {
57
+ window.open(link, "_blank");
58
+ }
59
+ })
60
+ .catch(console.error)
61
+ .finally(() => setLocked(false));
62
+ }
63
+
64
+ async function cancle() {
65
+ const openPage = () => {
66
+ dispatch(uiSlice.actions.showToast({
67
+ message: t("unable_to_cancle_subscription"),
68
+ intent: "error",
69
+ }));
70
+ window.open(cancelSubscriptionPage[lang] ?? cancelSubscriptionPage.en, "_blank");
71
+ };
72
+ if (!account) {
73
+ openPage();
74
+ } else {
75
+ try {
76
+ if (await cancelSubscription(account.token.access_token)) {
77
+ dispatch(uiSlice.actions.showToast({
78
+ message: t("subscription_cancelled"),
79
+ intent: "success",
80
+ }));
81
+ } else {
82
+ openPage();
83
+ }
84
+ } catch (e) {
85
+ openPage();
86
+ }
87
+ }
88
+ }
89
+
90
+ return <div class={"premium-plan-root " + (account.premium ? "have-premium" : "")}>
91
+ <div class="premium-plan-head flex">
92
+ <PremiumCheck />
93
+ {t("premium")}
94
+ <div class="flex-grow"></div>
95
+ {account.premium &&
96
+ <button
97
+ onClick={cancle}
98
+ class="ml-2 btn btn-ghost btn-xs text-success-content">
99
+ {t("cancle")}
100
+ </button>}
101
+ </div>
102
+ {!account.premium &&
103
+ <>
104
+ <div class="flex flex-row my-6 items-top">
105
+ <div class="premium-plan-cost">
106
+ $3
107
+ </div>
108
+ <div class="premium-plan-cost-expl">
109
+ <div>USD / mo</div>
110
+ <div><span class="text-blue-600">5</span> {t("try_free")}</div>
111
+ </div>
112
+ </div>
113
+ <button class={"btn btn-primary w-full " + (locked ? "hidden" : "")} onClick={onBuy}>
114
+ {t("buy")}
115
+ </button>
116
+ </>
117
+ }
118
+ <div class="flex flex-col mt-4">
119
+ <div class="premium-plan-highlight">
120
+ <PremiumCheck />
121
+ <div>{t("cloud_saves")}</div>
122
+ </div>
123
+ <div class="premium-plan-highlight">
124
+ <PremiumCheck />
125
+ <div>{t("experimental_features")}</div>
126
+ </div>
127
+ <div class="premium-plan-highlight">
128
+ <PremiumCheck />
129
+ <div>{t("writeable_fat32")}</div>
130
+ <div class="flex-grow"></div>
131
+ <button
132
+ onClick={() => dispatch(uiSlice.actions.frameFatDrives())}
133
+ target="_blank"
134
+ class="ml-2 btn btn-xs text-success-content">
135
+ {t("manage")}
136
+ </button>
137
+ </div>
138
+ <div class="premium-plan-highlight">
139
+ <PremiumCheck />
140
+ <div>{t("net_no_limits")}</div>
141
+ </div>
142
+ <div class="premium-plan-highlight" style="border-bottom: 0px">
143
+ <PremiumCheck />
144
+ <div>{t("unlock_options")}</div>
145
+ </div>
146
+ </div>
147
+ </div>;
148
+ }
149
+
150
+ function PremiumCheck(props: {}) {
151
+ return <svg xmlns="http://www.w3.org/2000/svg"
152
+ fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
153
+ class="mr-4 w-6 h-6 text-blue-500 flex-shrink-0">
154
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
155
+ </svg>;
156
+ }