persistence
This commit is contained in:
parent
9649d407fd
commit
3d58a0663a
11 changed files with 176 additions and 60 deletions
41
package-lock.json
generated
41
package-lock.json
generated
|
@ -9,8 +9,10 @@
|
|||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/storage": "^4.3.1",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-store": "^2.2.0",
|
||||
"solid-js": "^1.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1048,6 +1050,36 @@
|
|||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@solid-primitives/storage": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/storage/-/storage-4.3.1.tgz",
|
||||
"integrity": "sha512-xAJsY2pvXrAaCai4N2grmWY3xh5om9suTDVzGkRF5JBpDzs3Apk+xIovdTErbW0iCzXIEefENXb9xmSzdjuLYA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/utils": "^6.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tauri-apps/plugin-store": "*",
|
||||
"solid-js": "^1.6.12"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@tauri-apps/plugin-store": {
|
||||
"optional": true
|
||||
},
|
||||
"solid-start": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/utils": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.3.0.tgz",
|
||||
"integrity": "sha512-e7hTlJ1Ywh2+g/Qug+n4L1mpfxsikoIS4/sHE2EK9WatQt8UJqop/vE6bsLnXlU1xuhb/jo94Ah5Y27rd4wP7A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.4.1.tgz",
|
||||
|
@ -1284,6 +1316,15 @@
|
|||
"@tauri-apps/api": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-store": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-store/-/plugin-store-2.2.0.tgz",
|
||||
"integrity": "sha512-hJTRtuJis4w5fW1dkcgftsYxKXK0+DbAqurZ3CURHG5WkAyyZgbxpeYctw12bbzF9ZbZREXZklPq8mocCC3Sgg==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/storage": "^4.3.1",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-store": "^2.2.0",
|
||||
"solid-js": "^1.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
29
src-tauri/Cargo.lock
generated
29
src-tauri/Cargo.lock
generated
|
@ -3282,6 +3282,7 @@ dependencies = [
|
|||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-store",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3665,6 +3666,22 @@ dependencies = [
|
|||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-store"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c0c08fae6995909f5e9a0da6038273b750221319f2c0f3b526d6de1cde21505"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.5.1"
|
||||
|
@ -3883,9 +3900,21 @@ dependencies = [
|
|||
"mio",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.14"
|
||||
|
|
|
@ -22,4 +22,5 @@ tauri = { version = "2", features = [] }
|
|||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tauri-plugin-store = "2"
|
||||
|
||||
|
|
|
@ -2,9 +2,17 @@
|
|||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default"
|
||||
"opener:default",
|
||||
"store:allow-get",
|
||||
"store:allow-set",
|
||||
"store:allow-delete",
|
||||
"store:allow-keys",
|
||||
"store:allow-clear",
|
||||
"store:default"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ fn greet(name: &str) -> String {
|
|||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.run(tauri::generate_context!())
|
||||
|
|
|
@ -2,5 +2,10 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
// initialize store plugin:
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
slick_connections_lib::run()
|
||||
}
|
||||
|
|
24
src/App.css
24
src/App.css
|
@ -20,6 +20,7 @@
|
|||
--color-background: lch(var(--background-lightness) var(--background-chroma) var(--background-hue));
|
||||
--color-foreground-trace: lch(var(--foreground-lightness) var(--foreground-chroma) var(--foreground-hue) / 0.1);
|
||||
--color-foreground-faint: lch(var(--foreground-lightness) var(--foreground-chroma) var(--foreground-hue) / 0.25);
|
||||
--color-foreground-medium: lch(var(--foreground-lightness) var(--foreground-chroma) var(--foreground-hue) / 0.5);
|
||||
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--color-background);
|
||||
|
@ -112,20 +113,21 @@
|
|||
user-select: none;
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
padding: calc(1 * var(--unit));
|
||||
padding: calc(0.5 * var(--unit));
|
||||
border-radius: calc(1 * var(--unit));
|
||||
background-color: var(--color-foreground-trace);
|
||||
font-size: calc(5 * var(--unit));
|
||||
font-weight: 600;
|
||||
}
|
||||
.badge {
|
||||
font-size: calc(3 * var(--unit));
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
.puzzle-item.is-selected {
|
||||
background-color: var(--color-foreground-faint);
|
||||
background-color: var(--color-foreground-medium);
|
||||
outline: calc(0.33 * var(--unit)) solid var(--color-foreground);
|
||||
}
|
||||
.puzzle-group {
|
||||
|
@ -157,18 +159,17 @@
|
|||
font-weight: 700;
|
||||
}
|
||||
.puzzle-group-members {
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.puzzle-actions {
|
||||
display: flex;
|
||||
margin-top: calc(1 * var(--unit));
|
||||
margin-top: calc(4 * var(--unit));
|
||||
gap: calc(1 * var(--unit));
|
||||
}
|
||||
.puzzle-actions-secondary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(1 * var(--unit));
|
||||
min-width: calc(20 * var(--unit));
|
||||
&>button {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
|
@ -212,6 +213,3 @@ button:focus-visible, button:hover {
|
|||
button:active {
|
||||
box-shadow: 0 0 3em -1.5em inset var(--color-foreground)
|
||||
}
|
||||
#submitButton {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
41
src/App.tsx
41
src/App.tsx
|
@ -3,6 +3,13 @@ import "./App.css";
|
|||
import useAppModel from "./useAppModel";
|
||||
import FitText from "./FitText";
|
||||
|
||||
// TODO
|
||||
// add routing
|
||||
// make overview page with calendar list of puzzles
|
||||
// show solved / aced / busted
|
||||
// make detail page with puzzle id in path
|
||||
// add nav links
|
||||
|
||||
function App() {
|
||||
const {
|
||||
connections,
|
||||
|
@ -12,6 +19,7 @@ function App() {
|
|||
handlePinUnpin,
|
||||
handleSelectGame,
|
||||
handleShuffle,
|
||||
handleDeselect,
|
||||
getFromPuzzle,
|
||||
} = useAppModel();
|
||||
|
||||
|
@ -97,21 +105,24 @@ function App() {
|
|||
)}
|
||||
</For>
|
||||
<div class="puzzle-actions">
|
||||
<div class="puzzle-actions-secondary">
|
||||
<button
|
||||
type="button"
|
||||
disabled={store.selected.length === 0}
|
||||
on:click={handlePinUnpin}
|
||||
>
|
||||
{store.selected.length > 0 &&
|
||||
store.selected.every((x) => x < store.pinnedCount)
|
||||
? "Unpin"
|
||||
: "Pin"}
|
||||
</button>
|
||||
<button type="button" on:click={handleShuffle}>
|
||||
Shuffle
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={store.selected.length === 0}
|
||||
on:click={handlePinUnpin}
|
||||
>
|
||||
{store.selected.length > 0 &&
|
||||
store.selected.every((x) => x < store.pinnedCount)
|
||||
? "Unpin"
|
||||
: "Pin"}
|
||||
</button>
|
||||
<button type="button" on:click={handleShuffle}>
|
||||
Shuffle
|
||||
</button>
|
||||
<button type="button" on:click={handleDeselect} disabled={
|
||||
store.selected.length === 0
|
||||
}>
|
||||
Deselect all
|
||||
</button>
|
||||
<button
|
||||
id="submitButton"
|
||||
type="button"
|
||||
|
|
|
@ -1,32 +1,25 @@
|
|||
import { createEffect, createSignal, on, type Accessor } from "solid-js";
|
||||
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);
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
props.body,
|
||||
async () => {
|
||||
setWidth(100)
|
||||
setHeight(100)
|
||||
await new Promise(resolve => setTimeout(resolve, 1))
|
||||
const bounds = textRef?.getBBox();
|
||||
if (bounds === undefined) {
|
||||
return;
|
||||
}
|
||||
setWidth(bounds.width);
|
||||
setHeight(bounds.height);
|
||||
}
|
||||
)
|
||||
)
|
||||
createEffect(() => {
|
||||
props.body();
|
||||
const bounds = textRef?.getBBox();
|
||||
if (bounds === undefined) {
|
||||
return;
|
||||
}
|
||||
setWidth(bounds.width);
|
||||
setHeight(bounds.height);
|
||||
});
|
||||
|
||||
return (
|
||||
<svg
|
||||
style={{
|
||||
width: "100%",
|
||||
"max-height": "50%",
|
||||
"max-height": "40%",
|
||||
}}
|
||||
viewBox={`0 0 ${width()} ${height()}`}
|
||||
overflow="visible"
|
||||
|
@ -38,8 +31,9 @@ export default function FitText(props: { body: Accessor<string> }) {
|
|||
y="50%"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
font-size="1rem"
|
||||
font-size="30px"
|
||||
font-family="inherit"
|
||||
font-weight="inherit"
|
||||
>
|
||||
{props.body()}
|
||||
</text>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { makePersisted } from "@solid-primitives/storage";
|
||||
import connections from "./assets/connections.json";
|
||||
import { shuffleArray } from "./utils";
|
||||
import { createStore } from "solid-js/store";
|
||||
import { tauriStorage } from "@solid-primitives/storage/tauri";
|
||||
|
||||
type Connection = (typeof connections)[number];
|
||||
type Answer = Connection["answers"][number];
|
||||
|
@ -11,17 +13,34 @@ function fromIndex(index: number): [number, number] {
|
|||
return [row, col];
|
||||
}
|
||||
|
||||
type AppStore = {
|
||||
puzzleId: number;
|
||||
pinnedCount: number;
|
||||
selected: number[];
|
||||
solvedGroups: Answer[];
|
||||
puzzle: number[];
|
||||
get answers(): Answer[];
|
||||
};
|
||||
|
||||
export default function useAppModel() {
|
||||
const [store, setStore] = createStore({
|
||||
puzzleId: 1,
|
||||
pinnedCount: 0,
|
||||
selected: [] as number[],
|
||||
solvedGroups: [] as Answer[],
|
||||
puzzle: shuffleArray(Array.from({ length: 16 }, (_, i) => i)),
|
||||
get answers() {
|
||||
return connections.find((x) => x.id === store.puzzleId)!.answers;
|
||||
},
|
||||
});
|
||||
const storage =
|
||||
"__TAURI_INTERNALS__" in window ? tauriStorage("AppStore") : localStorage;
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore<AppStore>({
|
||||
puzzleId: 1,
|
||||
pinnedCount: 0,
|
||||
selected: [],
|
||||
solvedGroups: [],
|
||||
puzzle: shuffleArray(Array.from({ length: 16 }, (_, i) => i)),
|
||||
get answers(): Answer[] {
|
||||
return connections.find((x) => x.id === store.puzzleId)!.answers;
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "slick-connections",
|
||||
storage,
|
||||
}
|
||||
);
|
||||
|
||||
const handleSelectGame = (newId: number) => {
|
||||
setStore({
|
||||
|
@ -83,6 +102,12 @@ export default function useAppModel() {
|
|||
});
|
||||
};
|
||||
|
||||
const handleDeselect = () => {
|
||||
setStore({
|
||||
selected: [],
|
||||
});
|
||||
};
|
||||
|
||||
const handlePinUnpin = () => {
|
||||
if (store.selected.every((x) => x < store.pinnedCount)) {
|
||||
// we are unpinning
|
||||
|
@ -125,6 +150,7 @@ export default function useAppModel() {
|
|||
handlePinUnpin,
|
||||
handleSelectGame,
|
||||
handleShuffle,
|
||||
handleDeselect,
|
||||
getFromPuzzle,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue