feat: init impl
This commit is contained in:
parent
7f7d77749e
commit
a4999bb665
13
.eslintignore
Normal file
13
.eslintignore
Normal 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
|
20
.eslintrc.cjs
Normal file
20
.eslintrc.cjs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,2 +1,11 @@
|
|||||||
/.direnv
|
.DS_Store
|
||||||
/.helix
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
/.direnv
|
13
.prettierignore
Normal file
13
.prettierignore
Normal 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
|
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"pluginSearchDirs": ["."],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"svelte.enable-ts-plugin": true,
|
||||||
|
"editor.tabSize": 2
|
||||||
|
}
|
39
README.md
39
README.md
@ -1,11 +1,38 @@
|
|||||||
# fresh project
|
# create-svelte
|
||||||
|
|
||||||
### Usage
|
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||||
|
|
||||||
Start the project:
|
## Creating a project
|
||||||
|
|
||||||
```
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
deno task start
|
|
||||||
|
```bash
|
||||||
|
# create a new project in the current directory
|
||||||
|
npm create svelte@latest
|
||||||
|
|
||||||
|
# create a new project in my-app
|
||||||
|
npm create svelte@latest my-app
|
||||||
```
|
```
|
||||||
|
|
||||||
This will watch the project directory and restart as necessary.
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import { JSX } from "preact";
|
|
||||||
|
|
||||||
export default function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
{...props}
|
|
||||||
class={`px-3 py-2 bg-white rounded border(gray-500 2) hover:bg-gray-200 active:bg-gray-300 disabled:(opacity-50 cursor-not-allowed) ${
|
|
||||||
props.class ?? ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import { JSX } from "preact";
|
|
||||||
|
|
||||||
export default function Center(props: JSX.HTMLAttributes<HTMLDivElement>) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
{...props}
|
|
||||||
class={`flex items-center justify-center h-full w-full ${
|
|
||||||
props.class ?? ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import { JSX } from "preact";
|
|
||||||
|
|
||||||
export default function Input(props: JSX.HTMLAttributes<HTMLInputElement>) {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
{...props}
|
|
||||||
class={`px-3 py-2 bg-white rounded border(gray-500 2) disabled:(opacity-50 cursor-not-allowed) ${
|
|
||||||
props.class ?? ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
10
deno.json
10
deno.json
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"tasks": {
|
|
||||||
"start": "deno run -A --watch=static/,routes/ dev.ts"
|
|
||||||
},
|
|
||||||
"importMap": "./import_map.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"jsxImportSource": "preact"
|
|
||||||
}
|
|
||||||
}
|
|
151
deno.lock
151
deno.lock
@ -1,151 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2",
|
|
||||||
"remote": {
|
|
||||||
"https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
|
|
||||||
"https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49",
|
|
||||||
"https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f",
|
|
||||||
"https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d",
|
|
||||||
"https://deno.land/std@0.140.0/fs/expand_glob.ts": "0c10130d67c9b02164b03df8e43c6d6defbf8e395cb69d09e84a8586e6d72ac3",
|
|
||||||
"https://deno.land/std@0.140.0/fs/walk.ts": "117403ccd21fd322febe56ba06053b1ad5064c802170f19b1ea43214088fe95f",
|
|
||||||
"https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3",
|
|
||||||
"https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09",
|
|
||||||
"https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b",
|
|
||||||
"https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633",
|
|
||||||
"https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee",
|
|
||||||
"https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d",
|
|
||||||
"https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44",
|
|
||||||
"https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
|
|
||||||
"https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757",
|
|
||||||
"https://deno.land/std@0.173.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
|
|
||||||
"https://deno.land/std@0.173.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
|
|
||||||
"https://deno.land/std@0.173.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
|
|
||||||
"https://deno.land/std@0.173.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
|
|
||||||
"https://deno.land/std@0.173.0/path/_util.ts": "86c2375a996c1931b2f2ac71fefd5ddf0cf0e579fa4ab12d3e4c552d4223b8d8",
|
|
||||||
"https://deno.land/std@0.173.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
|
|
||||||
"https://deno.land/std@0.173.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
|
|
||||||
"https://deno.land/std@0.173.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232",
|
|
||||||
"https://deno.land/std@0.173.0/path/posix.ts": "0874b341c2c6968ca38d323338b8b295ea1dae10fa872a768d812e2e7d634789",
|
|
||||||
"https://deno.land/std@0.173.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
|
|
||||||
"https://deno.land/std@0.173.0/path/win32.ts": "672942919dd66ce1b8c224e77d3447e2ad8254eaff13fe6946420a9f78fa141e",
|
|
||||||
"https://deno.land/std@0.177.0/async/abortable.ts": "73acfb3ed7261ce0d930dbe89e43db8d34e017b063cf0eaa7d215477bf53442e",
|
|
||||||
"https://deno.land/std@0.177.0/async/deadline.ts": "c5facb0b404eede83e38bd2717ea8ab34faa2ffb20ef87fd261fcba32ba307aa",
|
|
||||||
"https://deno.land/std@0.177.0/async/debounce.ts": "adab11d04ca38d699444ac8a9d9856b4155e8dda2afd07ce78276c01ea5a4332",
|
|
||||||
"https://deno.land/std@0.177.0/async/deferred.ts": "42790112f36a75a57db4a96d33974a936deb7b04d25c6084a9fa8a49f135def8",
|
|
||||||
"https://deno.land/std@0.177.0/async/delay.ts": "73aa04cec034c84fc748c7be49bb15cac3dd43a57174bfdb7a4aec22c248f0dd",
|
|
||||||
"https://deno.land/std@0.177.0/async/mod.ts": "f04344fa21738e5ad6bea37a6bfffd57c617c2d372bb9f9dcfd118a1b622e576",
|
|
||||||
"https://deno.land/std@0.177.0/async/mux_async_iterator.ts": "70c7f2ee4e9466161350473ad61cac0b9f115cff4c552eaa7ef9d50c4cbb4cc9",
|
|
||||||
"https://deno.land/std@0.177.0/async/pool.ts": "fd082bd4aaf26445909889435a5c74334c017847842ec035739b4ae637ae8260",
|
|
||||||
"https://deno.land/std@0.177.0/async/retry.ts": "5efa3ba450ac0c07a40a82e2df296287b5013755d232049efd7ea2244f15b20f",
|
|
||||||
"https://deno.land/std@0.177.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757",
|
|
||||||
"https://deno.land/std@0.177.0/http/server.ts": "cbb17b594651215ba95c01a395700684e569c165a567e4e04bba327f41197433",
|
|
||||||
"https://deno.land/std@0.178.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
|
|
||||||
"https://deno.land/std@0.178.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
|
|
||||||
"https://deno.land/std@0.178.0/async/abortable.ts": "73acfb3ed7261ce0d930dbe89e43db8d34e017b063cf0eaa7d215477bf53442e",
|
|
||||||
"https://deno.land/std@0.178.0/async/deadline.ts": "c5facb0b404eede83e38bd2717ea8ab34faa2ffb20ef87fd261fcba32ba307aa",
|
|
||||||
"https://deno.land/std@0.178.0/async/debounce.ts": "adab11d04ca38d699444ac8a9d9856b4155e8dda2afd07ce78276c01ea5a4332",
|
|
||||||
"https://deno.land/std@0.178.0/async/deferred.ts": "42790112f36a75a57db4a96d33974a936deb7b04d25c6084a9fa8a49f135def8",
|
|
||||||
"https://deno.land/std@0.178.0/async/delay.ts": "73aa04cec034c84fc748c7be49bb15cac3dd43a57174bfdb7a4aec22c248f0dd",
|
|
||||||
"https://deno.land/std@0.178.0/async/mod.ts": "f04344fa21738e5ad6bea37a6bfffd57c617c2d372bb9f9dcfd118a1b622e576",
|
|
||||||
"https://deno.land/std@0.178.0/async/mux_async_iterator.ts": "70c7f2ee4e9466161350473ad61cac0b9f115cff4c552eaa7ef9d50c4cbb4cc9",
|
|
||||||
"https://deno.land/std@0.178.0/async/pool.ts": "fd082bd4aaf26445909889435a5c74334c017847842ec035739b4ae637ae8260",
|
|
||||||
"https://deno.land/std@0.178.0/async/retry.ts": "5efa3ba450ac0c07a40a82e2df296287b5013755d232049efd7ea2244f15b20f",
|
|
||||||
"https://deno.land/std@0.178.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757",
|
|
||||||
"https://deno.land/std@0.178.0/flags/mod.ts": "d1cdefa18472ef69858a17df5cf7c98445ed27ac10e1460183081303b0ebc270",
|
|
||||||
"https://deno.land/std@0.178.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32",
|
|
||||||
"https://deno.land/std@0.178.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32",
|
|
||||||
"https://deno.land/std@0.178.0/http/http_status.ts": "8a7bcfe3ac025199ad804075385e57f63d055b2aed539d943ccc277616d6f932",
|
|
||||||
"https://deno.land/std@0.178.0/http/server.ts": "cbb17b594651215ba95c01a395700684e569c165a567e4e04bba327f41197433",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/_db.ts": "7606d83e31f23ce1a7968cbaee852810c2cf477903a095696cdc62eaab7ce570",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/_util.ts": "916efbd30b6148a716f110e67a4db29d6949bf4048997b754415dd7e42c52378",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/content_type.ts": "c682589a0aeb016bfed355cc1ed6fbb3ead2ea48fc0000ac5de6a5730613ad1c",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/extension.ts": "7a4ef2813d7182f724a941f38161525996e4a67abc3cf6a0f9bc2168d73a0f0e",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/extensions_by_type.ts": "4358023feac696e6e9d49c0f1e76a859f03ca254df57812f31f8536890c3a443",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/format_media_type.ts": "1e35e16562e5c417401ffc388a9f8f421f97f0ee06259cbe990c51bae4e6c7a8",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/get_charset.ts": "8be15a1fd31a545736b91ace56d0e4c66ea0d7b3fdc5c90760e8202e7b4b1fad",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/mod.ts": "d3f0b99f85053bc0b98ecc24eaa3546dfa09b856dc0bbaf60d8956d2cdd710c8",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/parse_media_type.ts": "bed260d868ea271445ae41d748e7afed9b5a7f407d2777ead08cecf73e9278de",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/type_by_extension.ts": "6076a7fc63181d70f92ec582fdea2c927eb2cfc7f9c9bee9d6add2aca86f2355",
|
|
||||||
"https://deno.land/std@0.178.0/media_types/vendor/mime-db.v1.52.0.ts": "6925bbcae81ca37241e3f55908d0505724358cda3384eaea707773b2c7e99586",
|
|
||||||
"https://deno.land/std@0.178.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
|
|
||||||
"https://deno.land/std@0.178.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
|
|
||||||
"https://deno.land/std@0.178.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
|
|
||||||
"https://deno.land/std@0.178.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
|
|
||||||
"https://deno.land/std@0.178.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
|
|
||||||
"https://deno.land/std@0.178.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232",
|
|
||||||
"https://deno.land/std@0.178.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
|
|
||||||
"https://deno.land/std@0.178.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
|
|
||||||
"https://deno.land/std@0.178.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
|
|
||||||
"https://deno.land/std@0.178.0/semver/mod.ts": "409a2691f5a411c34e917c1e6d445a6d1d53f3fadf660e44a99dd0bf9b2ef412",
|
|
||||||
"https://deno.land/std@0.182.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
|
|
||||||
"https://deno.land/std@0.182.0/datetime/to_imf.ts": "8f9c0af8b167031ffe2e03da01a12a3b0672cc7562f89c61942a0ab0129771b2",
|
|
||||||
"https://deno.land/std@0.182.0/http/cookie.ts": "934f92d871d50852dbd7a836d721df5a9527b14381db16001b40991d30174ee4",
|
|
||||||
"https://deno.land/x/code_block_writer@11.0.3/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5",
|
|
||||||
"https://deno.land/x/code_block_writer@11.0.3/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff",
|
|
||||||
"https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6",
|
|
||||||
"https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4",
|
|
||||||
"https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d",
|
|
||||||
"https://deno.land/x/esbuild@v0.17.11/mod.d.ts": "dc279a3a46f084484453e617c0cabcd5b8bd1920c0e562e4ea02dfc828c8f968",
|
|
||||||
"https://deno.land/x/esbuild@v0.17.11/mod.js": "4f4e61964a551d9c0baf5bb19e973cf631cf8c66ddaf01e70070f8a100fc938c",
|
|
||||||
"https://deno.land/x/esbuild@v0.17.11/wasm.d.ts": "dc279a3a46f084484453e617c0cabcd5b8bd1920c0e562e4ea02dfc828c8f968",
|
|
||||||
"https://deno.land/x/esbuild@v0.17.11/wasm.js": "4030e7b50941ec6e06704c6b5f1f6416cc0f7f35f63daf63f184b728bea79a30",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/dev.ts": "a66c7d64be35bcd6a8e12eec9c27ae335044c70363a241f2e36ee776db468622",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/plugins/twind.ts": "c0570d6010e29ba24ee5f43b9d3f1fe735f7fac76d9a3e680c9896373d669876",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/plugins/twind/shared.ts": "023e0ffcd66668753b5049edab0de46e6d66194fb6026c679034b9bbf04ad6f3",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/runtime.ts": "b02ec1e2e32cf73a33d262b7c9dcab9468ce16cd89fd424196c71003698a4ab0",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/server.ts": "f379c9aad24471a71e58fb887fa57e5cc27ad9df035987eb260541c78df38e84",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/dev/deps.ts": "aef312af6de1315fa95fc0c32e87c56301b64efeb304193d0dce0da0634144d3",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/dev/error.ts": "21a38d240c00279662e6adde41367f1da0ae7e2836d993f818ea94aabab53e7b",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/dev/mod.ts": "14baa66a064961afcbad74748eeb2b99ce4bb246223ed69d8cc35b073e49b039",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/runtime/csp.ts": "9ee900e9b0b786057b1009da5976298c202d1b86d1f1e4d2510bde5f06530ac9",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/runtime/head.ts": "0f9932874497ab6e57ed1ba01d549e843523df4a5d36ef97460e7a43e3132fdc",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/runtime/utils.ts": "8320a874a44bdd5905c7d4b87a0e7a14a6c50a2ed133800e72ae57341e4d4faa",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/server/bundle.ts": "2febeb5071d0647993560807ca5b2f4cd60181d370ee8612e5db728cb10db9f1",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/server/constants.ts": "ad10dda1bc20c25c2926f6a8cfd79ef4368d70b4b03a645f65c04b3fa7d93a8c",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/server/context.ts": "896ca59b58d0713d8429c4ec8aea4cedb0e4c09a804a2e6ec0c81379f5f83b15",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/server/default_error_page.ts": "9a1a595a1a2b31c9b724b04db82b8af256285536db272658d831ac9ef1d3d448",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/server/deps.ts": "e205002c298ee6bf40af1365ced243b8cd86cf39e922ec2db2c91ef022eb72ad",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/server/htmlescape.ts": "834ac7d0caa9fc38dffd9b8613fb47aeecd4f22d5d70c51d4b20a310c085835c",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/server/mod.ts": "72d213444334dd2e94c757a0eee0fc486c0919399ea9184d07ad042f34edd00d",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/server/render.ts": "6f50707bd1f6e33ed84bb71ae3b0996d202b953cefc4285f5356524c7b21f01f",
|
|
||||||
"https://deno.land/x/fresh@1.1.5/src/server/types.ts": "6579aac850ea4af5cdfee8f9552fbadebb7b50841d180b75bd90466416feee86",
|
|
||||||
"https://deno.land/x/importmap@0.2.1/_util.ts": "ada9a9618b537e6c0316c048a898352396c882b9f2de38aba18fd3f2950ede89",
|
|
||||||
"https://deno.land/x/importmap@0.2.1/mod.ts": "ae3d1cd7eabd18c01a4960d57db471126b020f23b37ef14e1359bbb949227ade",
|
|
||||||
"https://deno.land/x/rutt@0.1.0/mod.ts": "4662ad4f687740ac612b779ed4c62eecebd718b56d24a07f719ec3b24464c139",
|
|
||||||
"https://deno.land/x/ts_morph@17.0.1/common/DenoRuntime.ts": "537800e840d0994f9055164e11bf33eadf96419246af0d3c453793c3ae67bdb3",
|
|
||||||
"https://deno.land/x/ts_morph@17.0.1/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed",
|
|
||||||
"https://deno.land/x/ts_morph@17.0.1/common/ts_morph_common.d.ts": "ee7767b0c68b23c65bb607c94b6cb3512e8237fbcb7d1d8383a33235cde2c068",
|
|
||||||
"https://deno.land/x/ts_morph@17.0.1/common/ts_morph_common.js": "49a79124b941ba2b35d81ac9eb90fc33c957b2640cdb97569c1941bac5a3bbdb",
|
|
||||||
"https://deno.land/x/ts_morph@17.0.1/common/typescript.d.ts": "57e52a0882af4e835473dda27e4316cc31149866970210f9f79b940e916b7838",
|
|
||||||
"https://deno.land/x/ts_morph@17.0.1/common/typescript.js": "5dd669eb199ee2a539924c63a92e23d95df43dfe2fbe3a9d68c871648be1ad5e",
|
|
||||||
"https://deno.land/x/ts_morph@17.0.1/mod.ts": "adba9b82f24865d15d2c78ef6074b9a7457011719056c9928c800f130a617c93",
|
|
||||||
"https://deno.land/x/ts_morph@17.0.1/ts_morph.d.ts": "a54b0c51b06d84defedf5fdd59c773d803808ae7c9678f7165f7a1a6dfa7f6a3",
|
|
||||||
"https://deno.land/x/ts_morph@17.0.1/ts_morph.js": "1bb80284b9e31a4c5c2078cd533fe9b12b4b2d710267055cb655225aa88fb2df",
|
|
||||||
"https://esm.sh/*preact-render-to-string@5.2.6": "36fc509887c5dd07c3cc7ec6cd47fb3ac820c9f74c9924dc935a787842fb7f3f",
|
|
||||||
"https://esm.sh/preact@10.13.1": "ae382301328ab874e2c42bee76e261bb8d094673fe76ca7eb71917636d43d8ad",
|
|
||||||
"https://esm.sh/preact@10.13.1/hooks": "1813f80c6d648f8137a62569e35972b0716f92539f192d6db66d45b7aafea43a",
|
|
||||||
"https://esm.sh/preact@10.13.1/jsx-runtime": "0caf2b2eade93af59e89abc31816b6cb2829a9b8a82b958c0ebc1d8bec241e2a",
|
|
||||||
"https://esm.sh/stable/preact@10.13.1/deno/hooks.js": "f25445c4f9fa6742119c41db4a85a2f75ee6ea941c506ae8ad3b1568c7692c61",
|
|
||||||
"https://esm.sh/stable/preact@10.13.1/deno/jsx-runtime.js": "7c8e1b8f272996846cbac0837dcb71f6f8cfc82611b3f7819501d07c37383dc0",
|
|
||||||
"https://esm.sh/stable/preact@10.13.1/deno/preact.mjs": "9b73545225d0ed274c89f39aee524a2857c81a73060e80c2c4bdc2a6de7bec26",
|
|
||||||
"https://esm.sh/twind@0.16.19": "ae2fc340ae156813bca1ff5c5489734f234cc2254565244056a3370375f14e3b",
|
|
||||||
"https://esm.sh/twind@0.16.19/sheets": "01c41aa230494e7f7d5aeaea7ae112ab9f3c3f38ace35f1ccf015352f3bdf56f",
|
|
||||||
"https://esm.sh/v114/csstype@3.1.2/index.d.ts": "4c68749a564a6facdf675416d75789ee5a557afda8960e0803cf6711fa569288",
|
|
||||||
"https://esm.sh/v114/preact-render-to-string@5.2.6/X-ZS8q/deno/preact-render-to-string.mjs": "672116885c5e5072207c527a0ec663f5bc52774a0868ec487985109520382a55",
|
|
||||||
"https://esm.sh/v114/preact-render-to-string@5.2.6/X-ZS8q/src/index.d.ts": "b1d73703252c8570fdf2952475805f5808ba3511fefbd93a3e7bd8406de7dcd0",
|
|
||||||
"https://esm.sh/v114/preact@10.13.1/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3",
|
|
||||||
"https://esm.sh/v114/preact@10.13.1/jsx-runtime/src/index.d.ts": "e153460ed2b3fe2ad8b93696ecd48fbf73cd628b0b0ea6692b71804a3af69dfd",
|
|
||||||
"https://esm.sh/v114/preact@10.13.1/src/index.d.ts": "65398710de6aa0a07412da79784e05e6e96763f51c7c91b77344d2d0af06385c",
|
|
||||||
"https://esm.sh/v114/preact@10.13.1/src/jsx.d.ts": "fde41cfb4944bcc5005e653c989ef8f85deb1cbb5364a1726654993937cd08d0",
|
|
||||||
"https://esm.sh/v114/style-vendorizer@2.2.3/deno/style-vendorizer.mjs": "fb725497dd9621a84f552f9b6a4f3df82af5989ff18c40e972de1bdf475c9765",
|
|
||||||
"https://esm.sh/v114/twind@0.16.19/deno/sheets.js": "dcdb26e5d2860d9fff3ad81b81625799e501d465efdc99b206d3b7cd94151913",
|
|
||||||
"https://esm.sh/v114/twind@0.16.19/deno/twind.mjs": "6fab7543c2d8f0e587272e82218082d154dc87ffb1675e622e92feb72bb37006",
|
|
||||||
"https://esm.sh/v114/twind@0.16.19/sheets/sheets.d.ts": "21a0bca806d9c088d939dc95b08531ee71a708b98da8f20fde4a7026593b73b0",
|
|
||||||
"https://esm.sh/v114/twind@0.16.19/twind.d.ts": "a7cb168607800a3a6d2eabc177d7737015bc93073c14ff9e84a0f37ead9203c0",
|
|
||||||
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/deps.ts": "b7248e5b750be62613a9417f407e65ed43726d83b11f9631d6dbb58634bbd7d1",
|
|
||||||
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/mod.ts": "3e507379372361162f93325a216b86f6098defb5bb60144555b507bca26d061f",
|
|
||||||
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/src/deno.ts": "71bee6b14e72ca193c0686d8b4f1f47d639a64745b6f5c7576f7a3616f436f57",
|
|
||||||
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/src/native_loader.ts": "2a0f5a7b68a57c4651ad48161b32532356b434597a6cf282683427482b38f6fa",
|
|
||||||
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/src/portable_loader.ts": "47adb6d9a00f13a87d0a15f2af79118eb93503234008c31392d71270bc0a42fa",
|
|
||||||
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/src/shared.ts": "007e8d575cb6ebcac4110f1f72188a8bec3aa29287b4ad26e98403c00bebf036"
|
|
||||||
}
|
|
||||||
}
|
|
5
dev.ts
5
dev.ts
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env -S deno run -A --watch=static/,routes/
|
|
||||||
|
|
||||||
import dev from "$fresh/dev.ts";
|
|
||||||
|
|
||||||
await dev(import.meta.url, "./main.ts");
|
|
@ -14,9 +14,8 @@
|
|||||||
devShells.default = config.mk-naked-shell.lib.mkNakedShell {
|
devShells.default = config.mk-naked-shell.lib.mkNakedShell {
|
||||||
name = "musikspider-devshell";
|
name = "musikspider-devshell";
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
deno
|
nodejs
|
||||||
nodePackages_latest.vscode-json-languageserver
|
nodePackages.pnpm
|
||||||
taplo
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
21
fresh.gen.ts
21
fresh.gen.ts
@ -1,21 +0,0 @@
|
|||||||
// DO NOT EDIT. This file is generated by fresh.
|
|
||||||
// This file SHOULD be checked into source version control.
|
|
||||||
// This file is automatically updated during development when running `dev.ts`.
|
|
||||||
|
|
||||||
import config from "./deno.json" assert { type: "json" };
|
|
||||||
import * as $0 from "./routes/index.tsx";
|
|
||||||
import * as $1 from "./routes/library.tsx";
|
|
||||||
import * as $2 from "./routes/login.tsx";
|
|
||||||
|
|
||||||
const manifest = {
|
|
||||||
routes: {
|
|
||||||
"./routes/index.tsx": $0,
|
|
||||||
"./routes/library.tsx": $1,
|
|
||||||
"./routes/login.tsx": $2,
|
|
||||||
},
|
|
||||||
islands: {},
|
|
||||||
baseUrl: import.meta.url,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default manifest;
|
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"imports": {
|
|
||||||
"$fresh/": "https://deno.land/x/fresh@1.1.5/",
|
|
||||||
"preact": "https://esm.sh/preact@10.13.1",
|
|
||||||
"preact/": "https://esm.sh/preact@10.13.1/",
|
|
||||||
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.6",
|
|
||||||
"@preact/signals": "https://esm.sh/*@preact/signals@1.1.3",
|
|
||||||
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.2.3",
|
|
||||||
"twind": "https://esm.sh/twind@0.16.19",
|
|
||||||
"twind/": "https://esm.sh/twind@0.16.19/",
|
|
||||||
"$std/": "https://deno.land/std@0.182.0/"
|
|
||||||
}
|
|
||||||
}
|
|
13
main.ts
13
main.ts
@ -1,13 +0,0 @@
|
|||||||
/// <reference no-default-lib="true" />
|
|
||||||
/// <reference lib="dom" />
|
|
||||||
/// <reference lib="dom.iterable" />
|
|
||||||
/// <reference lib="dom.asynciterable" />
|
|
||||||
/// <reference lib="deno.ns" />
|
|
||||||
|
|
||||||
import { start } from "$fresh/server.ts";
|
|
||||||
import manifest from "./fresh.gen.ts";
|
|
||||||
|
|
||||||
import twindPlugin from "$fresh/plugins/twind.ts";
|
|
||||||
import twindConfig from "./twind.config.ts";
|
|
||||||
|
|
||||||
await start(manifest, { plugins: [twindPlugin(twindConfig)] });
|
|
40
package.json
Normal file
40
package.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "musikspider",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||||
|
"format": "prettier --plugin-search-dir . --write ."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@skeletonlabs/skeleton": "^1.2.0",
|
||||||
|
"@sveltejs/adapter-auto": "^2.0.0",
|
||||||
|
"@sveltejs/adapter-static": "^2.0.2",
|
||||||
|
"@sveltejs/kit": "^1.15.4",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
||||||
|
"@typescript-eslint/parser": "^5.58.0",
|
||||||
|
"autoprefixer": "^10.4.14",
|
||||||
|
"eslint": "^8.38.0",
|
||||||
|
"eslint-config-prettier": "^8.8.0",
|
||||||
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
|
"postcss": "^8.4.21",
|
||||||
|
"prettier": "^2.8.7",
|
||||||
|
"prettier-plugin-svelte": "^2.10.0",
|
||||||
|
"svelte": "^3.58.0",
|
||||||
|
"svelte-check": "^3.2.0",
|
||||||
|
"tailwindcss": "^3.3.1",
|
||||||
|
"tslib": "^2.5.0",
|
||||||
|
"typescript": "^5.0.4",
|
||||||
|
"vite": "^4.2.1"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"svelte-infinite-loading": "^1.3.8",
|
||||||
|
"svelte-tiny-virtual-list": "^2.0.5"
|
||||||
|
}
|
||||||
|
}
|
2123
pnpm-lock.yaml
Normal file
2123
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
import { getCookies } from "$std/http/cookie.ts";
|
|
||||||
|
|
||||||
export function handler(req: Request): Response {
|
|
||||||
const cookies = getCookies(req.headers);
|
|
||||||
const username = cookies["username"];
|
|
||||||
const password = cookies["password"];
|
|
||||||
|
|
||||||
if (username != null && password != null) {
|
|
||||||
return new Response("", {
|
|
||||||
status: 303,
|
|
||||||
headers: { location: "/library" },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return new Response("", {
|
|
||||||
status: 303,
|
|
||||||
headers: { location: "/login" },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
import { Handlers, PageProps } from "$fresh/server.ts";
|
|
||||||
import { getCookies } from "$std/http/cookie.ts";
|
|
||||||
import { Head } from "$fresh/runtime.ts";
|
|
||||||
|
|
||||||
interface Data {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handler: Handlers<Data> = {
|
|
||||||
GET(req, ctx) {
|
|
||||||
const cookies = getCookies(req.headers);
|
|
||||||
const username = cookies["username"];
|
|
||||||
const password = cookies["password"];
|
|
||||||
|
|
||||||
if (username && password) {
|
|
||||||
return ctx.render({ username, password });
|
|
||||||
} else {
|
|
||||||
return new Response("", {
|
|
||||||
status: 303,
|
|
||||||
headers: { location: "/login" },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LibraryPage({ data }: PageProps<Data>) {
|
|
||||||
const { username, password } = data;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>musikspider</title>
|
|
||||||
</Head>
|
|
||||||
<p>Library {username} {password}</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
import { setCookie } from "$std/http/cookie.ts";
|
|
||||||
import { Handlers } from "$fresh/server.ts";
|
|
||||||
import { Head } from "$fresh/runtime.ts";
|
|
||||||
|
|
||||||
import Button from "../components/Button.tsx";
|
|
||||||
import Input from "../components/Input.tsx";
|
|
||||||
|
|
||||||
const LOGIN_MAX_AGE = 60 * 60 * 24 * 7;
|
|
||||||
|
|
||||||
export const handler: Handlers = {
|
|
||||||
GET(_, ctx) {
|
|
||||||
return ctx.render();
|
|
||||||
},
|
|
||||||
async POST(req, _) {
|
|
||||||
const form = await req.formData();
|
|
||||||
const username = form.get("username")!.toString();
|
|
||||||
const password = form.get("password")!.toString();
|
|
||||||
|
|
||||||
const response = new Response("", {
|
|
||||||
status: 303,
|
|
||||||
headers: { location: "/library" },
|
|
||||||
});
|
|
||||||
setCookie(response.headers, {
|
|
||||||
name: "username",
|
|
||||||
value: username,
|
|
||||||
maxAge: LOGIN_MAX_AGE,
|
|
||||||
httpOnly: true,
|
|
||||||
});
|
|
||||||
setCookie(response.headers, {
|
|
||||||
name: "password",
|
|
||||||
value: password,
|
|
||||||
maxAge: LOGIN_MAX_AGE,
|
|
||||||
httpOnly: true,
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>musikspider</title>
|
|
||||||
</Head>
|
|
||||||
<div class="flex items-center justify-center p-4 mx-auto max-w-screen-md h-screen">
|
|
||||||
<form method="POST">
|
|
||||||
<div class="flex flex-col gap-2 max-w-xs">
|
|
||||||
<div class="flex gap-4 place-items-center">
|
|
||||||
<label for="username" class="w-1/3">username</label>
|
|
||||||
<Input
|
|
||||||
class="w-2/3"
|
|
||||||
id="username"
|
|
||||||
type="text"
|
|
||||||
name="username"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-4 place-items-center">
|
|
||||||
<label for="password" class="w-1/3">password</label>
|
|
||||||
<Input
|
|
||||||
class="w-2/3"
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button class="w-min" type="submit">login</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
2
src/app.css
Normal file
2
src/app.css
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
html, body { @apply h-full overflow-hidden; }
|
||||||
|
.card { @apply shadow shadow-black; }
|
12
src/app.d.ts
vendored
Normal file
12
src/app.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
15
src/app.html
Normal file
15
src/app.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="dark" 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 data-sveltekit-preload-data="hover" data-theme="vintage">
|
||||||
|
<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
169
src/comms.ts
Normal file
169
src/comms.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import type { TrackWithId } from "./types";
|
||||||
|
|
||||||
|
const API_VERSION: number = 20;
|
||||||
|
const HTTP_DISABLED_ERROR: string =
|
||||||
|
"server does not have HTTP resources enabled, you will not be able to stream music";
|
||||||
|
const SERVER_API_INCOMPATIBLE_ERROR: (serverApi: number) => string =
|
||||||
|
(serverApi) => `server API version (${serverApi}) is different from our supported version (${API_VERSION})`;
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
device_id: string;
|
||||||
|
type: MessageType;
|
||||||
|
options: any;
|
||||||
|
};
|
||||||
|
type MessageType = 'request' | 'response' | 'broadcast';
|
||||||
|
type RequestCallback = (arg0: Message | null) => void;
|
||||||
|
|
||||||
|
interface Callbacks {
|
||||||
|
onDisconnect: (authenticated: boolean, reason: string) => void;
|
||||||
|
onConnect: (initial: Message) => void;
|
||||||
|
onIncompatible: (reason: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MetadataCommunicator {
|
||||||
|
ws: WebSocket | null;
|
||||||
|
deviceId: string;
|
||||||
|
callbacks: Map<string, RequestCallback>;
|
||||||
|
authenticated: boolean;
|
||||||
|
eventCallbacks: Callbacks;
|
||||||
|
onConnectCallbacks: (() => void)[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.callbacks = new Map();
|
||||||
|
this.deviceId = crypto.randomUUID();
|
||||||
|
this.authenticated = false;
|
||||||
|
this.eventCallbacks = {
|
||||||
|
onDisconnect: () => { },
|
||||||
|
onConnect: () => { },
|
||||||
|
onIncompatible: () => { },
|
||||||
|
};
|
||||||
|
this.onConnectCallbacks = [];
|
||||||
|
this.ws = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCallbacks(callbacks: Callbacks) {
|
||||||
|
this.eventCallbacks = callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(address: string, password: string) {
|
||||||
|
this.close();
|
||||||
|
|
||||||
|
this.ws = new WebSocket(`ws://${address}`);
|
||||||
|
|
||||||
|
this.ws.addEventListener('open', (event) => {
|
||||||
|
this.makeRequest("authenticate", 'request', { password }, (msg) => {
|
||||||
|
if (msg!.options.authenticated) {
|
||||||
|
this.authenticated = true;
|
||||||
|
this.eventCallbacks.onConnect(msg!);
|
||||||
|
this.onConnectCallbacks.forEach((f) => f());
|
||||||
|
this.onConnectCallbacks = [];
|
||||||
|
if (!msg!.options.environment.http_server_enabled) {
|
||||||
|
this.eventCallbacks.onIncompatible(HTTP_DISABLED_ERROR);
|
||||||
|
}
|
||||||
|
const serverApiVersion = msg!.options.environment.api_version;
|
||||||
|
if (serverApiVersion != API_VERSION) {
|
||||||
|
this.eventCallbacks.onIncompatible(SERVER_API_INCOMPATIBLE_ERROR(serverApiVersion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.ws.addEventListener('close', (event) => {
|
||||||
|
this.eventCallbacks.onDisconnect(this.authenticated, `${event.reason} (code ${event.code})`);
|
||||||
|
this.authenticated = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.addEventListener('message', (event) => {
|
||||||
|
const parsed: Message = JSON.parse(event.data);
|
||||||
|
const maybeCallback = this.callbacks.get(parsed.id);
|
||||||
|
if (maybeCallback) {
|
||||||
|
maybeCallback(parsed);
|
||||||
|
this.callbacks.delete(parsed.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTracksCount(): Promise<number> {
|
||||||
|
const options = { count_only: true };
|
||||||
|
const th = this;
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
th.makeRequest("query_tracks", "request", options, (resp) => {
|
||||||
|
if (resp) {
|
||||||
|
resolve(resp.options.count);
|
||||||
|
} else {
|
||||||
|
reject(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTracks(limit: number, offset: number, filter: string | null = null): Promise<TrackWithId[]> {
|
||||||
|
const options: any = { limit, offset };
|
||||||
|
if (filter !== null) options.filter = filter;
|
||||||
|
|
||||||
|
const th = this;
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
th.makeRequest("query_tracks", "request", options, (resp) => {
|
||||||
|
if (resp) {
|
||||||
|
const data: any[] = resp.options.data;
|
||||||
|
resolve(data.map((t) => ({
|
||||||
|
id: t.id,
|
||||||
|
track: {
|
||||||
|
title: t.title,
|
||||||
|
album_id: t.album_id,
|
||||||
|
artist_id: t.artist_id,
|
||||||
|
track_num: t.track,
|
||||||
|
thumbnail_id: t.thumbnail_id,
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
} else {
|
||||||
|
reject(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeRequest(name: string, type: MessageType, options: object, callback: RequestCallback) {
|
||||||
|
// return if not authenticated, allow authentication messages
|
||||||
|
if (this.isClosed() || !this.authenticated && name != "authenticate") {
|
||||||
|
callback(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Unique enough for our purposes (as request ID)
|
||||||
|
const id = Math.random().toString(36).substring(2) + Date.now().toString(36);
|
||||||
|
this.callbacks.set(id, callback);
|
||||||
|
const payload = JSON.stringify({
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
options,
|
||||||
|
device_id: this.deviceId,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
console.trace("sending metadata message: " + payload);
|
||||||
|
this.ws!.send(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this.isClosed()) return;
|
||||||
|
this.ws!.close();
|
||||||
|
this.authenticated = false;
|
||||||
|
this.callbacks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
isClosed() {
|
||||||
|
return (
|
||||||
|
this.ws === null
|
||||||
|
|| this.ws.readyState === WebSocket.CLOSED
|
||||||
|
|| this.ws.readyState === WebSocket.CLOSING
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnect(cb: () => void) {
|
||||||
|
if (!this.isClosed() && this.authenticated) {
|
||||||
|
cb();
|
||||||
|
} else {
|
||||||
|
this.onConnectCallbacks = [...this.onConnectCallbacks, cb];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/components/a.svelte
Normal file
11
src/components/a.svelte
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
export let href: string;
|
||||||
|
|
||||||
|
$: isOnPage = href === $page.route.id;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a {href} class="btn {isOnPage ? 'variant-ghost-primary' : 'hover:variant-soft-primary'}">
|
||||||
|
<slot />
|
||||||
|
</a>
|
10
src/components/navbar.svelte
Normal file
10
src/components/navbar.svelte
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
import Link from './a.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav class="flex">
|
||||||
|
<Link href="/">t</Link>
|
||||||
|
<Link href="/albums">a</Link>
|
||||||
|
<Link href="/artists">a</Link>
|
||||||
|
<Link href="/settings">s</Link>
|
||||||
|
</nav>
|
20
src/components/track.svelte
Normal file
20
src/components/track.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Track } from '../types';
|
||||||
|
import { address, token } from '../stores';
|
||||||
|
|
||||||
|
export let track: Track;
|
||||||
|
$: url = `http://${$address}/thumbnail/${track.thumbnail_id}?token=${$token}`;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="card flex gap-2">
|
||||||
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
<div class="rounded placeholder w-12 h-12">
|
||||||
|
<img
|
||||||
|
src={url}
|
||||||
|
loading="lazy"
|
||||||
|
class="w-12 h-12"
|
||||||
|
on:error={(ev) => (ev.target.style.display = 'none')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{track.track_num} - {track.title}
|
||||||
|
</div>
|
81
src/routes/+layout.svelte
Normal file
81
src/routes/+layout.svelte
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<script>
|
||||||
|
// Your selected Skeleton theme:
|
||||||
|
import '@skeletonlabs/skeleton/themes/theme-vintage.css';
|
||||||
|
// This contains the bulk of Skeletons required styles:
|
||||||
|
import '@skeletonlabs/skeleton/styles/all.css';
|
||||||
|
import '../app.css';
|
||||||
|
|
||||||
|
import { AppShell, Toast, toastStore } from '@skeletonlabs/skeleton';
|
||||||
|
import Navbar from '../components/navbar.svelte';
|
||||||
|
import { address, token, tracks, tracksSorted } from '../stores';
|
||||||
|
import { _metadataComm as comm } from './+layout';
|
||||||
|
|
||||||
|
comm.setCallbacks({
|
||||||
|
onConnect: () => {
|
||||||
|
toastStore.trigger({
|
||||||
|
message: 'Successfully connected to the server',
|
||||||
|
background: 'variant-filled-success'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDisconnect: (authenticated, reason) => {
|
||||||
|
const errorMessage = authenticated ? 'Disconnected' : 'Error while connecting to the server';
|
||||||
|
toastStore.trigger({
|
||||||
|
message: `${errorMessage}: ${reason}`,
|
||||||
|
background: 'variant-filled-error',
|
||||||
|
autohide: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onIncompatible: (reason) => {
|
||||||
|
toastStore.trigger({
|
||||||
|
message: `Possible incompatible environment: ${reason}`,
|
||||||
|
background: 'variant-filled-warning',
|
||||||
|
autohide: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
comm.connect($address, $token);
|
||||||
|
comm.onConnect(() => {
|
||||||
|
comm
|
||||||
|
.fetchTracksCount()
|
||||||
|
.then((count) => {
|
||||||
|
let remaining = count;
|
||||||
|
console.log(count);
|
||||||
|
while (remaining > 0) {
|
||||||
|
const offset = count - remaining;
|
||||||
|
comm.fetchTracks(500, offset).then((ts) => {
|
||||||
|
tracks.update((map) => {
|
||||||
|
ts.forEach((t) => map.set(t.id, t.track));
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
tracksSorted.update((map) => {
|
||||||
|
ts.forEach((t, index) => map.set(index + offset, t.id));
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
remaining -= 500;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => null);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>musikspider</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<AppShell>
|
||||||
|
<svelte:fragment slot="footer">
|
||||||
|
<div class="flex w-screen place-content-center">
|
||||||
|
<div class="card m-2 sm:hidden z-1 fixed bottom-[48px]"><Navbar /></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="card m-2 max-md:m-0 px-4 flex flex-1 items-center min-h-[48px]">
|
||||||
|
now playing
|
||||||
|
<div class="mx-auto max-sm:hidden"><Navbar /></div>
|
||||||
|
<div class="max-sm:ml-auto">volume</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
|
<slot />
|
||||||
|
</AppShell>
|
||||||
|
<Toast />
|
6
src/routes/+layout.ts
Normal file
6
src/routes/+layout.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { MetadataCommunicator } from "../comms";
|
||||||
|
|
||||||
|
export const ssr = false;
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
export const _metadataComm = new MetadataCommunicator();
|
14
src/routes/+page.svelte
Normal file
14
src/routes/+page.svelte
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import VirtualList from 'svelte-tiny-virtual-list';
|
||||||
|
import { tracks, tracksSorted } from '../stores';
|
||||||
|
import Track from '../components/track.svelte';
|
||||||
|
|
||||||
|
$: trackCount = $tracksSorted.size;
|
||||||
|
let trackItemSize = 60;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<VirtualList height={trackCount * trackItemSize} itemSize={trackItemSize} itemCount={trackCount}>
|
||||||
|
<div slot="item" let:index let:style {style}>
|
||||||
|
<Track track={$tracks.get($tracksSorted.get(index) ?? BigInt(0))} />
|
||||||
|
</div>
|
||||||
|
</VirtualList>
|
18
src/routes/settings/+page.svelte
Normal file
18
src/routes/settings/+page.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
import { token, address } from '../../stores';
|
||||||
|
import Input from './input.svelte';
|
||||||
|
import { _metadataComm as comm } from '../+layout';
|
||||||
|
|
||||||
|
$: reconnect = () => comm.connect($address, $token);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 m-8 max-md:m-2">
|
||||||
|
<div class="card grid gap-1 p-4 max-w-md">
|
||||||
|
<h3>server settings</h3>
|
||||||
|
<Input store={address} name="address" />
|
||||||
|
<Input store={token} name="token" type="password" />
|
||||||
|
<button class="btn variant-filled hover:variant-filled-primary w-min" on:click={reconnect}
|
||||||
|
>connect</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
16
src/routes/settings/input.svelte
Normal file
16
src/routes/settings/input.svelte
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let store: any;
|
||||||
|
export let name: string;
|
||||||
|
export let type = 'text';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="label grid grid-cols-2 items-center gap-3">
|
||||||
|
<span class="badge variant-filled w-min text-sm">{name}</span>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
value={$store}
|
||||||
|
{name}
|
||||||
|
{type}
|
||||||
|
on:change={(ev) => store.set(ev.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
14
src/stores.ts
Normal file
14
src/stores.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import type { ResourceId, Track } from './types';
|
||||||
|
|
||||||
|
function writableStorage(key: string, defaultValue: string) {
|
||||||
|
const store = writable(localStorage.getItem(key) ?? defaultValue);
|
||||||
|
store.subscribe(value => localStorage.setItem(key, value));
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const address = writableStorage("address", "127.0.0.1:5505");
|
||||||
|
export const token = writableStorage("token", "");
|
||||||
|
|
||||||
|
export const tracks = writable<Map<ResourceId, Track>>(new Map());
|
||||||
|
export const tracksSorted = writable<Map<number, ResourceId>>(new Map());
|
24
src/types.ts
Normal file
24
src/types.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export type ResourceId = bigint;
|
||||||
|
|
||||||
|
export interface Track {
|
||||||
|
title: string,
|
||||||
|
track_num: number,
|
||||||
|
album_id: ResourceId,
|
||||||
|
artist_id: ResourceId,
|
||||||
|
thumbnail_id: ResourceId,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrackWithId {
|
||||||
|
id: ResourceId,
|
||||||
|
track: Track,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Artist {
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Album {
|
||||||
|
title: string,
|
||||||
|
artist_id: ResourceId,
|
||||||
|
thumbnail_id: ResourceId,
|
||||||
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
@ -1,6 +0,0 @@
|
|||||||
<svg width="40" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M34.092 8.845C38.929 20.652 34.092 27 30 30.5c1 3.5-2.986 4.222-4.5 2.5-4.457 1.537-13.512 1.487-20-5C2 24.5 4.73 16.714 14 11.5c8-4.5 16-7 20.092-2.655Z" fill="#FFDB1E"/>
|
|
||||||
<path d="M14 11.5c6.848-4.497 15.025-6.38 18.368-3.47C37.5 12.5 21.5 22.612 15.5 25c-6.5 2.587-3 8.5-6.5 8.5-3 0-2.5-4-5.183-7.75C2.232 23.535 6.16 16.648 14 11.5Z" fill="#fff" stroke="#FFDB1E"/>
|
|
||||||
<path d="M28.535 8.772c4.645 1.25-.365 5.695-4.303 8.536-3.732 2.692-6.606 4.21-7.923 4.83-.366.173-1.617-2.252-1.617-1 0 .417-.7 2.238-.934 2.326-1.365.512-4.223 1.29-5.835 1.29-3.491 0-1.923-4.754 3.014-9.122.892-.789 1.478-.645 2.283-.645-.537-.773-.534-.917.403-1.546C17.79 10.64 23 8.77 25.212 8.42c.366.014.82.35.82.629.41-.14 2.095-.388 2.503-.278Z" fill="#FFE600"/>
|
|
||||||
<path d="M14.297 16.49c.985-.747 1.644-1.01 2.099-2.526.566.121.841-.08 1.29-.701.324.466 1.657.608 2.453.701-.715.451-1.057.852-1.452 2.106-1.464-.611-3.167-.302-4.39.42Z" fill="#fff"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.0 KiB |
13
svelte.config.js
Normal file
13
svelte.config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import adapter from '@sveltejs/adapter-static';
|
||||||
|
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
17
tailwind.config.js
Normal file
17
tailwind.config.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
'./src/**/*.{html,js,svelte,ts}',
|
||||||
|
require('path').join(require.resolve(
|
||||||
|
'@skeletonlabs/skeleton'),
|
||||||
|
'../**/*.{html,js,svelte,ts}'
|
||||||
|
),
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
...require('@skeletonlabs/skeleton/tailwind/skeleton.cjs')()
|
||||||
|
],
|
||||||
|
darkMode: 'class',
|
||||||
|
}
|
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
import { Options } from "$fresh/plugins/twind.ts";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
selfURL: import.meta.url,
|
|
||||||
} as Options;
|
|
6
vite.config.ts
Normal file
6
vite.config.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()]
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user