basic usability complete
This commit is contained in:
parent
1e71255371
commit
a416f66a56
8 changed files with 118 additions and 45 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -14,6 +14,7 @@
|
|||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-store": "^2.2.0",
|
||||
"solid-icons": "^1.1.0",
|
||||
"solid-js": "^1.9.3",
|
||||
"vite-plugin-inline-css-modules": "^0.0.8"
|
||||
},
|
||||
|
@ -1803,6 +1804,15 @@
|
|||
"seroval": "^1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/solid-icons": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/solid-icons/-/solid-icons-1.1.0.tgz",
|
||||
"integrity": "sha512-IesTfr/F1ElVwH2E1110s2RPXH4pujKfSs+koT8rwuTAdleO5s26lNSpqJV7D1+QHooJj18mcOiz2PIKs0ic+A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"solid-js": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/solid-js": {
|
||||
"version": "1.9.5",
|
||||
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.5.tgz",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-store": "^2.2.0",
|
||||
"solid-icons": "^1.1.0",
|
||||
"solid-js": "^1.9.3",
|
||||
"vite-plugin-inline-css-modules": "^0.0.8"
|
||||
},
|
||||
|
|
11
src/App.css
11
src/App.css
|
@ -83,9 +83,12 @@
|
|||
|
||||
.puzzle-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
flex-direction: row;
|
||||
> h1 {
|
||||
margin-bottom: 1em;
|
||||
> h2 {
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
|
@ -96,6 +99,10 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: calc(1 * var(--unit));
|
||||
& button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.puzzle-row {
|
||||
|
|
|
@ -1,36 +1,76 @@
|
|||
import { For } from "solid-js";
|
||||
import { For, Show, createMemo } from "solid-js";
|
||||
import useAppModel from "./useAppModel";
|
||||
import { A } from "@solidjs/router";
|
||||
import { css } from 'vite-plugin-inline-css-modules'
|
||||
|
||||
const styles = css`
|
||||
calendar: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(7, 1fr)",
|
||||
},
|
||||
entry: (solved: boolean) => {
|
||||
return {
|
||||
borderRadius: "50%",
|
||||
backgroundColor: solved ? "green" : "gray",
|
||||
.calendar {
|
||||
column-width: 7em;
|
||||
column-gap: 0.3em;
|
||||
}
|
||||
.entry {
|
||||
width: 0.8em;
|
||||
margin: 0.1em;
|
||||
display: inline-block;
|
||||
height: 0.8em;
|
||||
border-radius: 25%;
|
||||
background-color: gray;
|
||||
}
|
||||
.entryBlank {
|
||||
background: none;
|
||||
}
|
||||
.nextPuzzle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
font-size: 3em;
|
||||
line-height: 1;
|
||||
padding: 1em;
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--group-green);
|
||||
margin-bottom: 1em;
|
||||
&:hover, &:focus-visible, &:active {
|
||||
color: var(--color-foreground);
|
||||
outline: none;
|
||||
background-color: var(--group-yellow);
|
||||
}
|
||||
},
|
||||
}
|
||||
`
|
||||
|
||||
const colorStrings = [
|
||||
'var(--group-purple)',
|
||||
'var(--group-blue)',
|
||||
'var(--group-green)',
|
||||
'var(--group-yellow)',
|
||||
]
|
||||
|
||||
export default function Dashboard() {
|
||||
const { connections, store } = useAppModel();
|
||||
const nextUnsolvedId = createMemo(() => {
|
||||
return connections.find(x => store.solutions[x.id] === undefined)?.id
|
||||
})
|
||||
return (
|
||||
<div class={styles.calendar}>
|
||||
<For each={connections}>
|
||||
{(item) => {
|
||||
const isSolved = store.solutions[item.id] !== undefined;
|
||||
return (
|
||||
<A
|
||||
href={`/puzzle/${item.id}`}
|
||||
{...stylex.props(styles.entry(isSolved))}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
<div>
|
||||
<Show when={nextUnsolvedId() !== undefined}>
|
||||
<a class={styles.nextPuzzle} href={`/puzzle/${nextUnsolvedId()}`}>Next puzzle: #{nextUnsolvedId()}</a>
|
||||
</Show>
|
||||
<div class={styles.calendar}>
|
||||
<div class={`${styles.entry} ${styles.entryBlank}`}></div>
|
||||
<For each={connections}>
|
||||
{(item) => {
|
||||
const isSolved = store.solutions[item.id] !== undefined;
|
||||
return (
|
||||
<a
|
||||
href={`/puzzle/${item.id}`}
|
||||
class={styles.entry}
|
||||
style={isSolved ? {
|
||||
"background-color": colorStrings[store.solutions[item.id].guesses - 4] ?? 'var(--color-foreground)'
|
||||
} : {}}
|
||||
title={`#${item.id}, ${item.date}, ${store.solutions[item.id]?.guesses}`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,17 +2,16 @@ import { createEffect, createSignal, type Accessor } from "solid-js";
|
|||
|
||||
export default function FitText(props: { body: Accessor<string> }) {
|
||||
let textRef: SVGTextElement | undefined;
|
||||
const [width, setWidth] = createSignal(100);
|
||||
const [height, setHeight] = createSignal(100);
|
||||
const [viewBox, setViewbox] = createSignal<string | undefined>()
|
||||
|
||||
createEffect(() => {
|
||||
createEffect(async () => {
|
||||
props.body();
|
||||
await Promise.resolve()
|
||||
const bounds = textRef?.getBBox();
|
||||
if (bounds === undefined) {
|
||||
return;
|
||||
}
|
||||
setWidth(bounds.width);
|
||||
setHeight(bounds.height);
|
||||
setViewbox(`0 0 ${bounds.width} ${bounds.height}`)
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -21,15 +20,14 @@ export default function FitText(props: { body: Accessor<string> }) {
|
|||
width: "100%",
|
||||
"max-height": "40%",
|
||||
}}
|
||||
viewBox={`0 0 ${width()} ${height()}`}
|
||||
overflow="visible"
|
||||
viewBox={viewBox()}
|
||||
fill="currentcolor"
|
||||
>
|
||||
<text
|
||||
ref={textRef}
|
||||
x="50%"
|
||||
y="50%"
|
||||
dominant-baseline="central"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="middle"
|
||||
font-size="30px"
|
||||
font-family="inherit"
|
||||
|
|
|
@ -3,6 +3,7 @@ import "./App.css";
|
|||
import usePuzzleModel from "./usePuzzleModel";
|
||||
import FitText from "./FitText";
|
||||
import { useNavigate, useParams } from "@solidjs/router";
|
||||
import { TbArrowLeft, TbArrowRight, TbArrowUp } from 'solid-icons/tb'
|
||||
|
||||
// TODO
|
||||
// add routing
|
||||
|
@ -32,7 +33,7 @@ function Puzzle() {
|
|||
<main class="container">
|
||||
<div class="puzzle">
|
||||
<header class="puzzle-header">
|
||||
<h1>Slick Connections</h1>
|
||||
<h2>#{id()}</h2>
|
||||
<div class="puzzle-header-actions">
|
||||
<button
|
||||
type="button"
|
||||
|
@ -41,7 +42,15 @@ function Puzzle() {
|
|||
}}
|
||||
disabled={id() === 1}
|
||||
>
|
||||
-
|
||||
<TbArrowLeft />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
navigate('/');
|
||||
}}
|
||||
>
|
||||
<TbArrowUp />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -50,7 +59,7 @@ function Puzzle() {
|
|||
}}
|
||||
disabled={id() === connections.length - 1}
|
||||
>
|
||||
+
|
||||
<TbArrowRight />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
@ -5,7 +5,7 @@ import { tauriStorage } from "@solid-primitives/storage/tauri";
|
|||
|
||||
type Solution = {
|
||||
id: number
|
||||
mistakes: number
|
||||
guesses: number
|
||||
}
|
||||
|
||||
type AppStore = {
|
||||
|
@ -24,10 +24,10 @@ export default function useAppModel() {
|
|||
storage,
|
||||
}
|
||||
);
|
||||
function setSolution(id: number, mistakes: number) {
|
||||
function setSolution(id: number, guesses: number) {
|
||||
setStore("solutions", id, {
|
||||
id,
|
||||
mistakes
|
||||
guesses
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Accessor, createEffect } from "solid-js";
|
|||
import connections from "./assets/connections.json";
|
||||
import { shuffleArray } from "./utils";
|
||||
import { createStore } from "solid-js/store";
|
||||
import useAppModel from "./useAppModel";
|
||||
|
||||
type Connection = (typeof connections)[number];
|
||||
type Answer = Connection["answers"][number];
|
||||
|
@ -12,7 +13,8 @@ function fromIndex(index: number): [number, number] {
|
|||
return [row, col];
|
||||
}
|
||||
|
||||
type AppStore = {
|
||||
type PuzzleStore = {
|
||||
guesses: number;
|
||||
pinnedCount: number;
|
||||
selected: number[];
|
||||
solvedGroups: Answer[];
|
||||
|
@ -20,16 +22,22 @@ type AppStore = {
|
|||
};
|
||||
|
||||
export default function usePuzzleModel(id: Accessor<number>) {
|
||||
const [store, setStore] = createStore<AppStore>({
|
||||
const [store, setStore] = createStore<PuzzleStore>({
|
||||
guesses: 0,
|
||||
pinnedCount: 0,
|
||||
selected: [],
|
||||
solvedGroups: [],
|
||||
puzzle: shuffleArray(Array.from({ length: 16 }, (_, i) => i)),
|
||||
puzzle: [],
|
||||
});
|
||||
|
||||
const {
|
||||
setSolution
|
||||
} = useAppModel()
|
||||
|
||||
createEffect(() => {
|
||||
id()
|
||||
setStore({
|
||||
guesses: 0,
|
||||
pinnedCount: 0,
|
||||
selected: [],
|
||||
solvedGroups: [],
|
||||
|
@ -53,6 +61,7 @@ export default function usePuzzleModel(id: Accessor<number>) {
|
|||
};
|
||||
|
||||
const handleGuess = () => {
|
||||
setStore('guesses', x => x+1)
|
||||
const selected = store.puzzle.length === 4 ? [0, 1, 2, 3] : store.selected;
|
||||
const selectedAnswers = selected.map((x) => getFromPuzzle(x));
|
||||
const { level } = selectedAnswers[0];
|
||||
|
@ -75,12 +84,11 @@ export default function usePuzzleModel(id: Accessor<number>) {
|
|||
});
|
||||
const newSolvedGroup = answers().find((x) => x.level === level);
|
||||
if (newSolvedGroup != null) {
|
||||
setStore({
|
||||
solvedGroups: [...store.solvedGroups, newSolvedGroup],
|
||||
});
|
||||
setStore('solvedGroups', x => x.concat(newSolvedGroup))
|
||||
}
|
||||
if (store.puzzle.length === 0) {
|
||||
// completely solved!
|
||||
setSolution(id(), store.guesses)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue