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",
|
"version": "0.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@solid-primitives/storage": "^4.3.1",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
|
"@tauri-apps/plugin-store": "^2.2.0",
|
||||||
"solid-js": "^1.9.3"
|
"solid-js": "^1.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -1048,6 +1050,36 @@
|
||||||
"win32"
|
"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": {
|
"node_modules/@tauri-apps/api": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.4.1.tgz",
|
||||||
|
@ -1284,6 +1316,15 @@
|
||||||
"@tauri-apps/api": "^2.0.0"
|
"@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": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@solid-primitives/storage": "^4.3.1",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
|
"@tauri-apps/plugin-store": "^2.2.0",
|
||||||
"solid-js": "^1.9.3"
|
"solid-js": "^1.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
29
src-tauri/Cargo.lock
generated
29
src-tauri/Cargo.lock
generated
|
@ -3282,6 +3282,7 @@ dependencies = [
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"tauri-plugin-store",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3665,6 +3666,22 @@ dependencies = [
|
||||||
"zbus",
|
"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]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.5.1"
|
version = "2.5.1"
|
||||||
|
@ -3883,9 +3900,21 @@ dependencies = [
|
||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.14"
|
version = "0.7.14"
|
||||||
|
|
|
@ -22,4 +22,5 @@ tauri = { version = "2", features = [] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
tauri-plugin-store = "2"
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,17 @@
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "Capability for the main window",
|
"description": "Capability for the main window",
|
||||||
"windows": ["main"],
|
"windows": [
|
||||||
|
"main"
|
||||||
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"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)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.invoke_handler(tauri::generate_handler![greet])
|
.invoke_handler(tauri::generate_handler![greet])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
|
|
@ -2,5 +2,10 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
fn main() {
|
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()
|
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-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-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-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);
|
color: var(--color-foreground);
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
|
@ -112,20 +113,21 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: calc(1 * var(--unit));
|
padding: calc(0.5 * var(--unit));
|
||||||
border-radius: calc(1 * var(--unit));
|
border-radius: calc(1 * var(--unit));
|
||||||
background-color: var(--color-foreground-trace);
|
background-color: var(--color-foreground-trace);
|
||||||
font-size: calc(5 * var(--unit));
|
font-size: calc(5 * var(--unit));
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.badge {
|
.badge {
|
||||||
|
font-size: calc(3 * var(--unit));
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
.puzzle-item.is-selected {
|
.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);
|
outline: calc(0.33 * var(--unit)) solid var(--color-foreground);
|
||||||
}
|
}
|
||||||
.puzzle-group {
|
.puzzle-group {
|
||||||
|
@ -157,18 +159,17 @@
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.puzzle-group-members {
|
.puzzle-group-members {
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
.puzzle-actions {
|
.puzzle-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: calc(1 * var(--unit));
|
margin-top: calc(4 * var(--unit));
|
||||||
gap: calc(1 * var(--unit));
|
gap: calc(1 * var(--unit));
|
||||||
}
|
&>button {
|
||||||
.puzzle-actions-secondary {
|
flex-basis: 0;
|
||||||
display: flex;
|
flex-grow: 1;
|
||||||
flex-direction: column;
|
}
|
||||||
gap: calc(1 * var(--unit));
|
|
||||||
min-width: calc(20 * var(--unit));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
@ -212,6 +213,3 @@ button:focus-visible, button:hover {
|
||||||
button:active {
|
button:active {
|
||||||
box-shadow: 0 0 3em -1.5em inset var(--color-foreground)
|
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 useAppModel from "./useAppModel";
|
||||||
import FitText from "./FitText";
|
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() {
|
function App() {
|
||||||
const {
|
const {
|
||||||
connections,
|
connections,
|
||||||
|
@ -12,6 +19,7 @@ function App() {
|
||||||
handlePinUnpin,
|
handlePinUnpin,
|
||||||
handleSelectGame,
|
handleSelectGame,
|
||||||
handleShuffle,
|
handleShuffle,
|
||||||
|
handleDeselect,
|
||||||
getFromPuzzle,
|
getFromPuzzle,
|
||||||
} = useAppModel();
|
} = useAppModel();
|
||||||
|
|
||||||
|
@ -97,21 +105,24 @@ function App() {
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
<div class="puzzle-actions">
|
<div class="puzzle-actions">
|
||||||
<div class="puzzle-actions-secondary">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
disabled={store.selected.length === 0}
|
||||||
disabled={store.selected.length === 0}
|
on:click={handlePinUnpin}
|
||||||
on:click={handlePinUnpin}
|
>
|
||||||
>
|
{store.selected.length > 0 &&
|
||||||
{store.selected.length > 0 &&
|
store.selected.every((x) => x < store.pinnedCount)
|
||||||
store.selected.every((x) => x < store.pinnedCount)
|
? "Unpin"
|
||||||
? "Unpin"
|
: "Pin"}
|
||||||
: "Pin"}
|
</button>
|
||||||
</button>
|
<button type="button" on:click={handleShuffle}>
|
||||||
<button type="button" on:click={handleShuffle}>
|
Shuffle
|
||||||
Shuffle
|
</button>
|
||||||
</button>
|
<button type="button" on:click={handleDeselect} disabled={
|
||||||
</div>
|
store.selected.length === 0
|
||||||
|
}>
|
||||||
|
Deselect all
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
id="submitButton"
|
id="submitButton"
|
||||||
type="button"
|
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> }) {
|
export default function FitText(props: { body: Accessor<string> }) {
|
||||||
let textRef: SVGTextElement | undefined;
|
let textRef: SVGTextElement | undefined;
|
||||||
const [width, setWidth] = createSignal(100);
|
const [width, setWidth] = createSignal(100);
|
||||||
const [height, setHeight] = createSignal(100);
|
const [height, setHeight] = createSignal(100);
|
||||||
|
|
||||||
createEffect(
|
createEffect(() => {
|
||||||
on(
|
props.body();
|
||||||
props.body,
|
const bounds = textRef?.getBBox();
|
||||||
async () => {
|
if (bounds === undefined) {
|
||||||
setWidth(100)
|
return;
|
||||||
setHeight(100)
|
}
|
||||||
await new Promise(resolve => setTimeout(resolve, 1))
|
setWidth(bounds.width);
|
||||||
const bounds = textRef?.getBBox();
|
setHeight(bounds.height);
|
||||||
if (bounds === undefined) {
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
setWidth(bounds.width);
|
|
||||||
setHeight(bounds.height);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
"max-height": "50%",
|
"max-height": "40%",
|
||||||
}}
|
}}
|
||||||
viewBox={`0 0 ${width()} ${height()}`}
|
viewBox={`0 0 ${width()} ${height()}`}
|
||||||
overflow="visible"
|
overflow="visible"
|
||||||
|
@ -38,8 +31,9 @@ export default function FitText(props: { body: Accessor<string> }) {
|
||||||
y="50%"
|
y="50%"
|
||||||
dominant-baseline="central"
|
dominant-baseline="central"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
font-size="1rem"
|
font-size="30px"
|
||||||
font-family="inherit"
|
font-family="inherit"
|
||||||
|
font-weight="inherit"
|
||||||
>
|
>
|
||||||
{props.body()}
|
{props.body()}
|
||||||
</text>
|
</text>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { makePersisted } from "@solid-primitives/storage";
|
||||||
import connections from "./assets/connections.json";
|
import connections from "./assets/connections.json";
|
||||||
import { shuffleArray } from "./utils";
|
import { shuffleArray } from "./utils";
|
||||||
import { createStore } from "solid-js/store";
|
import { createStore } from "solid-js/store";
|
||||||
|
import { tauriStorage } from "@solid-primitives/storage/tauri";
|
||||||
|
|
||||||
type Connection = (typeof connections)[number];
|
type Connection = (typeof connections)[number];
|
||||||
type Answer = Connection["answers"][number];
|
type Answer = Connection["answers"][number];
|
||||||
|
@ -11,17 +13,34 @@ function fromIndex(index: number): [number, number] {
|
||||||
return [row, col];
|
return [row, col];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppStore = {
|
||||||
|
puzzleId: number;
|
||||||
|
pinnedCount: number;
|
||||||
|
selected: number[];
|
||||||
|
solvedGroups: Answer[];
|
||||||
|
puzzle: number[];
|
||||||
|
get answers(): Answer[];
|
||||||
|
};
|
||||||
|
|
||||||
export default function useAppModel() {
|
export default function useAppModel() {
|
||||||
const [store, setStore] = createStore({
|
const storage =
|
||||||
puzzleId: 1,
|
"__TAURI_INTERNALS__" in window ? tauriStorage("AppStore") : localStorage;
|
||||||
pinnedCount: 0,
|
const [store, setStore] = makePersisted(
|
||||||
selected: [] as number[],
|
createStore<AppStore>({
|
||||||
solvedGroups: [] as Answer[],
|
puzzleId: 1,
|
||||||
puzzle: shuffleArray(Array.from({ length: 16 }, (_, i) => i)),
|
pinnedCount: 0,
|
||||||
get answers() {
|
selected: [],
|
||||||
return connections.find((x) => x.id === store.puzzleId)!.answers;
|
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) => {
|
const handleSelectGame = (newId: number) => {
|
||||||
setStore({
|
setStore({
|
||||||
|
@ -83,6 +102,12 @@ export default function useAppModel() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeselect = () => {
|
||||||
|
setStore({
|
||||||
|
selected: [],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handlePinUnpin = () => {
|
const handlePinUnpin = () => {
|
||||||
if (store.selected.every((x) => x < store.pinnedCount)) {
|
if (store.selected.every((x) => x < store.pinnedCount)) {
|
||||||
// we are unpinning
|
// we are unpinning
|
||||||
|
@ -125,6 +150,7 @@ export default function useAppModel() {
|
||||||
handlePinUnpin,
|
handlePinUnpin,
|
||||||
handleSelectGame,
|
handleSelectGame,
|
||||||
handleShuffle,
|
handleShuffle,
|
||||||
|
handleDeselect,
|
||||||
getFromPuzzle,
|
getFromPuzzle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue