Skip to content

Commit 03397b7

Browse files
committed
derp
1 parent 07b5421 commit 03397b7

File tree

13 files changed

+264
-94
lines changed

13 files changed

+264
-94
lines changed

frontend/__tests__/components/AsyncContent.spec.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ describe("AsyncContent", () => {
6565

6666
it("renders loading state while loadingStore is pending", () => {
6767
const loadingStore = createLoadingStore(
68+
"test",
6869
async () => {
6970
await new Promise((resolve) => setTimeout(resolve, 100));
7071
return { data: "data" };
@@ -87,6 +88,7 @@ describe("AsyncContent", () => {
8788

8889
it("renders data when loadingStore resolves", async () => {
8990
const loadingStore = createLoadingStore<{ data?: string }>(
91+
"test",
9092
async () => {
9193
return { data: "Test Data" };
9294
},
@@ -102,6 +104,7 @@ describe("AsyncContent", () => {
102104

103105
it("renders error message when loadingStore fails", async () => {
104106
const loadingStore = createLoadingStore(
107+
"test",
105108
async () => {
106109
throw new Error("Test error");
107110
},

frontend/__tests__/signals/util/loadingStore.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe("createLoadingStore", () => {
1111
});
1212

1313
it("should initialize with the correct state", () => {
14-
const store = createLoadingStore(mockFetcher, initialValue);
14+
const store = createLoadingStore("test", mockFetcher, initialValue);
1515

1616
expect(store.state().state).toBe("unresolved");
1717
expect(store.state().loading).toBe(false);
@@ -22,22 +22,22 @@ describe("createLoadingStore", () => {
2222
});
2323

2424
it("should transition to loading when load is called", async () => {
25-
const store = createLoadingStore(mockFetcher, initialValue);
25+
const store = createLoadingStore("test", mockFetcher, initialValue);
2626
store.load();
2727

2828
expect(store.state().state).toBe("pending");
2929
expect(store.state().loading).toBe(true);
3030
});
3131

3232
it("should enable loading if ready is called", async () => {
33-
const store = createLoadingStore(mockFetcher, initialValue);
33+
const store = createLoadingStore("test", mockFetcher, initialValue);
3434
mockFetcher.mockResolvedValueOnce({ data: "test" });
3535

3636
await store.ready();
3737
});
3838

3939
it("should call the fetcher when load is called", async () => {
40-
const store = createLoadingStore(mockFetcher, initialValue);
40+
const store = createLoadingStore("test", mockFetcher, initialValue);
4141
mockFetcher.mockResolvedValueOnce({ data: "test" });
4242
store.load();
4343

@@ -50,7 +50,7 @@ describe("createLoadingStore", () => {
5050

5151
it("should handle error when fetcher fails", async () => {
5252
mockFetcher.mockRejectedValueOnce(new Error("Failed to load"));
53-
const store = createLoadingStore(mockFetcher, initialValue);
53+
const store = createLoadingStore("test", mockFetcher, initialValue);
5454

5555
store.load();
5656

@@ -61,7 +61,7 @@ describe("createLoadingStore", () => {
6161
});
6262

6363
it("should transition to refreshing state on refresh", async () => {
64-
const store = createLoadingStore(mockFetcher, initialValue);
64+
const store = createLoadingStore("test", mockFetcher, initialValue);
6565
mockFetcher.mockResolvedValueOnce({ data: "test" });
6666
store.load();
6767

@@ -71,7 +71,7 @@ describe("createLoadingStore", () => {
7171
});
7272

7373
it("should trigger load when refresh is called and shouldLoad is false", async () => {
74-
const store = createLoadingStore(mockFetcher, initialValue);
74+
const store = createLoadingStore("test", mockFetcher, initialValue);
7575
mockFetcher.mockResolvedValueOnce({ data: "test" });
7676
expect(store.state().state).toBe("unresolved");
7777

@@ -88,7 +88,7 @@ describe("createLoadingStore", () => {
8888
});
8989

9090
it("should reset the store to its initial value on reset", async () => {
91-
const store = createLoadingStore(mockFetcher, initialValue);
91+
const store = createLoadingStore("test", mockFetcher, initialValue);
9292
mockFetcher.mockResolvedValueOnce({ data: "test" });
9393
store.load();
9494

@@ -103,7 +103,7 @@ describe("createLoadingStore", () => {
103103
});
104104

105105
it("should handle a promise rejection during reset", async () => {
106-
const store = createLoadingStore(mockFetcher, initialValue);
106+
const store = createLoadingStore("test", mockFetcher, initialValue);
107107

108108
// Mock the fetcher to resolve with data
109109
mockFetcher.mockResolvedValueOnce({ data: "test" });

frontend/src/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<div class="bar"></div>
2222
</div>
2323
</div>
24+
<PreLoader />
2425
<div id="popups">
2526
<load src="html/popups.html" />
2627
</div>

frontend/src/ts/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import * as Sentry from "./sentry";
3131
import { tryCatch } from "@monkeytype/util/trycatch";
3232
import * as AuthEvent from "./observables/auth-event";
3333
import { qs, qsa } from "./utils/dom";
34+
import { preloaderDonePromise } from "./components/common/PreLoader";
3435

3536
export const gmailProvider = new GoogleAuthProvider();
3637
export const githubProvider = new GithubAuthProvider();
@@ -59,6 +60,8 @@ async function sendVerificationEmail(): Promise<void> {
5960
async function getDataAndInit(): Promise<boolean> {
6061
try {
6162
console.log("getting account data");
63+
await preloaderDonePromise;
64+
console.log("getting account data waiting done");
6265
const snapshot = await DB.initSnapshot();
6366

6467
if (snapshot === false) {

frontend/src/ts/components/common/Loader.tsx

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ type ChildData<L extends LoadShape> = {
2222
};
2323

2424
type LoaderProps<L extends LoadShape> = {
25-
active: Accessor<boolean>;
25+
active: true | Accessor<boolean>;
2626
load: L;
2727
showLoader?: boolean;
2828
errorMessage?: string;
29-
children: (data: ChildData<L>) => JSXElement;
29+
onComplete?: (data: ChildData<L>) => void;
30+
children?: (data: ChildData<L>) => JSXElement;
3031
};
3132

3233
export default function Loader<L extends LoadShape>(
@@ -36,18 +37,23 @@ export default function Loader<L extends LoadShape>(
3637
Object.values(props.load),
3738
);
3839

39-
createEffect(
40-
on(
41-
props.active,
42-
(active) => {
43-
if (active) {
44-
console.debug("Loader: load all stores");
45-
loaders().forEach((it) => it.store.load());
46-
}
47-
},
48-
{ defer: true },
49-
),
50-
);
40+
if (props.active === true) {
41+
console.debug("Loader: load all stores");
42+
loaders().forEach((it) => it.store.load());
43+
} else {
44+
createEffect(
45+
on(
46+
props.active,
47+
(active) => {
48+
if (active) {
49+
console.debug("Loader: load all stores");
50+
loaders().forEach((it) => it.store.load());
51+
}
52+
},
53+
{ defer: true },
54+
),
55+
);
56+
}
5157

5258
const stores = createMemo(
5359
() =>
@@ -63,6 +69,18 @@ export default function Loader<L extends LoadShape>(
6369
},
6470
);
6571

72+
let completed = false;
73+
const allReady = createMemo(() =>
74+
loaders().every((it) => it.store.state().ready),
75+
);
76+
77+
createEffect(() => {
78+
if (!completed && allReady()) {
79+
completed = true;
80+
props.onComplete?.(stores());
81+
}
82+
});
83+
6684
const firstLoadingKeyframe = createMemo<Keyframe | undefined>(() => {
6785
let min: Keyframe | undefined;
6886

@@ -96,7 +114,7 @@ export default function Loader<L extends LoadShape>(
96114
</p>
97115
}
98116
>
99-
{props.children(stores())}
117+
{props.children?.(stores())}
100118
</Show>
101119
</Show>
102120
);
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { createEffect, JSXElement, on } from "solid-js";
2+
import { createLoadingStore } from "../../signals/util/loadingStore";
3+
import { PartialConfig } from "@monkeytype/schemas/configs";
4+
import Ape from "../../ape";
5+
import { isAuthenticated } from "../../signals/user";
6+
import { Preset } from "@monkeytype/schemas/presets";
7+
import Loader from "./Loader";
8+
import { serverConfiguration } from "../../signals/server-configuration";
9+
import { connections } from "../../signals/connections";
10+
import { GetUserResponse } from "@monkeytype/contracts/users";
11+
import { initSnapshot } from "../../db";
12+
import { Connection } from "@monkeytype/schemas/connections";
13+
import { promiseWithResolvers } from "../../utils/misc";
14+
15+
const { promise: preloaderDonePromise, resolve: loadDone } =
16+
promiseWithResolvers();
17+
18+
export { preloaderDonePromise };
19+
20+
export function PreLoader(): JSXElement {
21+
console.log("#### preloader");
22+
const user = createLoadingStore<GetUserResponse["data"]>(
23+
"user",
24+
async () => {
25+
const response = await Ape.users.get();
26+
27+
if (response.status !== 200) {
28+
throw new Error(response.body.message);
29+
}
30+
return response.body.data;
31+
},
32+
33+
() => ({}) as GetUserResponse["data"],
34+
);
35+
const partialConfig = createLoadingStore<PartialConfig>(
36+
"userConfig",
37+
async () => {
38+
const response = await Ape.configs.get();
39+
40+
if (response.status !== 200) {
41+
throw new Error(response.body.message);
42+
}
43+
return response.body.data as PartialConfig;
44+
},
45+
46+
() => ({}) as PartialConfig,
47+
);
48+
const presets = createLoadingStore<Preset[]>(
49+
"presets",
50+
async () => {
51+
const response = await Ape.presets.get();
52+
53+
if (response.status !== 200) {
54+
throw new Error(response.body.message);
55+
}
56+
return response.body.data;
57+
},
58+
59+
() => [],
60+
);
61+
62+
createEffect(() => {
63+
on(isAuthenticated, (authenticated: boolean) => {
64+
if (authenticated) return;
65+
66+
console.debug("PreLoader: cleaning user data.");
67+
[partialConfig, user, presets].forEach((it) => it.reset());
68+
});
69+
});
70+
71+
return (
72+
<Loader
73+
active={() => isAuthenticated() && serverConfiguration.state().ready}
74+
onComplete={isLoaded}
75+
load={{
76+
userData: {
77+
store: user,
78+
keyframe: {
79+
percentage: 80,
80+
durationMs: 1,
81+
text: "Downloading user data...",
82+
},
83+
},
84+
configData: {
85+
store: partialConfig,
86+
keyframe: {
87+
percentage: 85,
88+
durationMs: 1,
89+
text: "Downloading user config...",
90+
},
91+
},
92+
presetsData: {
93+
store: presets,
94+
keyframe: {
95+
percentage: 90,
96+
durationMs: 1,
97+
text: "Downloading user presets...",
98+
},
99+
},
100+
connectionsData: {
101+
store: connections,
102+
keyframe: {
103+
percentage: 95,
104+
durationMs: 1,
105+
text: "Downloading friends...",
106+
},
107+
},
108+
}}
109+
/>
110+
);
111+
}
112+
113+
function isLoaded(stores: {
114+
userData: GetUserResponse["data"];
115+
configData: PartialConfig;
116+
presetsData: Preset[];
117+
connectionsData: Connection[];
118+
}): void {
119+
console.log("preloader done loading", stores.userData.name);
120+
void initSnapshot(stores);
121+
loadDone();
122+
}

frontend/src/ts/components/mount.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import { JSXElement } from "solid-js";
55
import { Footer } from "./layout/footer/Footer";
66
import { Modals } from "./modals/Modals";
77
import { AboutPage } from "./pages/AboutPage";
8+
import { PreLoader } from "./common/PreLoader";
89

910
const components: Record<string, () => JSXElement> = {
1011
Footer: () => <Footer />,
1112
Modals: () => <Modals />,
1213
AboutPage: () => <AboutPage />,
14+
PreLoader: () => <PreLoader />,
1315
};
1416

1517
function mountToMountpoint(name: string, component: () => JSXElement): void {

0 commit comments

Comments
 (0)