Switched to Svelete Kit, without SSR for now

This commit is contained in:
Gender Shrapnel 2022-07-27 04:07:18 +02:00
parent 7d42fff1fb
commit 6b799308da
Signed by: modzero
GPG Key ID: 4E11A06C6D1E5213
43 changed files with 2511 additions and 360 deletions

13
.eslintignore Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

24
.eslintrc.cjs Normal file
View File

@ -0,0 +1,24 @@
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
],
plugins: ["svelte3", "@typescript-eslint"],
ignorePatterns: ["*.cjs"],
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
settings: {
"svelte3/typescript": () => require("typescript"),
},
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
},
env: {
browser: true,
es2017: true,
node: true,
},
};

7
.gitignore vendored
View File

@ -11,6 +11,13 @@ node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
/.svelte-kit
/package
.env
.env.*
!.env.example
.vercel
.output
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

17
.prettierignore Normal file
View File

@ -0,0 +1,17 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
# Tauri and Rust stuff
/src-tauri

1
.prettierrc Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run" type="CargoCommandRunConfiguration" factoryName="Cargo Command" singleton="false">
<option name="command" value="run --manifest-path ./src-tauri/Cargo.toml --no-default-features" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,19 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tauri Dev" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="tauri dev" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,12 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="UI Dev Build" type="js.build_tools.npm" singleton="false">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="dev" />
</scripts>
<node-interpreter value="project" />
<envs />
<method v="2" />
</configuration>
</component>

63
.vscode/launch.json vendored
View File

@ -1,37 +1,32 @@
{ {
// Use IntelliSense to learn about possible attributes. // Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes. // Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"type": "lldb", "type": "lldb",
"request": "launch", "request": "launch",
"name": "Tauri Development Debug", "name": "Tauri Development Debug",
"cargo": { "cargo": {
"args": [ "args": [
"build", "build",
"--manifest-path=./src-tauri/Cargo.toml", "--manifest-path=./src-tauri/Cargo.toml",
"--no-default-features" "--no-default-features"
] ]
},
// task for the `beforeDevCommand` if used, must be configured in `.vscode/tasks.json`
"preLaunchTask": "ui:dev"
}, },
{ // task for the `beforeDevCommand` if used, must be configured in `.vscode/tasks.json`
"type": "lldb", "preLaunchTask": "ui:dev"
"request": "launch", },
"name": "Tauri Production Debug", {
"cargo": { "type": "lldb",
"args": [ "request": "launch",
"build", "name": "Tauri Production Debug",
"--release", "cargo": {
"--manifest-path=./src-tauri/Cargo.toml" "args": ["build", "--release", "--manifest-path=./src-tauri/Cargo.toml"]
]
},
// task for the `beforeBuildCommand` if used, must be configured in `.vscode/tasks.json`
"preLaunchTask": "ui:build"
}, },
] // task for the `beforeBuildCommand` if used, must be configured in `.vscode/tasks.json`
} "preLaunchTask": "ui:build"
}
]
}

74
.vscode/tasks.json vendored
View File

@ -1,45 +1,33 @@
{ {
// See https://go.microsoft.com/fwlink/?LinkId=733558 // See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format // for the documentation about the tasks.json format
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"label": "tauri:dev", "label": "tauri:dev",
"type": "shell", "type": "shell",
"command": "cargo", "command": "cargo",
"problemMatcher": [], "problemMatcher": [],
"args": [ "args": ["tauri", "dev"],
"tauri", "icon": {
"dev" "id": "run"
],
"icon": {
"id": "run"
},
},
{
"label": "ui:dev",
"type": "shell",
"isBackground": true,
"command": "npm",
"problemMatcher": [],
"args": [
"run",
"dev",
"--",
"--clearScreen",
"false"
]
},
{
"label": "ui:build",
"group": "build",
"type": "shell",
"command": "npm",
"problemMatcher": [],
"args": [
"run",
"build"
]
} }
], },
} {
"label": "ui:dev",
"type": "shell",
"isBackground": true,
"command": "npm",
"problemMatcher": [],
"args": ["run", "dev", "--", "--clearScreen", "false"]
},
{
"label": "ui:build",
"group": "build",
"type": "shell",
"command": "npm",
"problemMatcher": [],
"args": ["run", "build"]
}
]
}

View File

@ -8,7 +8,7 @@ A basic timer for my broken brain.
## Forking ## Forking
I'm developing this for myself, and have limited goals. I'm developing this for myself, and have limited goals.
## License ## License
@ -21,8 +21,8 @@ the Free Software Foundation, either version 3 of the License, or
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/potato.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ziemniak</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

1389
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,26 +5,39 @@
"type": "module", "type": "module",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json", "check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. . && eslint .",
"format": "prettier --write --plugin-search-dir=. .",
"tauri": "tauri" "tauri": "tauri"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.1", "@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "next",
"@types/cookie": "^0.5.1",
"@tauri-apps/cli": "^1.0.5", "@tauri-apps/cli": "^1.0.5",
"@tsconfig/svelte": "^3.0.0",
"@types/luxon": "^3.0.0", "@types/luxon": "^3.0.0",
"luxon": "^3.0.1", "@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.7.1",
"prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.49.0", "svelte": "^3.49.0",
"svelte-check": "^2.8.0", "svelte-check": "^2.8.0",
"svelte-preprocess": "^4.10.7", "svelte-preprocess": "4.10.7",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"typescript": "^4.6.4", "typescript": "^4.7.4",
"vite": "^3.0.0" "vite": "^3.0.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.0.2" "@fontsource/fira-mono": "^4.5.0",
"@tauri-apps/api": "^1.0.2",
"luxon": "^3.0.1",
"cookie": "^0.4.1"
} }
} }

View File

@ -4,7 +4,7 @@
"beforeBuildCommand": "npm run build", "beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",
"devPath": "http://localhost:5173", "devPath": "http://localhost:5173",
"distDir": "../dist" "distDir": "../.svelte-kit/output/dist"
}, },
"package": { "package": {
"productName": "ziemniak", "productName": "ziemniak",

View File

@ -1,32 +0,0 @@
<script>
import { onDestroy } from 'svelte'
import { invoke } from '@tauri-apps/api'
import { listen, once } from '@tauri-apps/api/event'
let seconds = 5
function start_timer() {
invoke('start_timer', {
duration: { secs: seconds, nanos: 0 },
message: "Hi!",
})
let timer_tick_unlisten = listen('timer-tick', (event) => {
console.log("Tick!", event.payload.id, event.payload.elapsed)
})
once('timer-done', (event) => {
console.log("Done!", event.payload.id)
timer_tick_unlisten.then(ttu => ttu())
})
}
</script>
<main>
<label>
Fire after
<input type="number" bind:value={seconds} />
</label>
<button on:click="{start_timer}">Fire!</button>
</main>

View File

@ -1,81 +1,107 @@
@import "@fontsource/fira-mono";
:root { :root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif; font-family: Arial, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
font-size: 16px; Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
line-height: 24px; --font-mono: "Fira Mono", monospace;
font-weight: 400; --pure-white: #ffffff;
--primary-color: #b9c6d2;
color-scheme: light dark; --secondary-color: #d0dde9;
color: rgba(255, 255, 255, 0.87); --tertiary-color: #edf0f8;
background-color: #242424; --accent-color: #ff3e00;
--heading-color: rgba(0, 0, 0, 0.7);
font-synthesis: none; --text-color: #444444;
text-rendering: optimizeLegibility; --background-without-opacity: rgba(255, 255, 255, 0.7);
-webkit-font-smoothing: antialiased; --column-width: 42rem;
-moz-osx-font-smoothing: grayscale; --column-margin-top: 4rem;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
} }
body { body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh; min-height: 100vh;
margin: 0;
background-color: var(--primary-color);
background: linear-gradient(
180deg,
var(--primary-color) 0%,
var(--secondary-color) 10.45%,
var(--tertiary-color) 41.35%
);
}
body::before {
content: "";
width: 80vw;
height: 100vh;
position: absolute;
top: 0;
left: 10vw;
z-index: -1;
background: radial-gradient(
50% 50% at 50% 50%,
var(--pure-white) 0%,
rgba(255, 255, 255, 0) 100%
);
opacity: 0.05;
}
#svelte {
min-height: 100vh;
display: flex;
flex-direction: column;
}
h1,
h2,
p {
font-weight: 400;
color: var(--heading-color);
}
p {
line-height: 1.5;
}
a {
color: var(--accent-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
} }
h1 { h1 {
font-size: 3.2em; font-size: 2rem;
line-height: 1.1;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center; text-align: center;
} }
button { h2 {
border-radius: 8px; font-size: 1rem;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
} }
@media (prefers-color-scheme: light) { pre {
:root { font-size: 16px;
color: #213547; font-family: var(--font-mono);
background-color: #ffffff; background-color: rgba(255, 255, 255, 0.45);
} border-radius: 3px;
a:hover { box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
color: #747bff; padding: 0.5em;
} overflow-x: auto;
button { color: var(--text-color);
background-color: #f9f9f9; }
input,
button {
font-size: inherit;
font-family: inherit;
}
button:focus:not(:focus-visible) {
outline: none;
}
@media (min-width: 720px) {
h1 {
font-size: 2.4rem;
} }
} }

22
src/app.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
interface Locals {
userid: string;
}
// interface Platform {}
// interface Session {}
// interface Stuff {}
}
declare global {
interface Window {
__TAURI__: any;
}
}

12
src/app.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body>
<div>%sveltekit.body%</div>
</body>
</html>

View File

@ -1,23 +0,0 @@
<svg width="128" height="128" style="enable-background:new 0 0 128 128;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Layer_2" style="display:none;">
<path d=" M71.52,44.35c-6.36-3.15-15.04-9.52-16.33-11.23" style="display:inline;fill:none;stroke:#330000;stroke-width:1.8115;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"/>
<path d=" M100.93,47.54c-15.84-1.92-37.94-15.19-39.85-16.57" style="display:inline;fill:none;stroke:#330000;stroke-width:1.8115;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"/>
<path d=" M106.25,41.12C86.91,39.26,66.33,28.38,65.9,27.88" style="display:inline;fill:none;stroke:#330000;stroke-width:1.8115;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"/>
<path d=" M114.28,34.86c-15.82-0.36-42.16-9.41-43.57-10.08" style="display:inline;fill:none;stroke:#330000;stroke-width:1.8115;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"/>
<path d=" M113.84,26.83c-12.91,1.05-38.1-5.93-38.94-6.04" style="display:inline;fill:none;stroke:#330000;stroke-width:1.8115;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"/>
<path d=" M105.04,19.23c-9.5,1-26.01-3.26-27.3-3.53" style="display:inline;fill:none;stroke:#330000;stroke-width:1.8115;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"/>
</g>
<g id="图层_1">
<path d="M112.51,21.82c-4.8-3.07-10.15-5.51-15.56-7.61c-7.7-2.99-15.42-3.67-23.2-2.18 c-6.89,1.32-13.39,4.5-18.93,8.9c-3.46,2.74-6.92,5.49-10.13,8.47c-7.74,7.2-15.59,14.39-22.55,22.25 C16.01,58.56,10.52,66.11,5.7,73.88c-4.14,6.67-4.63,13.99-2.16,21.24c5.5,16.09,31.28,26.36,47.4,19.42 c5.88-2.53,11.69-5.27,17.48-8.01c6.66-3.15,13.16-6.7,19.94-9.56c20.74-8.77,32.83-24.01,36.89-44.51 C127.77,39.74,123.59,28.9,112.51,21.82z M60.63,33.33c0.11,0.42-0.25-0.01-0.14,0.41c-0.03-0.11-0.06-0.23-0.09-0.34 C60.48,33.37,60.56,33.35,60.63,33.33z" style="fill:#B89278;"/>
<g>
<path d="M104.36,70.22c-0.09,0-0.18-0.02-0.26-0.07c-0.17-0.1-0.27-0.3-0.24-0.51 c0.35-2.25,0.79-4.52,1.21-6.72c0.99-5.12,1.93-9.96,1.89-14.8c-0.06-7.2-5.06-12.89-9.47-17.92c-0.16-0.19-0.17-0.46-0.01-0.65 c0.1-0.12,0.24-0.18,0.38-0.18c0.09,0,0.18,0.02,0.26,0.08c6.85,4.25,12.58,15.13,13.04,24.77c0.23,4.82-0.71,11.5-6.51,15.9 C104.58,70.19,104.47,70.22,104.36,70.22z" style="fill:#855C52;"/>
<path d="M97.88,29.88c10.73,6.66,19.12,30.24,6.49,39.85c1.14-7.31,3.16-14.54,3.11-21.6 C107.41,40.85,102.55,35.21,97.88,29.88 M97.88,28.88c-0.29,0-0.57,0.12-0.76,0.35c-0.32,0.38-0.32,0.93,0.01,1.31 c4.35,4.96,9.29,10.58,9.35,17.59c0.04,4.8-0.89,9.61-1.88,14.71c-0.43,2.2-0.87,4.47-1.22,6.73c-0.06,0.4,0.13,0.8,0.48,1.01 c0.16,0.09,0.34,0.14,0.51,0.14c0.21,0,0.43-0.07,0.61-0.2c4.72-3.59,7.04-9.24,6.71-16.32c-0.46-9.78-6.29-20.84-13.27-25.17 C98.24,28.93,98.06,28.88,97.88,28.88L97.88,28.88z" style="fill:#855C52;"/>
</g>
<g>
<path d="M45.54,82.39c-0.13,0-0.25-0.05-0.35-0.14c-0.41-0.4-0.81-0.8-1.21-1.2 c-3.82-3.82-7.44-7.43-13.7-7.43c-0.18,0-0.36,0-0.55,0.01c-0.01,0-0.01,0-0.02,0c-0.23,0-0.42-0.15-0.48-0.37 c-0.06-0.23,0.04-0.46,0.25-0.57c1.41-0.75,2.97-1.13,4.64-1.13c5.99,0,11.45,4.71,11.92,10.29c0.02,0.21-0.1,0.41-0.29,0.49 C45.68,82.38,45.61,82.39,45.54,82.39z" style="fill:#855C52;"/>
<path d="M34.11,72.06c5.42,0,10.96,4.28,11.42,9.83c-4.28-4.2-8.11-8.77-15.26-8.77 c-0.19,0-0.38,0-0.57,0.01C31.09,72.39,32.6,72.06,34.11,72.06 M34.11,71.06L34.11,71.06c-1.75,0-3.39,0.4-4.87,1.18 c-0.41,0.22-0.62,0.69-0.5,1.14c0.12,0.44,0.51,0.74,0.97,0.74c0.01,0,0.02,0,0.03,0c0.18-0.01,0.36-0.01,0.53-0.01 c6.06,0,9.6,3.54,13.35,7.28c0.4,0.4,0.8,0.8,1.21,1.2c0.19,0.19,0.44,0.29,0.7,0.29c0.14,0,0.29-0.03,0.43-0.1 c0.38-0.18,0.61-0.57,0.57-0.99C46.04,75.98,40.35,71.06,34.11,71.06L34.11,71.06z" style="fill:#855C52;"/>
</g>
<path d="M32.88,40.51c7.74-3.96,25.52-9.94,25.85-8.17c0.01,2.75-4.2,4.47-5.5,4.94 c-10.07,3.67-21.63,9.32-31.09,14.36C24.61,48.92,30.35,42.7,32.88,40.51z" style="fill:#855C52;"/>
<path d="M71,97.58c-3.38,0.27-8.02,0.27-11.41,0.27c-3.16,0-2.13,1.91-1.22,2.19 c4.24,1.29,7.86,1.95,12.29,2.15c0.89,0.04,5.96,0.29,6.76-0.1c6.58-3.18,10.54-5.01,14.01-6.51C84.67,96.75,76.57,97.13,71,97.58z " style="fill:#855C52;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

25
src/hooks.ts Normal file
View File

@ -0,0 +1,25 @@
import * as cookie from "cookie";
import type { Handle } from "@sveltejs/kit";
export const handle: Handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get("cookie") || "");
event.locals.userid = cookies["userid"] || crypto.randomUUID();
const response = await resolve(event, {
ssr: false,
});
if (!cookies["userid"]) {
// if this is the first time the user has visited this app,
// set a cookie so that we recognise them when they return
response.headers.set(
"set-cookie",
cookie.serialize("userid", event.locals.userid, {
path: "/",
httpOnly: true,
})
);
}
return response;
};

118
src/lib/Counter.svelte Normal file
View File

@ -0,0 +1,118 @@
<script>
import { spring } from "svelte/motion";
let count = 0;
const displayed_count = spring();
$: displayed_count.set(count);
$: offset = modulo($displayed_count, 1);
/**
* @param {number} n
* @param {number} m
*/
function modulo(n, m) {
// handle negative numbers
return ((n % m) + m) % m;
}
</script>
<div class="counter">
<button
on:click={() => (count -= 1)}
aria-label="Decrease the counter by one"
>
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5" />
</svg>
</button>
<div class="counter-viewport">
<div
class="counter-digits"
style="transform: translate(0, {100 * offset}%)"
>
<strong class="hidden" aria-hidden="true"
>{Math.floor($displayed_count + 1)}</strong
>
<strong>{Math.floor($displayed_count)}</strong>
</div>
</div>
<button
on:click={() => (count += 1)}
aria-label="Increase the counter by one"
>
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
</svg>
</button>
</div>
<style>
.counter {
display: flex;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin: 1rem 0;
}
.counter button {
width: 2em;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border: 0;
background-color: transparent;
touch-action: manipulation;
color: var(--text-color);
font-size: 2rem;
}
.counter button:hover {
background-color: var(--secondary-color);
}
svg {
width: 25%;
height: 25%;
}
path {
vector-effect: non-scaling-stroke;
stroke-width: 2px;
stroke: var(--text-color);
}
.counter-viewport {
width: 8em;
height: 4em;
overflow: hidden;
text-align: center;
position: relative;
}
.counter-viewport strong {
position: absolute;
display: flex;
width: 100%;
height: 100%;
font-weight: 400;
color: var(--accent-color);
font-size: 4rem;
align-items: center;
justify-content: center;
}
.counter-digits {
position: absolute;
width: 100%;
height: 100%;
}
.hidden {
top: -100%;
user-select: none;
}
</style>

84
src/lib/form.ts Normal file
View File

@ -0,0 +1,84 @@
import { invalidate } from '$app/navigation';
// this action (https://svelte.dev/tutorial/actions) allows us to
// progressively enhance a <form> that already works without JS
export function enhance(
form: HTMLFormElement,
{
pending,
error,
result
}: {
pending?: ({ data, form }: { data: FormData; form: HTMLFormElement }) => void;
error?: ({
data,
form,
response,
error
}: {
data: FormData;
form: HTMLFormElement;
response: Response | null;
error: Error | null;
}) => void;
result?: ({
data,
form,
response
}: {
data: FormData;
response: Response;
form: HTMLFormElement;
}) => void;
} = {}
) {
let current_token: unknown;
async function handle_submit(e: SubmitEvent) {
const token = (current_token = {});
e.preventDefault();
const data = new FormData(form);
if (pending) pending({ data, form });
try {
const response = await fetch(form.action, {
method: form.method,
headers: {
accept: 'application/json'
},
body: data
});
if (token !== current_token) return;
if (response.ok) {
if (result) result({ data, form, response });
const url = new URL(form.action);
url.search = url.hash = '';
invalidate(url.href);
} else if (error) {
error({ data, form, error: null, response });
} else {
console.error(await response.text());
}
} catch (e: unknown) {
if (error && e instanceof Error) {
error({ data, form, error: e, response: null });
} else {
throw e;
}
}
}
form.addEventListener('submit', handle_submit);
return {
destroy() {
form.removeEventListener('submit', handle_submit);
}
};
}

View File

@ -0,0 +1,126 @@
<script>
import { page } from "$app/stores";
import logo from "./svelte-logo.svg";
</script>
<header>
<div class="corner">
<a href="https://kit.svelte.dev">
<img src={logo} alt="SvelteKit" />
</a>
</div>
<nav>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
</svg>
<ul>
<li class:active={$page.url.pathname === "/"}>
<a sveltekit:prefetch href="/">Home</a>
</li>
<li class:active={$page.url.pathname === "/about"}>
<a sveltekit:prefetch href="/about">About</a>
</li>
<li class:active={$page.url.pathname === "/todos"}>
<a sveltekit:prefetch href="/todos">Todos</a>
</li>
</ul>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
</svg>
</nav>
<div class="corner">
<!-- TODO put something else here? github link? -->
</div>
</header>
<style>
header {
display: flex;
justify-content: space-between;
}
.corner {
width: 3em;
height: 3em;
}
.corner a {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.corner img {
width: 2em;
height: 2em;
object-fit: contain;
}
nav {
display: flex;
justify-content: center;
--background: rgba(255, 255, 255, 0.7);
}
svg {
width: 2em;
height: 3em;
display: block;
}
path {
fill: var(--background);
}
ul {
position: relative;
padding: 0;
margin: 0;
height: 3em;
display: flex;
justify-content: center;
align-items: center;
list-style: none;
background: var(--background);
background-size: contain;
}
li {
position: relative;
height: 100%;
}
li.active::before {
--size: 6px;
content: "";
width: 0;
height: 0;
position: absolute;
top: 0;
left: calc(50% - var(--size));
border: var(--size) solid transparent;
border-top: var(--size) solid var(--accent-color);
}
nav a {
display: flex;
height: 100%;
align-items: center;
padding: 0 1em;
color: var(--heading-color);
font-weight: 700;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.1em;
text-decoration: none;
transition: color 0.2s linear;
}
a:hover {
color: var(--accent-color);
}
</style>

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128">
<title>svelte-logo</title>
<path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/>
<path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,8 +0,0 @@
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')
})
export default app

View File

@ -0,0 +1,47 @@
<script>
import Header from "$lib/header/Header.svelte";
import "../app.css";
</script>
<Header />
<main>
<slot />
</main>
<footer>
<p>
visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit
</p>
</footer>
<style>
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
width: 100%;
max-width: 1024px;
margin: 0 auto;
box-sizing: border-box;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
</style>

51
src/routes/about.svelte Normal file
View File

@ -0,0 +1,51 @@
<script context="module">
import { browser, dev } from "$app/env";
// we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement...
export const hydrate = dev;
// ...but if the client-side router is already loaded
// (i.e. we came here from elsewhere in the app), use it
export const router = browser;
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in prod
export const prerender = true;
</script>
<svelte:head>
<title>About</title>
<meta name="description" content="About this app" />
</svelte:head>
<div class="content">
<h1>About this app</h1>
<p>
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your
own by typing the following into your command line and following the prompts:
</p>
<pre>npm create svelte@latest</pre>
<p>
The page you're looking at is purely static HTML, with no client-side
interactivity needed. Because of that, we don't need to load any JavaScript.
Try viewing the page's source, or opening the devtools network panel and
reloading.
</p>
<p>
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and
form handling. Try using it with JavaScript disabled!
</p>
</div>
<style>
.content {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
}
</style>

113
src/routes/index.svelte Normal file
View File

@ -0,0 +1,113 @@
<script context="module">
export const prerender = false;
</script>
<script type="ts">
import Counter from "$lib/Counter.svelte";
import { onDestroy, onMount } from "svelte";
import type { UnlistenFn } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api";
import { listen } from "@tauri-apps/api/event";
let timerTickUnlisten: Promise<UnlistenFn> | null = null;
let timerDoneUnlisten: Promise<UnlistenFn> | null = null;
type TimerEventPayload = {
id: string;
started?: unknown;
duration: {
secs: number;
nsecs: number;
};
elapsed?: {
secs: number;
nsecs: number;
};
message: string;
};
onMount(() => {
timerTickUnlisten = listen<TimerEventPayload>("timer-tick", (event) => {
console.log("Tick!", event.payload.id, event.payload.elapsed);
});
timerDoneUnlisten = listen<TimerEventPayload>("timer-done", (event) => {
console.log("Done!", event.payload.id);
});
});
let seconds = 5;
function start_timer() {
invoke("start_timer", {
duration: { secs: seconds, nanos: 0 },
message: "Hi!",
});
}
onDestroy(() => {
timerTickUnlisten?.then((ttu) => ttu());
timerDoneUnlisten?.then((tdu) => tdu());
});
</script>
<svelte:head>
<title>Home</title>
<meta name="description" content="Svelte demo app" />
</svelte:head>
<section>
<h1>
<span class="welcome">
<picture>
<source srcset="svelte-welcome.webp" type="image/webp" />
<img src="svelte-welcome.png" alt="Welcome" />
</picture>
</span>
to your new<br />SvelteKit app
</h1>
<h2>
try editing <strong>src/routes/index.svelte</strong>
</h2>
<Counter />
<div>
<label>
Fire after
<input type="number" bind:value={seconds} />
</label>
<button on:click={start_timer}>Fire!</button>
</div>
</section>
<style>
section {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
}
h1 {
width: 100%;
}
.welcome {
display: block;
position: relative;
width: 100%;
height: 0;
padding: 0 0 calc(100% * 495 / 2048) 0;
}
.welcome img {
position: absolute;
width: 100%;
height: 100%;
top: 0;
display: block;
}
</style>

22
src/routes/todos/_api.ts Normal file
View File

@ -0,0 +1,22 @@
/*
This module is used by the /todos endpoint to
make calls to api.svelte.dev, which stores todos
for each user. The leading underscore indicates that this is
a private module, _not_ an endpoint visiting /todos/_api
will net you a 404 response.
(The data on the todo app will expire periodically; no
guarantees are made. Don't use it to organise your life.)
*/
const base = "https://api.svelte.dev";
export function api(method: string, resource: string, data?: Record<string, unknown>) {
return fetch(`${base}/${resource}`, {
method,
headers: {
'content-type': 'application/json'
},
body: data && JSON.stringify(data)
});
}

View File

@ -0,0 +1,187 @@
<script lang="ts">
import { enhance } from '$lib/form';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
type Todo = {
uid: string;
created_at: Date;
text: string;
done: boolean;
pending_delete: boolean;
};
export let todos: Todo[];
</script>
<svelte:head>
<title>Todos</title>
<meta name="description" content="A todo list app" />
</svelte:head>
<div class="todos">
<h1>Todos</h1>
<form
class="new"
action="/todos"
method="post"
use:enhance={{
result: async ({ form }) => {
form.reset();
}
}}
>
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
</form>
{#each todos as todo (todo.uid)}
<div
class="todo"
class:done={todo.done}
transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }}
>
<form
action="/todos?_method=PATCH"
method="post"
use:enhance={{
pending: ({ data }) => {
todo.done = !!data.get('done');
}
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" />
</form>
<form
action="/todos?_method=DELETE"
method="post"
use:enhance={{
pending: () => (todo.pending_delete = true)
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form>
</div>
{/each}
</div>
<style>
.todos {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
line-height: 1;
}
.new {
margin: 0 0 0.5rem 0;
}
input {
border: 1px solid transparent;
}
input:focus-visible {
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #ff3e00 !important;
outline: none;
}
.new input {
font-size: 28px;
width: 100%;
padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
text-align: center;
}
.todo {
display: grid;
grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem;
align-items: center;
margin: 0 0 0.5rem 0;
padding: 0.5rem;
background-color: white;
border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s;
}
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
form.text {
position: relative;
display: flex;
align-items: center;
flex: 1;
}
.todo input {
flex: 1;
padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px;
}
.todo button {
width: 2em;
height: 2em;
border: none;
background-color: transparent;
background-position: 50% 50%;
background-repeat: no-repeat;
}
button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
box-sizing: border-box;
background-size: 1em auto;
}
.done .toggle {
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.delete {
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
opacity: 0.2;
}
.delete:hover,
.delete:focus {
transition: opacity 0.2s;
opacity: 1;
}
.save {
position: absolute;
right: 0;
opacity: 0;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
}
.todo input:focus + .save,
.save:focus {
transition: opacity 0.2s;
opacity: 1;
}
</style>

67
src/routes/todos/index.ts Normal file
View File

@ -0,0 +1,67 @@
import { api } from './_api';
import type { RequestHandler } from './__types';
export const GET: RequestHandler = async ({ locals }) => {
// locals.userid comes from src/hooks.js
const response = await api('GET', `todos/${locals.userid}`);
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return {
body: {
todos: []
}
};
}
if (response.status === 200) {
return {
body: {
todos: await response.json()
}
};
}
return {
status: response.status
};
};
export const POST: RequestHandler = async ({ request, locals }) => {
const form = await request.formData();
await api('POST', `todos/${locals.userid}`, {
text: form.get('text')
});
return {};
};
// If the user has JavaScript disabled, the URL will change to
// include the method override unless we redirect back to /todos
const redirect = {
status: 303,
headers: {
location: '/todos'
}
};
export const PATCH: RequestHandler = async ({ request, locals }) => {
const form = await request.formData();
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
text: form.has('text') ? form.get('text') : undefined,
done: form.has('done') ? !!form.get('done') : undefined
});
return redirect;
};
export const DELETE: RequestHandler = async ({ request, locals }) => {
const form = await request.formData();
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`);
return redirect;
};

2
src/vite-env.d.ts vendored
View File

@ -1,2 +0,0 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

3
static/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

BIN
static/svelte-welcome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

BIN
static/svelte-welcome.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -1,7 +1,20 @@
import sveltePreprocess from 'svelte-preprocess' import adapter from '@sveltejs/adapter-auto';
import preprocess from 'svelte-preprocess';
export default { /** @type {import('@sveltejs/kit').Config} */
// Consult https://github.com/sveltejs/svelte-preprocess const config = {
// for more information about preprocessors // Consult https://github.com/sveltejs/svelte-preprocess
preprocess: sveltePreprocess() // for more information about preprocessors
} preprocess: preprocess(),
kit: {
adapter: adapter(),
// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
}
}
};
export default config;

View File

@ -1,21 +1,13 @@
{ {
"extends": "@tsconfig/svelte/tsconfig.json", "extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "allowJs": true,
"useDefineForClassFields": true, "checkJs": true,
"module": "ESNext", "esModuleInterop": true,
"resolveJsonModule": true, "forceConsistentCasingInFileNames": true,
"baseUrl": ".", "resolveJsonModule": true,
/** "skipLibCheck": true,
* Typecheck JS in `.svelte` and `.js` files by default. "sourceMap": true,
* Disable checkJs if you'd like to use dynamic types in JS. "strict": true
* Note that setting allowJs false does not prevent the use }
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
} }

View File

@ -1,8 +0,0 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node"
},
"include": ["vite.config.ts"]
}

View File

@ -1,10 +1,8 @@
import { defineConfig } from 'vite' import { sveltekit } from "@sveltejs/kit/vite";
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/ /** @type {import('vite').UserConfig} */
export default defineConfig({ const config = {
plugins: [svelte()], plugins: [sveltekit()],
build: { };
write: true
} export default config;
})