Compare commits
No commits in common. "main" and "gh-pages" have entirely different histories.
21
README.md
|
@ -1,21 +0,0 @@
|
||||||
# Tauri + Solid + Typescript
|
|
||||||
|
|
||||||
This is a side project to reimplement the NYT game Connections, but with a feature to pin words so they are unaffected by Shuffle. I also wanted to make some UI improvements over the connections archive app I have been using on my phone.
|
|
||||||
|
|
||||||
I made this hoping to get some exposure to Tauri and SolidJS.
|
|
||||||
|
|
||||||
## Dev setup
|
|
||||||
|
|
||||||
`npm i` should install everything you need.
|
|
||||||
`npm run` will list the possible commands:
|
|
||||||
- `start` / `dev`: live development
|
|
||||||
- `build`: bundle web assets to `/dist`
|
|
||||||
- `serve`: serve web assets locally
|
|
||||||
- `tauri`: send commands to tauri CLI ([reference](https://v2.tauri.app/reference/cli/))
|
|
||||||
|
|
||||||
## Recommended IDE Setup
|
|
||||||
|
|
||||||
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
|
||||||
|
|
||||||
## Connections data
|
|
||||||
Data from https://github.com/Eyefyre/NYT-Connections-Answers/blob/main/connections.json
|
|
1
assets/index-64ysZdl6.css
Normal file
3
assets/index-CbWMZWYQ.js
Normal file
1
assets/index-CiTuSxU_.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
function c(e,r,t,a){if(typeof r=="function"?e!==r||!0:!r.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return t==="m"?a:t==="a"?a.call(e):a?a.value:r.get(e)}function p(e,r,t,a,o){if(typeof r=="function"?e!==r||!0:!r.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return r.set(e,t),t}var s;function y(e,r=!1){return window.__TAURI_INTERNALS__.transformCallback(e,r)}async function i(e,r={},t){return window.__TAURI_INTERNALS__.invoke(e,r,t)}class h{get rid(){return c(this,s,"f")}constructor(r){s.set(this,void 0),p(this,s,r)}async close(){return i("plugin:resources|close",{rid:this.rid})}}s=new WeakMap;var d;(function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"})(d||(d={}));async function g(e,r){await i("plugin:event|unlisten",{event:e,eventId:r})}async function u(e,r,t){var a;const o=(a=void 0)!==null&&a!==void 0?a:{kind:"Any"};return i("plugin:event|listen",{event:e,target:o,handler:y(r)}).then(l=>async()=>g(e,l))}class n extends h{constructor(r){super(r)}static async load(r,t){const a=await i("plugin:store|load",{path:r,...t});return new n(a)}static async get(r){return await i("plugin:store|get_store",{path:r}).then(t=>t?new n(t):null)}async set(r,t){await i("plugin:store|set",{rid:this.rid,key:r,value:t})}async get(r){const[t,a]=await i("plugin:store|get",{rid:this.rid,key:r});return a?t:void 0}async has(r){return await i("plugin:store|has",{rid:this.rid,key:r})}async delete(r){return await i("plugin:store|delete",{rid:this.rid,key:r})}async clear(){await i("plugin:store|clear",{rid:this.rid})}async reset(){await i("plugin:store|reset",{rid:this.rid})}async keys(){return await i("plugin:store|keys",{rid:this.rid})}async values(){return await i("plugin:store|values",{rid:this.rid})}async entries(){return await i("plugin:store|entries",{rid:this.rid})}async length(){return await i("plugin:store|length",{rid:this.rid})}async reload(){await i("plugin:store|reload",{rid:this.rid})}async save(){await i("plugin:store|save",{rid:this.rid})}async onKeyChange(r,t){return await u("store://change",a=>{a.payload.resourceId===this.rid&&a.payload.key===r&&t(a.payload.exists?a.payload.value:void 0)})}async onChange(r){return await u("store://change",t=>{t.payload.resourceId===this.rid&&r(t.payload.key,t.payload.exists?t.payload.value:void 0)})}}export{n as Store};
|
|
@ -6,12 +6,13 @@
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/src/assets/logo.svg" />
|
<link rel="icon" type="image/svg+xml" href="/src/assets/logo.svg" />
|
||||||
<title>Slick Connections</title>
|
<title>Slick Connections</title>
|
||||||
|
<script type="module" crossorigin src="/assets/index-CbWMZWYQ.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-64ysZdl6.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<script src="/src/index.tsx" type="module"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
2684
package-lock.json
generated
32
package.json
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
"name": "slick-connections",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"start": "vite",
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"serve": "vite preview",
|
|
||||||
"tauri": "tauri",
|
|
||||||
"publish": "vite build && gh-pages -d dist"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@solid-primitives/storage": "^4.3.1",
|
|
||||||
"@solidjs/router": "^0.15.3",
|
|
||||||
"@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"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tauri-apps/cli": "^2",
|
|
||||||
"gh-pages": "^6.3.0",
|
|
||||||
"typescript": "~5.6.2",
|
|
||||||
"vite": "^6.0.3",
|
|
||||||
"vite-plugin-solid": "^2.11.0"
|
|
||||||
}
|
|
||||||
}
|
|
5239
src-tauri/Cargo.lock
generated
|
@ -1,26 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "slick-connections"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "A Tauri App"
|
|
||||||
authors = ["you"]
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
# The `_lib` suffix may seem redundant but it is necessary
|
|
||||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
|
||||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
|
||||||
name = "slick_connections_lib"
|
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
tauri-build = { version = "2", features = [] }
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tauri = { version = "2", features = [] }
|
|
||||||
tauri-plugin-opener = "2"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1"
|
|
||||||
tauri-plugin-store = "2"
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
fn main() {
|
|
||||||
tauri_build::build()
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
|
||||||
"identifier": "default",
|
|
||||||
"description": "Capability for the main window",
|
|
||||||
"windows": [
|
|
||||||
"main"
|
|
||||||
],
|
|
||||||
"permissions": [
|
|
||||||
"core:default",
|
|
||||||
"opener:default",
|
|
||||||
"store:allow-get",
|
|
||||||
"store:allow-set",
|
|
||||||
"store:allow-delete",
|
|
||||||
"store:allow-keys",
|
|
||||||
"store:allow-clear",
|
|
||||||
"store:default"
|
|
||||||
]
|
|
||||||
}
|
|
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 974 B |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 903 B |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,15 +0,0 @@
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
|
||||||
#[tauri::command]
|
|
||||||
fn greet(name: &str) -> String {
|
|
||||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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!())
|
|
||||||
.expect("error while running tauri application");
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
|
||||||
#![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()
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
|
||||||
"productName": "slick-connections",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"identifier": "com.slick-connections.app",
|
|
||||||
"build": {
|
|
||||||
"beforeDevCommand": "npm run dev",
|
|
||||||
"devUrl": "http://localhost:1420",
|
|
||||||
"beforeBuildCommand": "npm run build",
|
|
||||||
"frontendDist": "../dist"
|
|
||||||
},
|
|
||||||
"app": {
|
|
||||||
"windows": [
|
|
||||||
{
|
|
||||||
"title": "slick-connections",
|
|
||||||
"width": 800,
|
|
||||||
"height": 600
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"security": {
|
|
||||||
"csp": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"targets": "all",
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
261
src/App.css
|
@ -1,261 +0,0 @@
|
||||||
*, :before, :after {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
--background-lightness: 90%;
|
|
||||||
--background-chroma: 0;
|
|
||||||
--background-hue: 0;
|
|
||||||
--foreground-lightness: 10%;
|
|
||||||
--foreground-chroma: 0;
|
|
||||||
--foreground-hue: 0;
|
|
||||||
|
|
||||||
--color-foreground: lch(var(--foreground-lightness) var(--foreground-chroma) var(--foreground-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-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);
|
|
||||||
|
|
||||||
/* --group-green: lch(74.83% 54.18 117.22);
|
|
||||||
--group-blue: lch(78.8% 23.7 270.9);
|
|
||||||
--group-yellow: lch(89.36% 57.75 90.6);
|
|
||||||
--group-purple: lch(61.69% 41.01 319.57); */
|
|
||||||
--group-green: lch(74.83% 54.18 117.22);
|
|
||||||
--group-blue: lch(75% 23.7 270.9);
|
|
||||||
--group-yellow: lch(85% 57.75 90.6);
|
|
||||||
--group-purple: lch(65% 41.01 319.57);
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background-lightness: 10%;
|
|
||||||
--background-chroma: 0;
|
|
||||||
--background-hue: 0;
|
|
||||||
--foreground-lightness: 90%;
|
|
||||||
--foreground-chroma: 0;
|
|
||||||
--foreground-hue: 0;
|
|
||||||
|
|
||||||
--group-blue: lch(30% 23.7 270.9);
|
|
||||||
--group-green: lch(30% 54.18 117.22);
|
|
||||||
--group-yellow: lch(30% 57.75 90.6);
|
|
||||||
--group-purple: lch(30% 41.01 319.57);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
margin: 0;
|
|
||||||
padding-top: 10vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.puzzle {
|
|
||||||
--unit: min(0.5rem, 1vw);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: calc(1 * var(--unit));
|
|
||||||
font-size: calc(3 * var(--unit));
|
|
||||||
width: calc((20 * 4 + 3) * var(--unit));
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.puzzle-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
flex-direction: row;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
> h2 {
|
|
||||||
font-size: 2em;
|
|
||||||
line-height: 1;
|
|
||||||
flex-grow: 1;
|
|
||||||
text-align: left;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.puzzle-header-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: calc(1 * var(--unit));
|
|
||||||
& button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.puzzle-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
height: calc(12 * var(--unit));
|
|
||||||
gap: calc(1 * var(--unit));
|
|
||||||
}
|
|
||||||
.puzzle-item {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
flex-basis: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
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-medium);
|
|
||||||
outline: calc(0.33 * var(--unit)) solid var(--color-foreground);
|
|
||||||
}
|
|
||||||
.puzzle-group {
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: calc(1 * var(--unit));
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&[data-level="0"] {
|
|
||||||
background-color: var(--group-yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-level="1"] {
|
|
||||||
background-color: var(--group-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-level="2"] {
|
|
||||||
background-color: var(--group-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-level="3"] {
|
|
||||||
background-color: var(--group-purple);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.puzzle-group-name {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.puzzle-group-members {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
.puzzle-actions {
|
|
||||||
display: flex;
|
|
||||||
margin-top: calc(4 * var(--unit));
|
|
||||||
gap: calc(1 * var(--unit));
|
|
||||||
&>button {
|
|
||||||
flex-basis: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
font-size: 0.8em;
|
|
||||||
line-height: 1;
|
|
||||||
margin: 0;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #646cff;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
appearance: none;
|
|
||||||
overflow: visible;
|
|
||||||
min-width: 2em;
|
|
||||||
background: var(--color-foreground-faint);
|
|
||||||
color: var(--color-foreground);
|
|
||||||
font-size: inherit;
|
|
||||||
border: none;
|
|
||||||
padding: 0.5em;
|
|
||||||
border-radius: calc(1 * var(--unit, 0.5em));
|
|
||||||
}
|
|
||||||
button:disabled {
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
|
||||||
button:focus-visible, button:hover {
|
|
||||||
box-shadow: 0 0 3em -1.5em inset var(--color-foreground)
|
|
||||||
}
|
|
||||||
button:active {
|
|
||||||
box-shadow: 0 0 3em -1.5em inset var(--color-foreground)
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: scale(1.5) rotate(-10deg) }
|
|
||||||
to { transform: scale(1.5) rotate(10deg) }
|
|
||||||
}
|
|
||||||
.celebration {
|
|
||||||
z-index: -1;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
background-image: repeating-conic-gradient(
|
|
||||||
var(--group-yellow) 0deg 5deg,
|
|
||||||
var(--group-green) 5deg 10deg,
|
|
||||||
var(--group-blue) 10deg 15deg,
|
|
||||||
var(--group-purple) 15deg 20deg
|
|
||||||
);
|
|
||||||
animation: spin 2s linear infinite;
|
|
||||||
}
|
|
||||||
&:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 2rem;
|
|
||||||
right: 2rem;
|
|
||||||
bottom: 2rem;
|
|
||||||
left: 2rem;
|
|
||||||
background-color: var(--color-background);
|
|
||||||
box-shadow: 0 0 1rem 1rem var(--color-background);
|
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
18
src/App.tsx
|
@ -1,18 +0,0 @@
|
||||||
import { type JSX } from "solid-js"
|
|
||||||
|
|
||||||
type AppProps = {
|
|
||||||
children?: JSX.Element
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App({ children }: AppProps) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<header>
|
|
||||||
<h1>Slick Connections</h1>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
import { For, Show, createMemo } from "solid-js";
|
|
||||||
import useAppModel from "./useAppModel";
|
|
||||||
import { css } from "vite-plugin-inline-css-modules";
|
|
||||||
|
|
||||||
const styles = css`
|
|
||||||
.calendarWrapper {
|
|
||||||
column-width: 14em;
|
|
||||||
}
|
|
||||||
.calendar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.25em;
|
|
||||||
}
|
|
||||||
.calendarHeader {
|
|
||||||
flex-shrink: 1;
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid var(--color-foreground-trace);
|
|
||||||
}
|
|
||||||
.entry {
|
|
||||||
break-before: avoid-column;
|
|
||||||
break-inside: avoid-column;
|
|
||||||
width: 2em;
|
|
||||||
display: inline-block;
|
|
||||||
height: 2em;
|
|
||||||
border-radius: 10%;
|
|
||||||
background-color: var(--color-foreground-faint);
|
|
||||||
&:empty {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.entryDate {
|
|
||||||
color: var(--color-foreground);
|
|
||||||
margin: 0.2em;
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
.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>
|
|
||||||
<Show when={nextUnsolvedId() !== undefined}>
|
|
||||||
<a class={styles.nextPuzzle} href={`/puzzle/${nextUnsolvedId()}`}>
|
|
||||||
Next puzzle: #{nextUnsolvedId()}
|
|
||||||
</a>
|
|
||||||
</Show>
|
|
||||||
<div class={styles.calendarWrapper}>
|
|
||||||
<div class={styles.calendar}>
|
|
||||||
<For each={connections}>
|
|
||||||
{(item) => {
|
|
||||||
const isSolved = (store.solutions ?? [])[item.id] !== undefined;
|
|
||||||
const date = new Date(item.date);
|
|
||||||
const showHeader = item.id === 1 || date.getDate() === 1;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{showHeader && (
|
|
||||||
<>
|
|
||||||
<div class={styles.calendarHeader}>
|
|
||||||
{date.toLocaleString("default", {
|
|
||||||
month: "long",
|
|
||||||
year: "numeric",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<For each={Array.from({ length: date.getDay() })}>
|
|
||||||
{() => (
|
|
||||||
<div class={`${styles.entry} ${styles.entryBlank}`} />
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<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}`}
|
|
||||||
>
|
|
||||||
<div class={styles.entryDate}>{date.getDate()}</div>
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { createEffect, createSignal, type Accessor } from "solid-js";
|
|
||||||
|
|
||||||
export default function FitText(props: { body: Accessor<string> }) {
|
|
||||||
let textRef: SVGTextElement | undefined;
|
|
||||||
const [viewBox, setViewbox] = createSignal<string | undefined>()
|
|
||||||
|
|
||||||
createEffect(async () => {
|
|
||||||
props.body();
|
|
||||||
await Promise.resolve()
|
|
||||||
const bounds = textRef?.getBBox();
|
|
||||||
if (bounds === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setViewbox(`0 0 ${bounds.width} ${bounds.height}`)
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
"max-height": "40%",
|
|
||||||
}}
|
|
||||||
viewBox={viewBox()}
|
|
||||||
fill="currentcolor"
|
|
||||||
>
|
|
||||||
<text
|
|
||||||
ref={textRef}
|
|
||||||
x="50%"
|
|
||||||
y="50%"
|
|
||||||
dominant-baseline="middle"
|
|
||||||
text-anchor="middle"
|
|
||||||
font-size="30px"
|
|
||||||
font-family="inherit"
|
|
||||||
font-weight="inherit"
|
|
||||||
>
|
|
||||||
{props.body()}
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
148
src/Puzzle.tsx
|
@ -1,148 +0,0 @@
|
||||||
import { For, Show } from "solid-js";
|
|
||||||
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
|
|
||||||
// make overview page with calendar list of puzzles
|
|
||||||
// show solved / aced / busted
|
|
||||||
// make detail page with puzzle id in path
|
|
||||||
// add nav links
|
|
||||||
|
|
||||||
function Puzzle() {
|
|
||||||
const params = useParams<{ id: string }>()
|
|
||||||
const id = () => parseInt(params.id);
|
|
||||||
|
|
||||||
const {
|
|
||||||
connections,
|
|
||||||
store,
|
|
||||||
setStore,
|
|
||||||
handleGuess,
|
|
||||||
handlePinUnpin,
|
|
||||||
handleShuffle,
|
|
||||||
handleDeselect,
|
|
||||||
getFromPuzzle,
|
|
||||||
} = usePuzzleModel(id);
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main class="container">
|
|
||||||
<div class="puzzle">
|
|
||||||
<header class="puzzle-header">
|
|
||||||
<h2>#{id()}</h2>
|
|
||||||
<div class="puzzle-header-actions">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
navigate(`/puzzle/${id() - 1}`);
|
|
||||||
}}
|
|
||||||
disabled={id() === 1}
|
|
||||||
>
|
|
||||||
<TbArrowLeft />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
navigate('/');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TbArrowUp />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
navigate(`/puzzle/${id() + 1}`);
|
|
||||||
}}
|
|
||||||
disabled={id() === connections.length - 1}
|
|
||||||
>
|
|
||||||
<TbArrowRight />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div>Create four groups of four words!</div>
|
|
||||||
<For each={store.solvedGroups}>
|
|
||||||
{({ group, level, members }) => (
|
|
||||||
<div class="puzzle-row">
|
|
||||||
<div class="puzzle-group" data-level={level}>
|
|
||||||
<div class="puzzle-group-name">{group}</div>
|
|
||||||
<div class="puzzle-group-members">{members.join(", ")}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
<For each={[0, 1, 2, 3].slice(0, store.puzzle.length / 4)}>
|
|
||||||
{(row) => (
|
|
||||||
<div class="puzzle-row">
|
|
||||||
{[0, 1, 2, 3].map((col) => {
|
|
||||||
const index = 4 * row + col;
|
|
||||||
const answer = () => getFromPuzzle(index).answer;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
classList={{
|
|
||||||
"puzzle-item": true,
|
|
||||||
"is-selected": store.selected.includes(index),
|
|
||||||
}}
|
|
||||||
style={{ "view-transition-name": `puzzle-${answer}` }}
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
setStore({
|
|
||||||
selected: store.selected.includes(index)
|
|
||||||
? store.selected.filter((x) => x !== index)
|
|
||||||
: [...store.selected, index],
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FitText body={answer} />
|
|
||||||
{store.pinnedCount > index && <div class="badge">🔒</div>}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
<div class="puzzle-actions">
|
|
||||||
<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"
|
|
||||||
on:click={handleGuess}
|
|
||||||
disabled={store.selected.length !== 4}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Show when={store.solvedGroups.length === 4}>
|
|
||||||
<pre on:click={() => navigator.clipboard.writeText(store.guessHistory)}>
|
|
||||||
{store.guessHistory}
|
|
||||||
</pre>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
{store.solvedGroups.length === 4 && <div class="celebration" />}
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Puzzle;
|
|
|
@ -1,22 +0,0 @@
|
||||||
/* @refresh reload */
|
|
||||||
import { render } from "solid-js/web";
|
|
||||||
import App from "./App";
|
|
||||||
import { Route, Router } from "@solidjs/router";
|
|
||||||
import Dashboard from "./Dashboard";
|
|
||||||
import Puzzle from "./Puzzle";
|
|
||||||
|
|
||||||
render(
|
|
||||||
() => (
|
|
||||||
<Router root={App}>
|
|
||||||
<Route path="/" component={Dashboard} />
|
|
||||||
<Route
|
|
||||||
path="/puzzle/:id"
|
|
||||||
component={Puzzle}
|
|
||||||
matchFilters={{
|
|
||||||
id: /^\d+$/,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Router>
|
|
||||||
),
|
|
||||||
document.getElementById("root") as HTMLElement
|
|
||||||
);
|
|
|
@ -1,45 +0,0 @@
|
||||||
import { makePersisted } from "@solid-primitives/storage";
|
|
||||||
import connections from "./assets/connections.json";
|
|
||||||
import { createStore } from "solid-js/store";
|
|
||||||
import { tauriStorage } from "@solid-primitives/storage/tauri";
|
|
||||||
|
|
||||||
type Solution = {
|
|
||||||
id: number
|
|
||||||
guesses: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppStore = {
|
|
||||||
solutions: Solution[]
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function useAppModel() {
|
|
||||||
const storage =
|
|
||||||
"__TAURI_INTERNALS__" in window ? tauriStorage("AppStore") : localStorage;
|
|
||||||
const [store, setStore] = makePersisted(
|
|
||||||
createStore<AppStore>({
|
|
||||||
solutions: []
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
name: "slick-connections",
|
|
||||||
storage,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
function setSolution(id: number, guesses: number) {
|
|
||||||
const nextSolutions = [
|
|
||||||
...(store.solutions ?? []),
|
|
||||||
];
|
|
||||||
nextSolutions[id] = {
|
|
||||||
id,
|
|
||||||
guesses,
|
|
||||||
}
|
|
||||||
setStore({
|
|
||||||
solutions: nextSolutions
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
connections,
|
|
||||||
store,
|
|
||||||
setSolution,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
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];
|
|
||||||
|
|
||||||
function fromIndex(index: number): [number, number] {
|
|
||||||
const col = index % 4;
|
|
||||||
const row = (index - col) / 4;
|
|
||||||
return [row, col];
|
|
||||||
}
|
|
||||||
|
|
||||||
type PuzzleStore = {
|
|
||||||
guesses: number;
|
|
||||||
pinnedCount: number;
|
|
||||||
selected: number[];
|
|
||||||
solvedGroups: Answer[];
|
|
||||||
puzzle: number[];
|
|
||||||
guessHistory: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const emoji = ["🟨", "🟩", "🟦", "🟪"];
|
|
||||||
|
|
||||||
export default function usePuzzleModel(id: Accessor<number>) {
|
|
||||||
const [store, setStore] = createStore<PuzzleStore>({
|
|
||||||
guesses: 0,
|
|
||||||
pinnedCount: 0,
|
|
||||||
selected: [],
|
|
||||||
solvedGroups: [],
|
|
||||||
puzzle: [],
|
|
||||||
guessHistory: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { setSolution } = useAppModel();
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
setStore({
|
|
||||||
guesses: 0,
|
|
||||||
pinnedCount: 0,
|
|
||||||
selected: [],
|
|
||||||
solvedGroups: [],
|
|
||||||
puzzle: shuffleArray(Array.from({ length: 16 }, (_, i) => i)),
|
|
||||||
guessHistory: `Connections
|
|
||||||
Puzzle #${id()}`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const answers = (): Answer[] => {
|
|
||||||
return connections.find((x) => x.id === id())!.answers;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFromPuzzle = (index: number) => {
|
|
||||||
const puzzleIndex = store.puzzle[index];
|
|
||||||
const [groupIndex, memberIndex] = fromIndex(puzzleIndex);
|
|
||||||
const group = answers()[groupIndex];
|
|
||||||
return {
|
|
||||||
group: group.group,
|
|
||||||
level: group.level,
|
|
||||||
answer: answers()[groupIndex].members[memberIndex],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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];
|
|
||||||
const isCorrect = selectedAnswers.every((x) => x.level === level);
|
|
||||||
const guessHistoryLine = selectedAnswers
|
|
||||||
.map((x) => emoji[x.level])
|
|
||||||
.join("");
|
|
||||||
setStore({
|
|
||||||
guessHistory: `${store.guessHistory}
|
|
||||||
${guessHistoryLine}`,
|
|
||||||
});
|
|
||||||
if (!isCorrect) {
|
|
||||||
// TODO you got it wrong
|
|
||||||
alert("wrong");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const selectedPinnedCount = selected.reduce(
|
|
||||||
(acc, cur) => acc + (cur < store.pinnedCount ? 1 : 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
setStore({
|
|
||||||
pinnedCount: store.pinnedCount - selectedPinnedCount,
|
|
||||||
puzzle: store.puzzle.filter((x) =>
|
|
||||||
selected.every((s) => store.puzzle[s] !== x)
|
|
||||||
),
|
|
||||||
selected: [],
|
|
||||||
});
|
|
||||||
const newSolvedGroup = answers().find((x) => x.level === level);
|
|
||||||
if (newSolvedGroup != null) {
|
|
||||||
setStore("solvedGroups", (x) => x.concat(newSolvedGroup));
|
|
||||||
}
|
|
||||||
if (store.puzzle.length === 0) {
|
|
||||||
// completely solved!
|
|
||||||
setSolution(id(), store.guesses);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleShuffle = () => {
|
|
||||||
const pinned = store.puzzle.slice(0, store.pinnedCount);
|
|
||||||
const toShuffle = store.puzzle.slice(store.pinnedCount);
|
|
||||||
setStore({
|
|
||||||
puzzle: [...pinned, ...shuffleArray(toShuffle)],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeselect = () => {
|
|
||||||
setStore({
|
|
||||||
selected: [],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePinUnpin = () => {
|
|
||||||
if (store.selected.every((x) => x < store.pinnedCount)) {
|
|
||||||
// we are unpinning
|
|
||||||
const puzzleStart = Array.from({ length: store.pinnedCount }, (_, i) => i)
|
|
||||||
.filter((x) => !store.selected.includes(x))
|
|
||||||
.map((x) => store.puzzle[x]);
|
|
||||||
const puzzleMiddle = store.selected.map((x) => store.puzzle[x]);
|
|
||||||
const puzzleEnd = store.puzzle.slice(store.pinnedCount);
|
|
||||||
const newPuzzle = [...puzzleStart, ...puzzleMiddle, ...puzzleEnd];
|
|
||||||
setStore({
|
|
||||||
pinnedCount: store.pinnedCount - store.selected.length,
|
|
||||||
selected: [],
|
|
||||||
puzzle: newPuzzle,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// we are pinning
|
|
||||||
const puzzleStart = store.puzzle.slice(0, store.pinnedCount);
|
|
||||||
const puzzleMid = store.selected
|
|
||||||
.filter((x) => x >= store.pinnedCount)
|
|
||||||
.map((x) => store.puzzle[x]);
|
|
||||||
const puzzleEnd = Array.from(
|
|
||||||
{ length: store.puzzle.length - store.pinnedCount },
|
|
||||||
(_, i) => i + store.pinnedCount
|
|
||||||
)
|
|
||||||
.filter((x) => !store.selected.includes(x))
|
|
||||||
.map((x) => store.puzzle[x]);
|
|
||||||
setStore({
|
|
||||||
pinnedCount: puzzleStart.length + puzzleMid.length,
|
|
||||||
selected: [],
|
|
||||||
puzzle: [...puzzleStart, ...puzzleMid, ...puzzleEnd],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
connections,
|
|
||||||
store,
|
|
||||||
setStore,
|
|
||||||
handleGuess,
|
|
||||||
handlePinUnpin,
|
|
||||||
handleShuffle,
|
|
||||||
handleDeselect,
|
|
||||||
getFromPuzzle,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
export function shuffleArray<T>(array: T[]) {
|
|
||||||
const copy = Array.from(array)
|
|
||||||
for (let i = copy.length - 1; i > 0; i--) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
|
||||||
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
||||||
}
|
|
||||||
return copy
|
|
||||||
}
|
|
1
src/vite-env.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
/// <reference types="vite/client" />
|
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"jsxImportSource": "solid-js",
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { defineConfig } from "vite";
|
|
||||||
import solid from "vite-plugin-solid";
|
|
||||||
import vitePluginInlineCSSModules from 'vite-plugin-inline-css-modules'
|
|
||||||
|
|
||||||
// @ts-expect-error process is a nodejs global
|
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig(async () => ({
|
|
||||||
plugins: [solid(), vitePluginInlineCSSModules()],
|
|
||||||
|
|
||||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
|
||||||
//
|
|
||||||
// 1. prevent vite from obscuring rust errors
|
|
||||||
clearScreen: false,
|
|
||||||
// 2. tauri expects a fixed port, fail if that port is not available
|
|
||||||
server: {
|
|
||||||
port: 1420,
|
|
||||||
strictPort: true,
|
|
||||||
host: host || false,
|
|
||||||
hmr: host
|
|
||||||
? {
|
|
||||||
protocol: "ws",
|
|
||||||
host,
|
|
||||||
port: 1421,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
watch: {
|
|
||||||
// 3. tell vite to ignore watching `src-tauri`
|
|
||||||
ignored: ["**/src-tauri/**"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|