feat: initial commit

This commit is contained in:
metamethods 2024-12-31 07:12:29 -08:00
commit 64ef290806
46 changed files with 3577 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

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

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock

15
.prettierrc Normal file
View File

@ -0,0 +1,15 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2024 metamethods
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
## 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://svelte.dev/docs/kit/adapters) for your target environment.

34
eslint.config.js Normal file
View File

@ -0,0 +1,34 @@
import prettier from 'eslint-config-prettier';
import js from '@eslint/js';
import { includeIgnoreFile } from '@eslint/compat';
import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
import { fileURLToPath } from 'node:url';
import ts from 'typescript-eslint';
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default ts.config(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs['flat/recommended'],
prettier,
...svelte.configs['flat/prettier'],
{
languageOptions: {
globals: {
...globals.browser,
...globals.node
}
}
},
{
files: ['**/*.svelte'],
languageOptions: {
parserOptions: {
parser: ts.parser
}
}
}
);

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "chuni-national-matchmaking-webui",
"private": true,
"version": "0.0.1",
"type": "module",
"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",
"format": "prettier --write .",
"lint": "prettier --check . && eslint ."
},
"devDependencies": {
"@eslint/compat": "^1.2.3",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",
"globals": "^15.0.0",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.6.5",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.9",
"typescript": "^5.0.0",
"typescript-eslint": "^8.0.0",
"vite": "^5.4.11"
},
"dependencies": {
"@fontsource-variable/plus-jakarta-sans": "^5.1.1",
"axios": "^1.7.9",
"lucide-svelte": "^0.469.0"
}
}

2807
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

7
src/app.css Normal file
View File

@ -0,0 +1,7 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
a {
@apply text-accent;
}

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

@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

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, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" class="bg-dark text-light">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

20
src/lib/BattleRank.svelte Normal file
View File

@ -0,0 +1,20 @@
<script lang="ts">
import { RANK_IMAGE_WIDTH, RANK_IMAGE_HEIGHT, imageOffset } from './battleRank';
import type { HTMLAttributes } from 'svelte/elements';
let { rank, ...rest }: { rank: number } & HTMLAttributes<HTMLDivElement> = $props();
const [xOffset, yOffset] = imageOffset(rank);
</script>
<div {...rest}>
<svg viewBox="0 0 {RANK_IMAGE_WIDTH} {RANK_IMAGE_HEIGHT}" xmlns="http://www.w3.org/2000/svg">
<image
id="img"
href="/battle_ranks.png"
width="638"
height="475"
transform="translate({-xOffset} {-yOffset})"
/>
</svg>
</div>

8
src/lib/Footer.svelte Normal file
View File

@ -0,0 +1,8 @@
<footer class="bg-medium">
<div class="mx-auto w-full max-w-screen-xl p-4">
<p>
made with <a href="https://svelte.dev/">sveltekit</a>, powered by
<a href="https://cloudflare.com/">cloudflare</a>
</p>
</div>
</footer>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import { fromVersionString } from './versioning';
import gameVersions from './gameVersions';
import type { HTMLAttributes } from 'svelte/elements';
let { version, ...rest }: { version: string } & HTMLAttributes<HTMLImageElement> = $props();
const { minor } = fromVersionString(version);
</script>
<img src="/versions/{gameVersions[minor.toString()]}" alt={version} {...rest} />

21
src/lib/Player.svelte Normal file
View File

@ -0,0 +1,21 @@
<script lang="ts">
import BattleRank from './BattleRank.svelte';
import Rating from './Rating.svelte';
import Team from './Team.svelte';
import type { Player } from './types';
let { player }: { player: Player } = $props();
</script>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<p>{player.name}</p>
<div class="flex gap-2">
<Rating rating={player.rating} />
<BattleRank rank={player.battleRank} class="w-16" />
</div>
</div>
{#if player.team}
<Team team={player.team} />
{/if}
</div>

18
src/lib/Rating.svelte Normal file
View File

@ -0,0 +1,18 @@
<script lang="ts">
import RatingCharacter from './RatingCharacter.svelte';
import { ratingType } from './ratings';
let { rating }: { rating: number } = $props();
const characters = rating.toString().padStart(4, '0');
const type = ratingType(rating);
</script>
<div class="flex items-center">
{#each characters as ratingCharacter, i}
{#if i == characters.length - 2}
<RatingCharacter character={'.'} {type} class="w-3" />
{/if}
<RatingCharacter character={Number(ratingCharacter)} {type} class="w-3" />
{/each}
</div>

View File

@ -0,0 +1,27 @@
<script lang="ts">
import { CHARACTER_IMAGE_WIDTH, CHARACTER_IMAGE_HEIGHT, imageOffset } from './ratings';
import type { HTMLAttributes } from 'svelte/elements';
let {
character,
type,
...rest
}: { character: number | '.'; type: number } & HTMLAttributes<HTMLDivElement> = $props();
const [xOffset, yOffset] = imageOffset(character, type);
</script>
<div {...rest}>
<svg
viewBox="0 0 {CHARACTER_IMAGE_WIDTH} {CHARACTER_IMAGE_HEIGHT}"
xmlns="http://www.w3.org/2000/svg"
>
<image
id="img"
href="/ratings.png"
width="247"
height="159"
transform="translate({-xOffset} {-yOffset})"
/>
</svg>
</div>

45
src/lib/Room.svelte Normal file
View File

@ -0,0 +1,45 @@
<script lang="ts">
import { User, Calendar } from 'lucide-svelte';
import Player from './Player.svelte';
import BattleRank from './BattleRank.svelte';
import GameVersion from './GameVersion.svelte';
import Separator from './Separator.svelte';
import type { Room } from './types';
let { room }: { room: Room } = $props();
</script>
<div class="flex flex-col gap-8 rounded-lg bg-medium p-6">
<div class="flex flex-col gap-4 sm:flex-row sm:justify-between">
<div class="flex flex-col gap-2">
<div class="flex flex-col items-center gap-4 sm:flex-row sm:items-start">
<GameVersion version={room.gameVersion} class="w-48 sm:w-32" />
<BattleRank rank={room.roomBattleRank} class="w-28 sm:w-20" />
</div>
</div>
<div class="flex flex-col">
<p class="flex items-center gap-2 text-sub">game version <Separator /> {room.gameVersion}</p>
<p class="flex items-center gap-2 text-sub">
room battle rank <Separator />
{room.roomBattleRank}
</p>
</div>
</div>
<div class="flex flex-col gap-4">
<p class="flex items-center gap-2">
<User class="inline-block w-6" />
{room.players.length} / 4
<Separator />
<Calendar class="inline-block w-6" />
{room.createdAt.toLocaleString()}
</p>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:grid-rows-2">
{#each room.players as player}
<Player {player} />
{/each}
</div>
</div>
</div>

1
src/lib/Separator.svelte Normal file
View File

@ -0,0 +1 @@
<div class="inline-block h-[2px] w-4 rounded-full bg-current brightness-50"></div>

41
src/lib/Team.svelte Normal file
View File

@ -0,0 +1,41 @@
<script lang="ts">
import type { Team } from './types';
let { team }: { team: Team } = $props();
</script>
<div
class="flex items-center justify-between rounded-md bg-dark px-2 py-1 {team.rank == 1
? 'team-first'
: team.rank == 2
? 'team-second'
: team.rank == 3
? 'team-third'
: ''}"
>
<p>
{team.name}
</p>
{#if team.rank <= 5}
<p class="font-bold">
#{team.rank}
</p>
{/if}
</div>
<style>
.team-first {
background-image: linear-gradient(135deg, #f4a7fd 0%, #8768f4 50%, #55f28b 100%);
@apply text-dark;
}
.team-second {
@apply bg-gradient-to-br from-yellow-400 to-yellow-600 text-dark;
}
.team-third {
@apply bg-gradient-to-br from-teal-400 to-blue-600 text-dark;
}
</style>

3
src/lib/array.ts Normal file
View File

@ -0,0 +1,3 @@
export function arrayPadEnd<T>(array: T[], amount: number, data: T) {
for (let i = 0; i < amount - array.length; i++) array.push(data);
}

14
src/lib/battleRank.ts Normal file
View File

@ -0,0 +1,14 @@
export const RANK_IMAGE_WIDTH = 120;
export const RANK_IMAGE_HEIGHT = 57;
const ROWS = 5;
const ROWS_SPACING = 41;
const COLUMNS = 5;
const COLUMS_SPACING = 10;
export function imageOffset(rank: number): [number, number] {
const row = rank % ROWS;
const column = ~~(rank / COLUMNS);
return [(RANK_IMAGE_WIDTH + COLUMS_SPACING) * row, (RANK_IMAGE_HEIGHT + ROWS_SPACING) * column];
}

22
src/lib/gameVersions.ts Normal file
View File

@ -0,0 +1,22 @@
// we are just assuming that major is just gonna stay at 2
// then just map every minor version for the game version here
export default {
'0': 'new.png',
'1': 'new.png',
'2': 'new.png',
'5': 'new_plus.png',
'10': 'sun.png',
'11': 'sun.png',
'15': 'sun_plus.png',
'16': 'sun_plus.png',
'20': 'luminous.png',
'22': 'luminous.png',
'25': 'luminous_plus.png',
'26': 'luminous_plus.png',
'27': 'luminous_plus.png'
} as Record<string, string>;

26
src/lib/ratings.ts Normal file
View File

@ -0,0 +1,26 @@
export const CHARACTER_IMAGE_WIDTH = 14;
export const CHARACTER_IMAGE_HEIGHT = 19;
const ROWS = 6;
const ROWS_SPACING = 9;
const COLUMNS = 11;
const COLUMS_SPACING = 10;
export function ratingType(rating: number): number {
if (rating <= 1199) return 4;
else if (rating <= 1324) return 3;
else if (rating <= 1449) return 2;
else if (rating <= 1524) return 1;
else if (rating <= 1599) return 0;
else return 5;
}
export function imageOffset(character: number | '.', type: number): [number, number] {
const row = type % ROWS;
const column = (character == '.' ? 10 : character) % COLUMNS;
return [
(CHARACTER_IMAGE_WIDTH + COLUMS_SPACING) * column,
(CHARACTER_IMAGE_HEIGHT + ROWS_SPACING) * row
];
}

View File

@ -0,0 +1,55 @@
import { env } from '$env/dynamic/private';
import type { APIPlayer, APIRoom, Player, Room } from '$lib/types';
import { fromVersionString, greaterThan } from '$lib/versioning';
import axios from 'axios';
import type { FetchFunction } from 'vite';
function toPlayer(apiPlayer: APIPlayer): Player {
return {
name: apiPlayer.userName,
rating: Number(apiPlayer.playerRating),
battleRank: Number(apiPlayer.battleRankId),
team:
apiPlayer.isJoinTeam == 'true'
? {
name: apiPlayer.teamName,
rank: Number(apiPlayer.teamRank) - 1 // team ranks are one off for some reason?
}
: undefined
};
}
function toRoom(apiRoom: APIRoom): Room {
return {
id: apiRoom.roomId,
createdAt: new Date(apiRoom.updatedAt),
gameVersion: apiRoom.dataVersion,
roomBattleRank: apiRoom.roomRanking,
players: apiRoom.matchingMemberInfoList.map((apiPlayer) => toPlayer(apiPlayer))
};
}
function filterRooms(rooms: Room[]): Room[] {
return rooms.filter(
(room) => !greaterThan(fromVersionString(room.gameVersion), fromVersionString(env.MAX_VERSION))
);
}
export async function fetchData() {
return {
activeRooms: await axios
.get<APIRoom[]>('http://yukiotoko.chara.lol:9000/api/active', {
headers: {
Authorization: env.YUKIOTOKO_API_TOKEN
}
})
.then((result) => filterRooms(result.data.map((apiRoom) => toRoom(apiRoom)))),
archivedRooms: await axios
.get<APIRoom[]>('http://yukiotoko.chara.lol:9000/api/history', {
headers: {
Authorization: env.YUKIOTOKO_API_TOKEN
}
})
.then((result) => filterRooms(result.data.map((apiRoom) => toRoom(apiRoom))))
};
}

79
src/lib/types.ts Normal file
View File

@ -0,0 +1,79 @@
export interface Team {
name: string;
rank: number;
}
export interface Player {
name: string;
rating: number;
battleRank: number;
team?: Team;
}
export interface APIPlayer {
errCnt: string;
userId: string;
placeId: string;
skillId: string;
skillLv: string;
clientId?: unknown;
joinTime: string;
reginId: string;
teamName: string;
teamRank: string;
trophyId: string;
userName: string;
messageId: string;
emblemBase: string;
hostErrCnt: string;
isJoinTeam: string;
romVersion: string;
avatarEquip: {
backID: string;
faceID: string;
headID: string;
itemID: string;
skinID: string;
wearID: string;
frontID: string;
};
characterId: string;
dataVersion: string;
emblemMedal: string;
optRatingId: string;
battleIconId: string;
battleRankId: string;
playerRating: string;
battleIconNum: string;
bestRatingAvg: string;
characterRank: string;
avatarEffectID: string;
genreGraphList: { genreId: string; musicCount: string }[];
giftMusicIdList: { musicId: string }[];
skillIdForChara: string;
battleCorrection: string;
ratingEffectColorId: string;
}
export interface Room {
id: number;
createdAt: Date;
gameVersion: string;
roomBattleRank: number;
players: Player[];
}
export interface APIRoom {
userId: string;
roomId: number;
dataVersion: string;
romVersion: string;
roomRanking: number;
roomMSec: number;
isFull: boolean;
matchingMemberInfoList: APIPlayer[];
isFinished: boolean;
allowAnybody: boolean;
updatedAt: string;
mergedRoom?: unknown;
}

20
src/lib/versioning.ts Normal file
View File

@ -0,0 +1,20 @@
interface Version {
major: number;
minor: number;
patch: number;
}
export function fromVersionString(versionString: string): Version {
const [_, major, minor, patch] = /(\d+)\.(\d+)\.(\d+)/.exec(versionString) ?? [null, -1, -1, -1];
return { major: Number(major), minor: Number(minor), patch: Number(patch) };
}
export function greaterThan(versionA: Version, versionB: Version): boolean {
return (
versionA.major > versionB.major ||
(versionA.major === versionB.major && versionA.minor > versionB.minor) ||
(versionA.major === versionB.major &&
versionA.minor === versionB.minor &&
versionA.patch > versionB.patch)
);
}

15
src/routes/+layout.svelte Normal file
View File

@ -0,0 +1,15 @@
<script lang="ts">
import Footer from '$lib/Footer.svelte';
import '@fontsource-variable/plus-jakarta-sans';
import '../app.css';
let { children } = $props();
</script>
<div class="min-h-screen">
<main class="mx-auto w-full max-w-screen-xl p-4">
{@render children()}
</main>
</div>
<Footer />

View File

@ -0,0 +1,6 @@
import type { PageServerLoad } from './$types';
import { fetchData } from '$lib/server/fetchData';
export const load: PageServerLoad = async ({ fetch }) => {
return await fetchData();
};

68
src/routes/+page.svelte Normal file
View File

@ -0,0 +1,68 @@
<script lang="ts">
import { arrayPadEnd } from '$lib/array';
import Room from '$lib/Room.svelte';
import axios from 'axios';
import type { PageData } from './$types';
let props: { data: PageData } = $props();
let data = $state(props.data);
let refreshTimer = $state(60);
async function refresh() {
data = await axios.get('/api/data').then((result) => result.data);
}
$effect(() =>
data.archivedRooms.forEach((archivedRoom) =>
arrayPadEnd(archivedRoom.players, 4, {
name: 'CPU',
battleRank: 0,
rating: 0
})
)
);
</script>
<div class="mb-16 space-y-8">
<section>
<h1 class="text-4xl font-bold">yukiotoko webui</h1>
<p>a frontend redesign for <a href="http://yukiotoko.chara.lol/">yukiotoko</a></p>
</section>
<div>
<button class="bg-accent rounded-lg px-3 py-2" onclick={refresh}>Refresh</button>
<p class="text-sub">load the current data from yukiotoko</p>
</div>
</div>
<div class="flex flex-col gap-8">
<section class="flex flex-col gap-4">
<div>
<h1 class="text-2xl font-bold">
active rooms <span class="text-sub">({data.activeRooms.length})</span>
</h1>
<p class="text-sub">all of the currently matchmaking rooms</p>
</div>
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
{#each data.activeRooms as activeRoom}
<Room room={activeRoom} />
{/each}
</div>
</section>
<section class="flex flex-col gap-4">
<div>
<h1 class="text-2xl font-bold">
archived rooms <span class="text-sub">({data.archivedRooms.length})</span>
</h1>
<p class="text-sub">rooms that were created from the last 24 hours</p>
</div>
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
{#each data.archivedRooms as archivedRoom}
<Room room={archivedRoom} />
{/each}
</div>
</section>
</div>

View File

@ -0,0 +1,5 @@
import { fetchData } from '$lib/server/fetchData';
export async function GET({}) {
return new Response(JSON.stringify(await fetchData()));
}

BIN
static/battle_ranks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/ratings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
static/versions/new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 KiB

BIN
static/versions/sun.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB

18
svelte.config.js Normal file
View File

@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

22
tailwind.config.ts Normal file
View File

@ -0,0 +1,22 @@
import defaultTheme from 'tailwindcss/defaultTheme';
import type { Config } from 'tailwindcss';
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {
colors: {
dark: '#060710',
medium: '#0A0B17',
light: '#CFD3FA',
sub: '#4D4D80',
accent: '#7E61FF'
},
fontFamily: {
sans: ['Plus Jakarta Sans Variable', ...defaultTheme.fontFamily.sans]
}
}
}
} satisfies Config;

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// 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
}

6
vite.config.ts Normal file
View File

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});