Skip to content

Commit d7f6fe8

Browse files
committed
wip
1 parent 436f2d2 commit d7f6fe8

5 files changed

Lines changed: 425 additions & 1 deletion

File tree

.oxlintrc.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,13 @@
7272
"import/no-unassigned-import": [
7373
"error",
7474
{
75-
"allow": ["solid-devtools", "simpledotcss/simple.min.css", "./canvas-shim", "./domains/ui/global-styles"]
75+
"allow": [
76+
"solid-devtools",
77+
"simpledotcss/simple.min.css",
78+
"./canvas-shim",
79+
"./domains/ui/global-styles",
80+
"./css/simple-extend.css"
81+
]
7682
}
7783
],
7884
"import/prefer-default-export": "off",

backup-changes.diff

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
diff --git a/README.md b/README.md
2+
index a0df01d..8a2ea7a 100644
3+
--- a/README.md
4+
+++ b/README.md
5+
@@ -74,3 +74,10 @@ The project also served as a way for me to experiment with different approaches,
6+
## Credits
7+
8+
Thanks to [@breadbored](https://github.com/breadbored/) and its original concept [Mario Kart World Toolkit](https://bread.codes/posts/mario-kart-world-toolkit/).
9+
+
10+
+// TODO: Add warning about obs and the window pixel to refresh
11+
+// TODO: Add screen models in the issue model (TV Samsung, TV whatever)
12+
+// TODO: Add how you can make the window hidden in OBS
13+
+// TODO: Modal changelog first time access
14+
+// TODO: Improve some OBS integration and explanation (Make sure to select Source and your switch source)
15+
+// Move total time of tracks from History to Result. Tries stays on History, and move WR in second tab
16+
diff --git a/package.json b/package.json
17+
index fe88eb2..4b35876 100644
18+
--- a/package.json
19+
+++ b/package.json
20+
@@ -23,6 +23,7 @@
21+
"dependencies": {
22+
"@emotion/css": "11.13.5",
23+
"@henrygd/queue": "1.2.0",
24+
+ "@solid-primitives/keyed": "1.5.3",
25+
"@solidjs/router": "0.15.4",
26+
"date-and-time": "4.2.0",
27+
"idb-keyval": "6.2.2",
28+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
29+
index 278f069..2325a22 100644
30+
--- a/pnpm-lock.yaml
31+
+++ b/pnpm-lock.yaml
32+
@@ -14,6 +14,9 @@ importers:
33+
'@henrygd/queue':
34+
specifier: 1.2.0
35+
version: 1.2.0
36+
+ '@solid-primitives/keyed':
37+
+ specifier: 1.5.3
38+
+ version: 1.5.3(solid-js@1.9.11)
39+
'@solidjs/router':
40+
specifier: 0.15.4
41+
version: 0.15.4(solid-js@1.9.11)
42+
@@ -1022,6 +1025,11 @@ packages:
43+
peerDependencies:
44+
solid-js: ^1.6.12
45+
46+
+ '@solid-primitives/keyed@1.5.3':
47+
+ resolution: {integrity: sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA==}
48+
+ peerDependencies:
49+
+ solid-js: ^1.6.12
50+
+
51+
'@solid-primitives/media@2.3.3':
52+
resolution: {integrity: sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA==}
53+
peerDependencies:
54+
@@ -2736,6 +2744,10 @@ snapshots:
55+
'@solid-primitives/utils': 6.3.2(solid-js@1.9.11)
56+
solid-js: 1.9.11
57+
58+
+ '@solid-primitives/keyed@1.5.3(solid-js@1.9.11)':
59+
+ dependencies:
60+
+ solid-js: 1.9.11
61+
+
62+
'@solid-primitives/media@2.3.3(solid-js@1.9.11)':
63+
dependencies:
64+
'@solid-primitives/event-listener': 2.4.3(solid-js@1.9.11)
65+
diff --git a/src/domains/attempt/components/attempts-table.tsx b/src/domains/attempt/components/attempts-table.tsx
66+
index 78ee99d..072a9ef 100644
67+
--- a/src/domains/attempt/components/attempts-table.tsx
68+
+++ b/src/domains/attempt/components/attempts-table.tsx
69+
@@ -6,6 +6,8 @@ import { HorizontalDivider } from "../../ui/components/grid/horizontal-divider";
70+
import { VerticalDivider } from "../../ui/components/grid/vertical-divider";
71+
import { cellCss } from "../../ui/css/cell-css";
72+
import { isDefined } from "remeda";
73+
+import { SymbolButton } from "../../ui/components/symbol-button";
74+
+import { Key } from "@solid-primitives/keyed";
75+
76+
const sFirstColumn = cellCss({
77+
extraPadding: "left",
78+
@@ -32,25 +34,28 @@ interface AttemptsTableProps {
79+
showTime?: boolean;
80+
showTrack?: boolean;
81+
limit?: number;
82+
+ onDelete?: (attempt: Attempt) => void;
83+
}
84+
85+
export const AttemptsTable: Component<AttemptsTableProps> = (props) => {
86+
- const GRID_COLUMNS = 16;
87+
+ const GRID_COLUMNS = 18;
88+
const GRID_SPLITS_COLUMNS = 7;
89+
const GRID_RESULT_COLUMNS = 3;
90+
const GRID_SEPARATION_THICKNESS = 2;
91+
92+
const showTime = createMemo(() => props.showTime ?? true);
93+
const showTrack = createMemo(() => props.showTrack ?? true);
94+
- const gridColumns = createMemo(() => GRID_COLUMNS - (Number(!showTime()) + Number(!showTrack())));
95+
-
96+
+ const showDelete = createMemo(() => !!props.onDelete);
97+
+ const showActions = createMemo(() => showDelete());
98+
+ const gridColumns = createMemo(() => GRID_COLUMNS - (Number(!showTime()) + Number(!showTrack()) + 2*Number(!showActions())));
99+
+ console.log(showActions())
100+
const attempts = createMemo(() => props.attempts.slice(0, props.limit));
101+
102+
return (
103+
<Switch>
104+
<Match when={attempts().length}>
105+
<GridColumn template={`repeat(${gridColumns()}, max-content)`} xAlign="center">
106+
- <For each={attempts()}>
107+
+ <Key each={attempts()} by={(attempt) => attempt.id}>
108+
{(attempt, aIndex) => (
109+
<>
110+
<Show when={aIndex() % 7 === 0}>
111+
@@ -73,26 +78,30 @@ export const AttemptsTable: Component<AttemptsTableProps> = (props) => {
112+
<VerticalDivider />
113+
<Cell text="⏱️" css={[sTitle]} />
114+
<VerticalDivider />
115+
- <Cell text="️🟡" css={[sTitle, sLastColumn]} />
116+
+ <Cell text="️🟡" css={[sTitle, showActions() ? undefined : sLastColumn]} />
117+
+ <Show when={showActions()}>
118+
+ <VerticalDivider />
119+
+ <Cell text="🎬" css={[sTitle, sLastColumn]} />
120+
+ </Show>
121+
<HorizontalDivider column={gridColumns()} thicknessFactor={GRID_SEPARATION_THICKNESS} />
122+
</Show>
123+
124+
- <Cell row={attempt.rowSplits} text={attempt.date} css={[sFirstColumn, sInfo]} />
125+
+ <Cell row={attempt().rowSplits} text={attempt().date} css={[sFirstColumn, sInfo]} />
126+
<Show when={showTime()}>
127+
- <Cell row={attempt.rowSplits} text={attempt.datetime} css={[sInfo]} />
128+
+ <Cell row={attempt().rowSplits} text={attempt().datetime} css={[sInfo]} />
129+
</Show>
130+
- <Cell row={attempt.rowSplits} text={attempt.raw.player} css={[sInfo]} />
131+
+ <Cell row={attempt().rowSplits} text={attempt().raw.player} css={[sInfo]} />
132+
<Show when={showTrack()}>
133+
- <Cell row={attempt.rowSplits} text={attempt.raw.track} css={[sInfo]} />
134+
+ <Cell row={attempt().rowSplits} text={attempt().raw.track} css={[sInfo]} />
135+
</Show>
136+
- <VerticalDivider row={attempt.rowSplits} />
137+
+ <VerticalDivider row={attempt().rowSplits} />
138+
<Switch>
139+
- <Match when={attempt.splits.length > 0}>
140+
- <For each={attempt.laps}>
141+
+ <Match when={attempt().splits.length > 0}>
142+
+ <For each={attempt().laps}>
143+
{(_, sIndex) => (
144+
<>
145+
<Switch>
146+
- <Match when={attempt.splits.at(sIndex())}>
147+
+ <Match when={attempt().splits.at(sIndex())}>
148+
{(split) => (
149+
<>
150+
<Cell text={`S${_}`} />
151+
@@ -102,7 +111,7 @@ export const AttemptsTable: Component<AttemptsTableProps> = (props) => {
152+
<Cell text={split().raw.coins} css={[sValue]} />
153+
<VerticalDivider />
154+
<Cell text={split().raw.shrooms} css={[sValue]} />
155+
- <Show when={sIndex() > 0 && sIndex() < attempt.laps.length - 1}>
156+
+ <Show when={sIndex() > 0 && sIndex() < attempt().laps.length - 1}>
157+
<HorizontalDivider column={GRID_SPLITS_COLUMNS} />
158+
</Show>
159+
</>
160+
@@ -110,23 +119,29 @@ export const AttemptsTable: Component<AttemptsTableProps> = (props) => {
161+
</Match>
162+
<Match when={true}>
163+
<Cell column={GRID_SPLITS_COLUMNS} />
164+
- <Show when={sIndex() > 0 && sIndex() < attempt.laps.length - 1}>
165+
+ <Show when={sIndex() > 0 && sIndex() < attempt().laps.length - 1}>
166+
<HorizontalDivider column={GRID_SPLITS_COLUMNS} />
167+
</Show>
168+
</Match>
169+
</Switch>
170+
<Show when={sIndex() === 0}>
171+
- <VerticalDivider row={attempt.rowSplits} />
172+
+ <VerticalDivider row={attempt().rowSplits} />
173+
<Switch>
174+
- <Match when={!isDefined(attempt.time) && !isDefined(attempt.coins)}>
175+
- <Cell column={GRID_RESULT_COLUMNS} row={attempt.rowSplits} css={[sLastColumn]} />
176+
+ <Match when={!isDefined(attempt().time) && !isDefined(attempt().coins)}>
177+
+ <Cell column={GRID_RESULT_COLUMNS} row={attempt().rowSplits} css={[sLastColumn]} />
178+
</Match>
179+
<Match when>
180+
- <Cell row={attempt.rowSplits} text={attempt.time} css={[sValue]} />
181+
- <VerticalDivider row={attempt.rowSplits} />
182+
- <Cell row={attempt.rowSplits} text={attempt.coins} css={[sValue, sLastColumn]} />
183+
+ <Cell row={attempt().rowSplits} text={attempt().time} css={[sValue]} />
184+
+ <VerticalDivider row={attempt().rowSplits} />
185+
+ <Cell row={attempt().rowSplits} text={attempt().coins} css={[sValue, sLastColumn]} />
186+
</Match>
187+
</Switch>
188+
+ <Show when={showActions()}>
189+
+ <VerticalDivider row={attempt().rowSplits} />
190+
+ <div style={`grid-row: span ${attempt().rowSplits}`}>
191+
+ <SymbolButton symbol="🗑" onClick={() => props.onDelete?.(attempt())}></SymbolButton>
192+
+ </div>
193+
+ </Show>
194+
<HorizontalDivider column={GRID_SPLITS_COLUMNS} />
195+
</Show>
196+
</>
197+
@@ -134,15 +149,23 @@ export const AttemptsTable: Component<AttemptsTableProps> = (props) => {
198+
</For>
199+
</Match>
200+
<Match when={true}>
201+
- <Cell column={GRID_SPLITS_COLUMNS} row={attempt.rowSplits} text="Not even one split 😭" />
202+
- <VerticalDivider row={attempt.rowSplits} />
203+
- <Cell row={attempt.rowSplits} column={GRID_RESULT_COLUMNS} css={[sLastColumn]} />
204+
+ <Cell column={GRID_SPLITS_COLUMNS} row={attempt().rowSplits} text="Not even one split 😭" />
205+
+ <VerticalDivider row={attempt().rowSplits} />
206+
+ <Cell row={attempt().rowSplits} column={GRID_RESULT_COLUMNS} css={[sLastColumn]}/>
207+
+ <Show when={showActions()}>
208+
+ <VerticalDivider row={attempt().rowSplits} />
209+
+ <div style={`grid-row: span ${attempt().rowSplits}`}>
210+
+ <button>
211+
+ 🗑
212+
+ </button>
213+
+ </div>
214+
+ </Show>
215+
</Match>
216+
</Switch>
217+
<HorizontalDivider column={gridColumns()} thicknessFactor={GRID_SEPARATION_THICKNESS} />
218+
</>
219+
)}
220+
- </For>
221+
+ </Key>
222+
</GridColumn>
223+
</Match>
224+
<Match when={true}>
225+
diff --git a/src/domains/attempt/schemas/attempt.ts b/src/domains/attempt/schemas/attempt.ts
226+
index dcd5df8..3aa9926 100644
227+
--- a/src/domains/attempt/schemas/attempt.ts
228+
+++ b/src/domains/attempt/schemas/attempt.ts
229+
@@ -30,7 +30,22 @@ export const AttemptSchema = v.pipe(
230+
};
231+
});
232+
233+
+ /*
234+
+ * We have to add an id because <For of SolidJS is based on reference of the objects
235+
+ * However, as our attempts list is a memo of the store.attempts which is augmented by this AttemptSchema
236+
+ * parsing, everytime the list store.attempts is updated, all the table is rerender which by side effect resets the scroll at the top of the page.
237+
+ * The workaround is to use <Key of solidjs/primitive that allows to define a ID as keys.
238+
+ */
239+
+ const id = [
240+
+ attemptEntity.timestamp,
241+
+ attemptEntity.player,
242+
+ attemptEntity.track,
243+
+ ...attemptEntity.splits.map((split) => split.time),
244+
+ ].join("|");
245+
+
246+
return {
247+
+ // The id is needed in conjunction with <Key each={} by={}> to avoid rerender of all the lists
248+
+ id: id,
249+
raw: attemptEntity,
250+
date: date,
251+
datetime: datetime,
252+
diff --git a/src/domains/database/compositions/use-personal-repository.ts b/src/domains/database/compositions/use-personal-repository.ts
253+
index 7b0a1c4..623f49a 100644
254+
--- a/src/domains/database/compositions/use-personal-repository.ts
255+
+++ b/src/domains/database/compositions/use-personal-repository.ts
256+
@@ -1,5 +1,5 @@
257+
import * as v from "valibot";
258+
-import { entries, sum, values } from "remeda";
259+
+import { entries, isDeepEqual, isStrictEqual, sum, values } from "remeda";
260+
import type { AttemptEntity } from "../schemas/attempt-entity";
261+
import type { AttemptsEntity } from "../schemas/attempts-entity";
262+
import type { Brand } from "../../_core/utils/brand";
263+
@@ -7,7 +7,7 @@ import { JSONUtils } from "../../_core/utils/json-utils";
264+
import { PersonalEntitySchema } from "../schemas/personal-entity";
265+
import { createMemo } from "solid-js";
266+
import { createSingletonRoot } from "../../_core/utils/solid-js";
267+
-import { produce } from "solid-js/store";
268+
+import { produce, reconcile } from "solid-js/store";
269+
import { useAttempts } from "../../attempt/compositions/use-attempts";
270+
import { useDatabases } from "./use-databases";
271+
272+
@@ -46,6 +46,14 @@ function usePersonalRepositorySingleton() {
273+
);
274+
};
275+
276+
+ const deleteAttempt = (delAttempt: AttemptEntity): void => {
277+
+ setStore(
278+
+ produce((store) => {
279+
+ store.attempts = store.attempts.filter((el) => !isDeepEqual(el, delAttempt));
280+
+ }),
281+
+ );
282+
+ };
283+
+
284+
const getAttemptsCountByTrack = (track: string): number => {
285+
return store.attemptsCountByTrack[track] ?? 0;
286+
};
287+
@@ -82,6 +90,7 @@ function usePersonalRepositorySingleton() {
288+
289+
return {
290+
upsertAttempt,
291+
+ deleteAttempt,
292+
attempts,
293+
lastAttempt,
294+
getAttemptsCount,
295+
diff --git a/src/views/history.tsx b/src/views/history.tsx
296+
index 4186043..7a20671 100644
297+
--- a/src/views/history.tsx
298+
+++ b/src/views/history.tsx
299+
@@ -10,6 +10,7 @@ import { css } from "@emotion/css";
300+
import { useAttemptsFilter } from "../domains/attempt/components/attempts-filter/use-attempts-filter";
301+
import { usePageTitle } from "./compositions/use-page-title";
302+
import { usePersonalRepository } from "../domains/database/compositions/use-personal-repository";
303+
+import type { Attempt } from "../domains/attempt/schemas/attempt";
304+
305+
const sActions = css({
306+
display: "flex",
307+
@@ -75,6 +76,10 @@ export const History: Component = () => {
308+
);
309+
};
310+
311+
+ const onDelete = (a: Attempt) => {
312+
+ personal.deleteAttempt(a.raw);
313+
+ };
314+
+
315+
return (
316+
<>
317+
<div class={sActions}>
318+
@@ -90,7 +95,7 @@ export const History: Component = () => {
319+
<AttemptsFilter />
320+
<OverallData />
321+
</div>
322+
- <AttemptsTable attempts={filtered().attempts}></AttemptsTable>
323+
+ <AttemptsTable attempts={filtered().attempts} onDelete={onDelete} />
324+
</>
325+
);
326+
};
327+
diff --git a/src/views/readme/faq-obs-studio.tsx b/src/views/readme/faq-obs-studio.tsx
328+
index 787d722..4b7d4d8 100644
329+
--- a/src/views/readme/faq-obs-studio.tsx
330+
+++ b/src/views/readme/faq-obs-studio.tsx
331+
@@ -30,7 +30,8 @@ export const FaqObsStudio: Component = () => {
332+
<A target="_blank" href="https://obsproject.com/kb/virtual-camera-guide">
333+
OBS Virtual Camera
334+
</A>{" "}
335+
- that mirrors your capture card. Then you use the OBS Virtual Camera as a source in your browser.
336+
+ that mirrors your capture card. Select Type of source Source, and then your switch source. Then you use the
337+
+ OBS Virtual Camera as a source in your browser.
338+
</p>
339+
</details>
340+
341+
@@ -45,12 +46,13 @@ export const FaqObsStudio: Component = () => {
342+
<p>
343+
On OBS Studio, add a new source <code>Window Capture</code>, and you select the popup. When you configure it
344+
for the first time, I suggest that you go on the track DK Spaceport as it is the worst-case scenario in terms
345+
- of number of splits. Do not forget to crop the header of the window 😊.
346+
+ of number of splits (height). Do not forget to crop the header of the window 😊.
347+
</p>
348+
<p>
349+
After the first configuration, every time you open again the popup, OBS Studio should capture it
350+
automatically.
351+
</p>
352+
+ <p>Extra: You can make the background of the winrdow transparent. Just click left filter add ColorKey 212121</p>
353+
</details>
354+
</>
355+
);
356+
diff --git a/src/views/result.tsx b/src/views/result.tsx
357+
index 706b371..5097797 100644
358+
--- a/src/views/result.tsx
359+
+++ b/src/views/result.tsx
360+
@@ -22,9 +22,9 @@ export const Result: Component = () => {
361+
<>
362+
<AttemptsFilter />
363+
<h2>Personal best</h2>
364+
- <AttemptsTable attempts={filtered().timeRecords}></AttemptsTable>
365+
+ <AttemptsTable attempts={filtered().timeRecords} />
366+
<h2>Best personal splits</h2>
367+
- <AttemptsTable attempts={filtered().splitRecords}></AttemptsTable>
368+
+ <AttemptsTable attempts={filtered().splitRecords} />
369+
</>
370+
);
371+
};

0 commit comments

Comments
 (0)