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.
- package/.github/workflows/build.yml +51 -0
- package/.gitmodules +4 -0
- package/README.deployment.md +11 -0
- package/README.md +29 -89
- package/index.html +106 -0
- package/package.json +46 -71
- package/postcss.config.cjs +8 -0
- package/src/app.css +14 -0
- package/src/base.css +10 -0
- package/src/components/checkbox.tsx +41 -0
- package/src/components/close-button.tsx +17 -0
- package/src/components/components.css +28 -0
- package/src/components/dos-option-checkbox.tsx +94 -0
- package/src/components/dos-option-select.tsx +108 -0
- package/src/components/dos-option-slider.tsx +35 -0
- package/src/components/error.tsx +16 -0
- package/src/components/loading.tsx +26 -0
- package/src/components/lock.tsx +10 -0
- package/src/components/select.tsx +33 -0
- package/src/components/slider.tsx +69 -0
- package/src/download-file.ts +18 -0
- package/src/frame/account-frame.tsx +156 -0
- package/src/frame/editor/defaults.ts +419 -0
- package/src/frame/editor/editor-conf-frame.tsx +226 -0
- package/src/frame/editor/editor-frame.css +55 -0
- package/src/frame/editor/editor-fs-frame.tsx +314 -0
- package/src/frame/fat-drives-frame.tsx +135 -0
- package/src/frame/frame.css +119 -0
- package/src/frame/frame.tsx +34 -0
- package/src/frame/image-rendering-frame.tsx +8 -0
- package/src/frame/network-frame.tsx +157 -0
- package/src/frame/quick-save-frame.tsx +96 -0
- package/src/frame/settings-frame.tsx +14 -0
- package/src/frame/stats-frame.tsx +87 -0
- package/src/host/bundle-storage.ts +91 -0
- package/src/host/fullscreen.ts +35 -0
- package/src/host/lcache.ts +203 -0
- package/src/host/lstorage.ts +156 -0
- package/src/i18n.ts +179 -0
- package/src/index.css +7 -0
- package/src/load.ts +132 -0
- package/src/login/login.css +9 -0
- package/src/login/login.tsx +77 -0
- package/src/main.tsx +197 -0
- package/src/public/types.ts +41 -0
- package/src/sidebar/account-button.tsx +33 -0
- package/src/sidebar/cloud-save-button.tsx +89 -0
- package/src/sidebar/diskette-icon.tsx +14 -0
- package/src/sidebar/fullscreen-button.tsx +44 -0
- package/src/sidebar/network-button.tsx +39 -0
- package/src/sidebar/sidebar-button.tsx +244 -0
- package/src/sidebar/sidebar.css +47 -0
- package/src/sidebar/sidebar.tsx +30 -0
- package/src/sockdrive/LICENSE +674 -0
- package/src/sockdrive/README.md +45 -0
- package/src/sockdrive/include/sockdrive.h +23 -0
- package/src/sockdrive/js/.eslintrc.json +43 -0
- package/src/sockdrive/js/package.json +29 -0
- package/src/sockdrive/js/src/fatfs/chains.js +318 -0
- package/src/sockdrive/js/src/fatfs/dir.js +385 -0
- package/src/sockdrive/js/src/fatfs/helpers.js +210 -0
- package/src/sockdrive/js/src/fatfs/index.js +606 -0
- package/src/sockdrive/js/src/fatfs/structs.js +255 -0
- package/src/sockdrive/js/src/fatfs/vol.js +166 -0
- package/src/sockdrive/js/src/sockdrive/cache.ts +133 -0
- package/src/sockdrive/js/src/sockdrive/drive.ts +457 -0
- package/src/sockdrive/js/src/sockdrive/lru.ts +221 -0
- package/src/sockdrive/js/src/sockdrive/types.ts +14 -0
- package/src/sockdrive/js/src/sockdrive-fat.d.ts +64 -0
- package/src/sockdrive/js/src/sockdrive-fat.ts +226 -0
- package/src/sockdrive/js/src/sockdrive-native.ts +100 -0
- package/src/sockdrive/js/src/test/test.ts +288 -0
- package/src/sockdrive/js/tsconfig.json +11 -0
- package/src/sockdrive/js/webpack.config.js +68 -0
- package/src/sockdrive/js/yarn.lock +1808 -0
- package/src/store/auth.ts +115 -0
- package/src/store/dos.ts +305 -0
- package/src/store/editor.ts +61 -0
- package/src/store/init.ts +48 -0
- package/src/store/storage.ts +32 -0
- package/src/store/ui.ts +186 -0
- package/src/store.tsx +108 -0
- package/src/ui.tsx +104 -0
- package/src/v8/changes.ts +56 -0
- package/src/v8/config.ts +18 -0
- package/src/v8/subscriptions.ts +62 -0
- package/src/vite-env.d.ts +2 -0
- package/src/window/dos/controls/keyboard.ts +32 -0
- package/src/window/dos/controls/keys.ts +331 -0
- package/src/window/dos/controls/mouse/mount.ts +99 -0
- package/src/window/dos/controls/mouse/mouse-locked.ts +51 -0
- package/src/window/dos/controls/mouse/mouse-not-locked.ts +84 -0
- package/src/window/dos/controls/mouse/mouse-swipe.ts +57 -0
- package/src/window/dos/controls/mouse/pointer.ts +85 -0
- package/src/window/dos/controls/mouse.ts +21 -0
- package/src/window/dos/dos-dhry2.tsx +191 -0
- package/src/window/dos/dos-runtime.tsx +214 -0
- package/src/window/dos/dos-window.tsx +84 -0
- package/src/window/dos/render/canvas.ts +65 -0
- package/src/window/dos/render/resize.ts +31 -0
- package/src/window/dos/render/webgl.ts +173 -0
- package/src/window/dos/sound/audio-node.ts +148 -0
- package/src/window/error-window.tsx +9 -0
- package/src/window/loading-window.tsx +54 -0
- package/src/window/prerun-window.tsx +123 -0
- package/src/window/select-window.tsx +118 -0
- package/src/window/window.css +78 -0
- package/src/window/window.tsx +49 -0
- package/src/ws/ws-sockdrive.ts +101 -0
- package/src/ws/ws-transport-layer.ts +455 -0
- package/tailwind.config.cjs +48 -0
- package/tsconfig.json +32 -0
- package/tsconfig.node.json +9 -0
- package/vite.config.ts +22 -0
- package/LICENSE +0 -339
- package/dist/emulators-ui-loader.png +0 -0
- package/dist/js-dos.css +0 -1690
- package/dist/js-dos.js +0 -17
- package/dist/js-dos.js.map +0 -1
- package/dist/types/src/backend/v7/latency.d.ts +0 -8
- package/dist/types/src/backend/v7/personal.d.ts +0 -2
- package/dist/types/src/backend/v7/v7-config.d.ts +0 -16
- package/dist/types/src/components/action-bar.d.ts +0 -2
- package/dist/types/src/components/action-hide.d.ts +0 -4
- package/dist/types/src/components/action-save-or-exit.d.ts +0 -4
- package/dist/types/src/components/client.d.ts +0 -6
- package/dist/types/src/components/controls.d.ts +0 -8
- package/dist/types/src/components/cpu-control.d.ts +0 -2
- package/dist/types/src/components/horizontal-slider.d.ts +0 -13
- package/dist/types/src/components/region.d.ts +0 -6
- package/dist/types/src/components/scale-control.d.ts +0 -3
- package/dist/types/src/components/sensitivity-control.d.ts +0 -3
- package/dist/types/src/components/sidebar/latency-info.d.ts +0 -7
- package/dist/types/src/components/sidebar/main.d.ts +0 -2
- package/dist/types/src/components/sidebar/networking.d.ts +0 -2
- package/dist/types/src/components/sidebar/token/token-add-time.d.ts +0 -2
- package/dist/types/src/components/sidebar/token/token-select.d.ts +0 -2
- package/dist/types/src/components/sidebar/token/token.d.ts +0 -17
- package/dist/types/src/components/sidebar.d.ts +0 -2
- package/dist/types/src/components/sync-control.d.ts +0 -2
- package/dist/types/src/components/tip.d.ts +0 -2
- package/dist/types/src/components/vertical-slider.d.ts +0 -12
- package/dist/types/src/components/volume-control.d.ts +0 -3
- package/dist/types/src/dom.d.ts +0 -8
- package/dist/types/src/hardware-transport-layer.d.ts +0 -18
- package/dist/types/src/icons.d.ts +0 -95
- package/dist/types/src/player-app.d.ts +0 -50
- package/dist/types/src/player.d.ts +0 -29
- package/dist/types/src/request.d.ts +0 -1
- package/dist/types/src/xhr.d.ts +0 -6
- package/dist/types/v7-services/src/personal.d.ts +0 -3
- package/dist/wdosbox.js +0 -55
- package/dist/wdosbox.js.symbols +0 -3174
- package/dist/wdosbox.wasm +0 -0
- 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
|
+
}
|