From a416f66a562fcacacfdef673211513b340762149 Mon Sep 17 00:00:00 2001 From: Joshua Seigler Date: Fri, 18 Apr 2025 13:35:30 -0400 Subject: [PATCH] basic usability complete --- package-lock.json | 10 +++++ package.json | 1 + src/App.css | 11 +++++- src/Dashboard.tsx | 86 +++++++++++++++++++++++++++++++------------ src/FitText.tsx | 14 +++---- src/Puzzle.tsx | 15 ++++++-- src/useAppModel.ts | 6 +-- src/usePuzzleModel.ts | 20 +++++++--- 8 files changed, 118 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b0ffb4..b2d5072 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 5dd1a79..6dc922a 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/App.css b/src/App.css index c0c621b..7bee737 100644 --- a/src/App.css +++ b/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 { diff --git a/src/Dashboard.tsx b/src/Dashboard.tsx index 817e21c..0ab0cca 100644 --- a/src/Dashboard.tsx +++ b/src/Dashboard.tsx @@ -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 ( -
- - {(item) => { - const isSolved = store.solutions[item.id] !== undefined; - return ( - - ); - }} - +
+ + Next puzzle: #{nextUnsolvedId()} + +
+
+ + {(item) => { + const isSolved = store.solutions[item.id] !== undefined; + return ( + + ); + }} + +
); } diff --git a/src/FitText.tsx b/src/FitText.tsx index ba56869..f48b59e 100644 --- a/src/FitText.tsx +++ b/src/FitText.tsx @@ -2,17 +2,16 @@ import { createEffect, createSignal, type Accessor } from "solid-js"; export default function FitText(props: { body: Accessor }) { let textRef: SVGTextElement | undefined; - const [width, setWidth] = createSignal(100); - const [height, setHeight] = createSignal(100); + const [viewBox, setViewbox] = createSignal() - 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 }) { width: "100%", "max-height": "40%", }} - viewBox={`0 0 ${width()} ${height()}`} - overflow="visible" + viewBox={viewBox()} fill="currentcolor" >
-

Slick Connections

+

#{id()}

+
diff --git a/src/useAppModel.ts b/src/useAppModel.ts index 450dd62..0cb5867 100644 --- a/src/useAppModel.ts +++ b/src/useAppModel.ts @@ -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 }) } diff --git a/src/usePuzzleModel.ts b/src/usePuzzleModel.ts index b6eae5c..e58c7c6 100644 --- a/src/usePuzzleModel.ts +++ b/src/usePuzzleModel.ts @@ -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) { - const [store, setStore] = createStore({ + const [store, setStore] = createStore({ + 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) { }; 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) { }); 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) } };