Initial commit
This commit is contained in:
commit
d06e6d913e
27 changed files with 4378 additions and 0 deletions
6
.devdbrc
Normal file
6
.devdbrc
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "sqlite",
|
||||||
|
"path": "/home/samuel/Programmieren/signal-stats/database.sqlite"
|
||||||
|
}
|
||||||
|
]
|
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
dist
|
||||||
|
.solid
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.vinxi
|
||||||
|
app.config.timestamp_*.js
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
gitignore
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# WASM file
|
||||||
|
/src/assets/sql-wasm.wasm
|
||||||
|
/src/assets/database.sqlite
|
36
README.md
Normal file
36
README.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
|
||||||
|
|
||||||
|
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install # or pnpm install or yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm run dev` or `npm start`
|
||||||
|
|
||||||
|
Runs the app in the development mode.<br>
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||||
|
|
||||||
|
The page will reload if you make edits.<br>
|
||||||
|
|
||||||
|
### `npm run build`
|
||||||
|
|
||||||
|
Builds the app for production to the `dist` folder.<br>
|
||||||
|
It correctly bundles Solid in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
|
The build is minified and the filenames include the hashes.<br>
|
||||||
|
Your app is ready to be deployed!
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
|
||||||
|
|
||||||
|
## This project was created with the [Solid CLI](https://solid-cli.netlify.app)
|
30
db_layout.md
Normal file
30
db_layout.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A --> D
|
||||||
|
B --> D
|
||||||
|
D --> F
|
||||||
|
F --> D
|
||||||
|
E --> G
|
||||||
|
G --> E
|
||||||
|
C --> |I think this is the only way to match messages to groups and recipients| H
|
||||||
|
I --> D
|
||||||
|
|
||||||
|
subgraph message
|
||||||
|
A[message.from_recipient_id]
|
||||||
|
B[message.to_recipient_id]
|
||||||
|
C[message.thread_id]
|
||||||
|
end
|
||||||
|
subgraph recipient
|
||||||
|
D[recipient._id]
|
||||||
|
E[recipient.group_id]
|
||||||
|
end
|
||||||
|
subgraph groups
|
||||||
|
F[groups.recipient_id]
|
||||||
|
G[groups.group_id]
|
||||||
|
end
|
||||||
|
subgraph thread
|
||||||
|
H[thread._id]
|
||||||
|
I[thread.recipient_id]
|
||||||
|
J@{ shape: braces, label: "Threads seem to be rooms in Signal" }
|
||||||
|
end
|
||||||
|
```
|
72
eslint.config.js
Normal file
72
eslint.config.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import eslint from "@eslint/js";
|
||||||
|
import tsparser from "@typescript-eslint/parser";
|
||||||
|
import eslintConfigPrettier from "eslint-config-prettier";
|
||||||
|
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
||||||
|
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
||||||
|
import solid from "eslint-plugin-solid/configs/typescript";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default tseslint.config([
|
||||||
|
eslint.configs.recommended,
|
||||||
|
// tseslint.configs.recommendedTypeChecked,
|
||||||
|
// tseslint.configs.strictTypeChecked,
|
||||||
|
// tseslint.configs.stylisticTypeChecked,
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
|
eslintConfigPrettier,
|
||||||
|
{
|
||||||
|
...solid,
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
// Specifies the ESLint parser
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2024,
|
||||||
|
// Allows for the parsing of modern ECMAScript features
|
||||||
|
sourceType: "module",
|
||||||
|
// Allows for the use of imports
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true, // Allows for the parsing of JSX
|
||||||
|
},
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
projectService: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: {
|
||||||
|
"simple-import-sort": simpleImportSort,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
|
||||||
|
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"simple-import-sort/imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
// solidjs
|
||||||
|
["^solid-(js|start)", "^@solidjs/"],
|
||||||
|
["^@?\\w"],
|
||||||
|
// components imports
|
||||||
|
["^~/components/?"],
|
||||||
|
// other /src imports
|
||||||
|
["^~/"],
|
||||||
|
// Parent imports. Put `..` last.
|
||||||
|
["^\\.\\.(?!/?$)", "^\\.\\./?$"],
|
||||||
|
// Other relative imports. Put same-folder imports and `.` last.
|
||||||
|
["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
|
||||||
|
// types imports
|
||||||
|
["^~/types$"],
|
||||||
|
// Side effect imports.
|
||||||
|
["^\\u0000"],
|
||||||
|
// Style imports.
|
||||||
|
["^.+\\.?(s?css)$"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
|
// "@typescript-eslint/consistent-type-imports": "error",
|
||||||
|
// "@typescript-eslint/consistent-type-exports": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
16
index.html
Normal file
16
index.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
|
||||||
|
<title>Solid App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script src="/src/index.tsx" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
48
package.json
Normal file
48
package.json
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"name": "vite-template-solid",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "vite",
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"serve": "vite preview",
|
||||||
|
"postinstall": "cp ./node_modules/sql.js/dist/sql-wasm.wasm ./src/assets/sql-wasm.wasm"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.16.0",
|
||||||
|
"@types/node": "^22.10.1",
|
||||||
|
"@types/sql.js": "^1.4.9",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
||||||
|
"@typescript-eslint/parser": "^8.17.0",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"eslint": "^9.16.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
|
"eslint-plugin-solid": "^0.14.4",
|
||||||
|
"postcss": "^8.4.49",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||||
|
"tailwindcss": "^3.4.16",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"typescript-eslint": "^8.17.0",
|
||||||
|
"vite": "^6.0.3",
|
||||||
|
"vite-plugin-solid": "^2.11.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@kobalte/core": "^0.13.7",
|
||||||
|
"@kobalte/tailwindcss": "^0.9.0",
|
||||||
|
"@solid-primitives/refs": "^1.0.8",
|
||||||
|
"@solidjs/router": "^0.15.1",
|
||||||
|
"chart.js": "^4.4.7",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"solid-js": "^1.9.3",
|
||||||
|
"sql.js": "^1.12.0",
|
||||||
|
"tailwind-merge": "^2.5.5",
|
||||||
|
"tailwindcss-animate": "^1.0.7"
|
||||||
|
}
|
||||||
|
}
|
3158
pnpm-lock.yaml
generated
Normal file
3158
pnpm-lock.yaml
generated
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: {},
|
||||||
|
},
|
||||||
|
};
|
9
prettier.config.js
Normal file
9
prettier.config.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export default {
|
||||||
|
semi: true,
|
||||||
|
trailingComma: "all",
|
||||||
|
singleQuote: false,
|
||||||
|
printWidth: 120,
|
||||||
|
tabWidth: 2,
|
||||||
|
singleAttributePerLine: true,
|
||||||
|
plugins: ["prettier-plugin-tailwindcss"],
|
||||||
|
};
|
33
src/App.module.css
Normal file
33
src/App.module.css
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
animation: logo-spin infinite 20s linear;
|
||||||
|
height: 40vmin;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: #282c34;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: calc(10px + 2vmin);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: #b318f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
20
src/App.tsx
Normal file
20
src/App.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { type Component } from "solid-js";
|
||||||
|
import { Route } from "@solidjs/router";
|
||||||
|
import { Home, Overview } from "./pages";
|
||||||
|
|
||||||
|
const App: Component = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Route
|
||||||
|
path="/"
|
||||||
|
component={Home}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/overview"
|
||||||
|
component={Overview}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
141
src/app.css
Normal file
141
src/app.css
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 240 10% 3.9%;
|
||||||
|
|
||||||
|
--muted: 240 4.8% 95.9%;
|
||||||
|
--muted-foreground: 240 3.8% 46.1%;
|
||||||
|
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 240 10% 3.9%;
|
||||||
|
|
||||||
|
--border: 240 5.9% 90%;
|
||||||
|
--input: 240 5.9% 90%;
|
||||||
|
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 240 10% 3.9%;
|
||||||
|
|
||||||
|
--primary: 240 5.9% 10%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--secondary: 240 4.8% 95.9%;
|
||||||
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
|
|
||||||
|
--accent: 240 4.8% 95.9%;
|
||||||
|
--accent-foreground: 240 5.9% 10%;
|
||||||
|
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--info: 204 94% 94%;
|
||||||
|
--info-foreground: 199 89% 48%;
|
||||||
|
|
||||||
|
--success: 149 80% 90%;
|
||||||
|
--success-foreground: 160 84% 39%;
|
||||||
|
|
||||||
|
--warning: 48 96% 89%;
|
||||||
|
--warning-foreground: 25 95% 53%;
|
||||||
|
|
||||||
|
--error: 0 93% 94%;
|
||||||
|
--error-foreground: 0 84% 60%;
|
||||||
|
|
||||||
|
--ring: 240 5.9% 10%;
|
||||||
|
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark,
|
||||||
|
[data-kb-theme="dark"] {
|
||||||
|
--background: 240 10% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--muted: 240 3.7% 15.9%;
|
||||||
|
--muted-foreground: 240 5% 64.9%;
|
||||||
|
|
||||||
|
--accent: 240 3.7% 15.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--popover: 240 10% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--border: 240 3.7% 15.9%;
|
||||||
|
--input: 240 3.7% 15.9%;
|
||||||
|
|
||||||
|
--card: 240 10% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 240 5.9% 10%;
|
||||||
|
|
||||||
|
--secondary: 240 3.7% 15.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--info: 204 94% 94%;
|
||||||
|
--info-foreground: 199 89% 48%;
|
||||||
|
|
||||||
|
--success: 149 80% 90%;
|
||||||
|
--success-foreground: 160 84% 39%;
|
||||||
|
|
||||||
|
--warning: 48 96% 89%;
|
||||||
|
--warning-foreground: 25 95% 53%;
|
||||||
|
|
||||||
|
--error: 0 93% 94%;
|
||||||
|
--error-foreground: 0 84% 60%;
|
||||||
|
|
||||||
|
--ring: 240 4.9% 83.9%;
|
||||||
|
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
font-feature-settings:
|
||||||
|
"rlig" 1,
|
||||||
|
"calt" 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.step {
|
||||||
|
counter-increment: step;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step:before {
|
||||||
|
@apply absolute w-9 h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background;
|
||||||
|
@apply ml-[-50px] mt-[-4px];
|
||||||
|
content: counter(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.container {
|
||||||
|
@apply px-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 4px solid transparent;
|
||||||
|
background-clip: content-box;
|
||||||
|
@apply bg-accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-corner {
|
||||||
|
display: none;
|
||||||
|
}
|
292
src/components/ui/charts.tsx
Normal file
292
src/components/ui/charts.tsx
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
import type { Component } from "solid-js"
|
||||||
|
import { createEffect, createSignal, mergeProps, on, onCleanup, onMount } from "solid-js"
|
||||||
|
import { unwrap } from "solid-js/store"
|
||||||
|
|
||||||
|
import type { Ref } from "@solid-primitives/refs"
|
||||||
|
import { mergeRefs } from "@solid-primitives/refs"
|
||||||
|
import type {
|
||||||
|
ChartComponent,
|
||||||
|
ChartData,
|
||||||
|
ChartItem,
|
||||||
|
ChartOptions,
|
||||||
|
Plugin as ChartPlugin,
|
||||||
|
ChartType,
|
||||||
|
ChartTypeRegistry,
|
||||||
|
TooltipModel
|
||||||
|
} from "chart.js"
|
||||||
|
import {
|
||||||
|
ArcElement,
|
||||||
|
BarController,
|
||||||
|
BarElement,
|
||||||
|
BubbleController,
|
||||||
|
CategoryScale,
|
||||||
|
Chart,
|
||||||
|
Colors,
|
||||||
|
DoughnutController,
|
||||||
|
Filler,
|
||||||
|
Legend,
|
||||||
|
LinearScale,
|
||||||
|
LineController,
|
||||||
|
LineElement,
|
||||||
|
PieController,
|
||||||
|
PointElement,
|
||||||
|
PolarAreaController,
|
||||||
|
RadarController,
|
||||||
|
RadialLinearScale,
|
||||||
|
ScatterController,
|
||||||
|
Tooltip
|
||||||
|
} from "chart.js"
|
||||||
|
|
||||||
|
type TypedChartProps = {
|
||||||
|
data: ChartData
|
||||||
|
options?: ChartOptions
|
||||||
|
plugins?: ChartPlugin[]
|
||||||
|
ref?: Ref<HTMLCanvasElement | null>
|
||||||
|
width?: number | undefined
|
||||||
|
height?: number | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartProps = TypedChartProps & {
|
||||||
|
type: ChartType
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartContext = {
|
||||||
|
chart: Chart
|
||||||
|
tooltip: TooltipModel<keyof ChartTypeRegistry>
|
||||||
|
}
|
||||||
|
|
||||||
|
const BaseChart: Component<ChartProps> = (rawProps) => {
|
||||||
|
const [canvasRef, setCanvasRef] = createSignal<HTMLCanvasElement | null>()
|
||||||
|
const [chart, setChart] = createSignal<Chart>()
|
||||||
|
|
||||||
|
const props = mergeProps(
|
||||||
|
{
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
options: { responsive: true } as ChartOptions,
|
||||||
|
plugins: [] as ChartPlugin[]
|
||||||
|
},
|
||||||
|
rawProps
|
||||||
|
)
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
const ctx = canvasRef()?.getContext("2d") as ChartItem
|
||||||
|
const config = unwrap(props)
|
||||||
|
const chart = new Chart(ctx, {
|
||||||
|
type: config.type,
|
||||||
|
data: config.data,
|
||||||
|
options: config.options,
|
||||||
|
plugins: config.plugins
|
||||||
|
})
|
||||||
|
setChart(chart)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => init())
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => props.data,
|
||||||
|
() => {
|
||||||
|
chart()!.data = props.data
|
||||||
|
chart()!.update()
|
||||||
|
},
|
||||||
|
{ defer: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => props.options,
|
||||||
|
() => {
|
||||||
|
chart()!.options = props.options
|
||||||
|
chart()!.update()
|
||||||
|
},
|
||||||
|
{ defer: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
[() => props.width, () => props.height],
|
||||||
|
() => {
|
||||||
|
chart()!.resize(props.width, props.height)
|
||||||
|
},
|
||||||
|
{ defer: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => props.type,
|
||||||
|
() => {
|
||||||
|
const dimensions = [chart()!.width, chart()!.height]
|
||||||
|
chart()!.destroy()
|
||||||
|
init()
|
||||||
|
chart()!.resize(...dimensions)
|
||||||
|
},
|
||||||
|
{ defer: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
chart()?.destroy()
|
||||||
|
mergeRefs(props.ref, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
Chart.register(Colors, Filler, Legend, Tooltip)
|
||||||
|
return (
|
||||||
|
<canvas
|
||||||
|
ref={mergeRefs(props.ref, (el) => setCanvasRef(el))}
|
||||||
|
height={props.height}
|
||||||
|
width={props.width}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTooltip(context: ChartContext) {
|
||||||
|
let el = document.getElementById("chartjs-tooltip")
|
||||||
|
if (!el) {
|
||||||
|
el = document.createElement("div")
|
||||||
|
el.id = "chartjs-tooltip"
|
||||||
|
document.body.appendChild(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = context.tooltip
|
||||||
|
if (model.opacity === 0 || !model.body) {
|
||||||
|
el.style.opacity = "0"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
el.className = `p-2 bg-card text-card-foreground rounded-lg border shadow-sm text-sm ${
|
||||||
|
model.yAlign ?? `no-transform`
|
||||||
|
}`
|
||||||
|
|
||||||
|
let content = ""
|
||||||
|
|
||||||
|
model.title.forEach((title) => {
|
||||||
|
content += `<h3 class="font-semibold leading-none tracking-tight">${title}</h3>`
|
||||||
|
})
|
||||||
|
|
||||||
|
content += `<div class="mt-1 text-muted-foreground">`
|
||||||
|
const body = model.body.flatMap((body) => body.lines)
|
||||||
|
body.forEach((line, i) => {
|
||||||
|
const colors = model.labelColors[i]
|
||||||
|
content += `
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="inline-block h-2 w-2 mr-1 rounded-full border" style="background: ${colors.backgroundColor}; border-color: ${colors.borderColor}"></span>
|
||||||
|
${line}
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
content += `</div>`
|
||||||
|
|
||||||
|
el.innerHTML = content
|
||||||
|
|
||||||
|
const pos = context.chart.canvas.getBoundingClientRect()
|
||||||
|
el.style.opacity = "1"
|
||||||
|
el.style.position = "absolute"
|
||||||
|
el.style.left = `${pos.left + window.scrollX + model.caretX}px`
|
||||||
|
el.style.top = `${pos.top + window.scrollY + model.caretY}px`
|
||||||
|
el.style.pointerEvents = "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTypedChart(
|
||||||
|
type: ChartType,
|
||||||
|
components: ChartComponent[]
|
||||||
|
): Component<TypedChartProps> {
|
||||||
|
const chartsWithScales: ChartType[] = ["bar", "line", "scatter"]
|
||||||
|
const chartsWithLegends: ChartType[] = ["bar", "line"]
|
||||||
|
|
||||||
|
const options: ChartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: chartsWithScales.includes(type)
|
||||||
|
? {
|
||||||
|
x: {
|
||||||
|
border: { display: false },
|
||||||
|
grid: { display: false }
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
border: {
|
||||||
|
dash: [3],
|
||||||
|
dashOffset: 3,
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: "hsla(240, 3.8%, 46.1%, 0.4)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
plugins: {
|
||||||
|
legend: chartsWithLegends.includes(type)
|
||||||
|
? {
|
||||||
|
display: true,
|
||||||
|
align: "end",
|
||||||
|
labels: {
|
||||||
|
usePointStyle: true,
|
||||||
|
boxWidth: 6,
|
||||||
|
boxHeight: 6,
|
||||||
|
color: "hsl(240, 3.8%, 46.1%)",
|
||||||
|
font: { size: 14 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
enabled: false,
|
||||||
|
external: (context) => showTooltip(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Chart.register(...components)
|
||||||
|
return (props) => <BaseChart type={type} options={options} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const BarChart = /* #__PURE__ */ createTypedChart("bar", [
|
||||||
|
BarController,
|
||||||
|
BarElement,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale
|
||||||
|
])
|
||||||
|
const BubbleChart = /* #__PURE__ */ createTypedChart("bubble", [
|
||||||
|
BubbleController,
|
||||||
|
PointElement,
|
||||||
|
LinearScale
|
||||||
|
])
|
||||||
|
const DonutChart = /* #__PURE__ */ createTypedChart("doughnut", [DoughnutController, ArcElement])
|
||||||
|
const LineChart = /* #__PURE__ */ createTypedChart("line", [
|
||||||
|
LineController,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale
|
||||||
|
])
|
||||||
|
const PieChart = /* #__PURE__ */ createTypedChart("pie", [PieController, ArcElement])
|
||||||
|
const PolarAreaChart = /* #__PURE__ */ createTypedChart("polarArea", [
|
||||||
|
PolarAreaController,
|
||||||
|
ArcElement,
|
||||||
|
RadialLinearScale
|
||||||
|
])
|
||||||
|
const RadarChart = /* #__PURE__ */ createTypedChart("radar", [
|
||||||
|
RadarController,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
RadialLinearScale
|
||||||
|
])
|
||||||
|
const ScatterChart = /* #__PURE__ */ createTypedChart("scatter", [
|
||||||
|
ScatterController,
|
||||||
|
PointElement,
|
||||||
|
LinearScale
|
||||||
|
])
|
||||||
|
|
||||||
|
export {
|
||||||
|
BaseChart as Chart,
|
||||||
|
BarChart,
|
||||||
|
BubbleChart,
|
||||||
|
DonutChart,
|
||||||
|
LineChart,
|
||||||
|
PieChart,
|
||||||
|
PolarAreaChart,
|
||||||
|
RadarChart,
|
||||||
|
ScatterChart
|
||||||
|
}
|
89
src/db.ts
Normal file
89
src/db.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import initSqlJS, { type Database } from "sql.js";
|
||||||
|
|
||||||
|
import wasmURL from "./assets/sql-wasm.wasm?url";
|
||||||
|
import dbURL from "./assets/database.sqlite?url";
|
||||||
|
import { createMemo, createSignal } from "solid-js";
|
||||||
|
|
||||||
|
export const SELF_ID = 2;
|
||||||
|
|
||||||
|
export const SQL = await initSqlJS({
|
||||||
|
locateFile: () => wasmURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
const file = await fetch(dbURL).then((res) => res.arrayBuffer());
|
||||||
|
|
||||||
|
const testDb = new SQL.Database(new Uint8Array(file));
|
||||||
|
|
||||||
|
export const [db, setDb] = createSignal<Database>(testDb);
|
||||||
|
|
||||||
|
const createStatement = (sql: string) => {
|
||||||
|
return createMemo(() => {
|
||||||
|
return db().prepare(sql);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const roomOverviewStmt = createStatement(`
|
||||||
|
SELECT
|
||||||
|
thread.recipient_id,
|
||||||
|
thread.active,
|
||||||
|
thread.archived,
|
||||||
|
recipient.profile_joined_name,
|
||||||
|
recipient.system_joined_name,
|
||||||
|
groups.title,
|
||||||
|
message_count,
|
||||||
|
last_message_date
|
||||||
|
FROM
|
||||||
|
thread
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
thread_id,
|
||||||
|
COUNT(*) AS message_count
|
||||||
|
FROM
|
||||||
|
message
|
||||||
|
WHERE
|
||||||
|
message.body IS NOT NULL
|
||||||
|
AND message.body != ''
|
||||||
|
GROUP BY
|
||||||
|
thread_id
|
||||||
|
) message_counts ON message_counts.thread_id = thread._id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
thread_id,
|
||||||
|
max(date_sent) AS last_message_date
|
||||||
|
FROM
|
||||||
|
message
|
||||||
|
GROUP BY
|
||||||
|
thread_id
|
||||||
|
) last_messages ON last_messages.thread_id = thread._id
|
||||||
|
JOIN recipient ON thread.recipient_id = recipient._id
|
||||||
|
LEFT JOIN groups ON recipient._id = groups.recipient_id
|
||||||
|
WHERE
|
||||||
|
message_count > 0
|
||||||
|
`);
|
||||||
|
|
||||||
|
export type RoomOverviewColumn = {
|
||||||
|
recipient_id: number;
|
||||||
|
active: 0 | 1;
|
||||||
|
archived: 0 | 1;
|
||||||
|
message_count: number;
|
||||||
|
last_message_date: number;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
profile_joined_name: string;
|
||||||
|
system_joined_name: string | null;
|
||||||
|
title: null;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
profile_joined_name: null;
|
||||||
|
system_joined_name: null;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const overallSentMessagesStmt = createStatement(`
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as message_count
|
||||||
|
FROM
|
||||||
|
message
|
||||||
|
WHERE (message.from_recipient_id = :recipient_id AND message.body IS NOT NULL AND message.body != '')
|
||||||
|
`);
|
13
src/index.css
Normal file
13
src/index.css
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
|
||||||
|
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
|
||||||
|
'Helvetica Neue', sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
23
src/index.tsx
Normal file
23
src/index.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/* @refresh reload */
|
||||||
|
import { render } from "solid-js/web";
|
||||||
|
|
||||||
|
import "./index.css";
|
||||||
|
import App from "./App";
|
||||||
|
import { Router } from "@solidjs/router";
|
||||||
|
|
||||||
|
const root = document.getElementById("root");
|
||||||
|
|
||||||
|
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||||
|
throw new Error(
|
||||||
|
"Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
() => (
|
||||||
|
<Router>
|
||||||
|
<App />
|
||||||
|
</Router>
|
||||||
|
),
|
||||||
|
root!
|
||||||
|
);
|
53
src/lib/database.ts
Normal file
53
src/lib/database.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
const TABLES = [
|
||||||
|
"attachment",
|
||||||
|
"sqlite_sequence",
|
||||||
|
"avatar_picker",
|
||||||
|
"recipient",
|
||||||
|
"thread",
|
||||||
|
"message",
|
||||||
|
"call",
|
||||||
|
"call_link",
|
||||||
|
"cds",
|
||||||
|
"chat_colors",
|
||||||
|
"chat_folder",
|
||||||
|
"chat_folder_membership",
|
||||||
|
"distribution_list",
|
||||||
|
"distribution_list_member",
|
||||||
|
"donation_receipt",
|
||||||
|
"drafts",
|
||||||
|
"emoji_search",
|
||||||
|
"groups",
|
||||||
|
"group_membership",
|
||||||
|
"group_receipts",
|
||||||
|
"identities",
|
||||||
|
"in_app_payment",
|
||||||
|
"in_app_payment_subscriber",
|
||||||
|
"kyber_prekey",
|
||||||
|
"mention",
|
||||||
|
"message_fts",
|
||||||
|
"message_fts_data",
|
||||||
|
"message_fts_idx",
|
||||||
|
"message_fts_docsize",
|
||||||
|
"message_fts_config",
|
||||||
|
"msl_payload",
|
||||||
|
"msl_message",
|
||||||
|
"msl_recipient",
|
||||||
|
"name_collision",
|
||||||
|
"name_collision_membership",
|
||||||
|
"notification_profile",
|
||||||
|
"notification_profile_allowed_members",
|
||||||
|
"notification_profile_schedule",
|
||||||
|
"one_time_prekeys",
|
||||||
|
"payments",
|
||||||
|
"pending_pni_signature_message",
|
||||||
|
"pending_retry_receipts",
|
||||||
|
"reaction",
|
||||||
|
"remapped_recipients",
|
||||||
|
"remapped_threads",
|
||||||
|
"remote_megaphone",
|
||||||
|
"sender_key_shared",
|
||||||
|
"sender_keys",
|
||||||
|
"sessions",
|
||||||
|
"storage_key",
|
||||||
|
"story_sends",
|
||||||
|
];
|
44
src/lib/sql-queries.ts
Normal file
44
src/lib/sql-queries.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// select messages from one chat / group
|
||||||
|
`SELECT
|
||||||
|
message.body,
|
||||||
|
message.from_recipient_id,
|
||||||
|
-- message."type" AS message_type,
|
||||||
|
FROM
|
||||||
|
message
|
||||||
|
JOIN thread ON message.thread_id = thread._id
|
||||||
|
JOIN recipient ON thread.recipient_id = recipient._id
|
||||||
|
WHERE
|
||||||
|
recipient._id = 1433
|
||||||
|
AND
|
||||||
|
message.body IS NOT NULL`;
|
||||||
|
|
||||||
|
// select messages from one chat / group with details of sender
|
||||||
|
`SELECT
|
||||||
|
message.body,
|
||||||
|
message.from_recipient_id,
|
||||||
|
-- message."type" AS message_type,
|
||||||
|
sender.system_joined_name,
|
||||||
|
sender.profile_joined_name
|
||||||
|
FROM
|
||||||
|
message
|
||||||
|
JOIN thread ON message.thread_id = thread._id
|
||||||
|
JOIN recipient AS r ON thread.recipient_id = r._id
|
||||||
|
JOIN recipient AS sender ON message.from_recipient_id = sender._id
|
||||||
|
WHERE
|
||||||
|
r._id = 4
|
||||||
|
AND message.body IS NOT NULL
|
||||||
|
AND message.body != ''`;
|
||||||
|
|
||||||
|
// select all chats / groups with details
|
||||||
|
`SELECT
|
||||||
|
thread.recipient_id,
|
||||||
|
thread.active,
|
||||||
|
recipient.profile_joined_name,
|
||||||
|
recipient.system_joined_name,
|
||||||
|
groups.title
|
||||||
|
FROM
|
||||||
|
thread
|
||||||
|
JOIN recipient ON thread.recipient_id = recipient._id
|
||||||
|
LEFT JOIN groups ON recipient._id = groups.recipient_id
|
||||||
|
WHERE (SELECT 1 FROM message WHERE message.thread_id = thread._id AND message.body IS NOT NULL)
|
||||||
|
`;
|
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { type ClassValue, clsx } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
32
src/pages/home.tsx
Normal file
32
src/pages/home.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { redirect, useNavigate, type RouteSectionProps } from "@solidjs/router";
|
||||||
|
import { type Component, type JSX } from "solid-js";
|
||||||
|
import { setDb, SQL } from "~/db";
|
||||||
|
|
||||||
|
export const Home: Component<RouteSectionProps> = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onFileChange: JSX.ChangeEventHandler<HTMLInputElement, Event> = (event) => {
|
||||||
|
const file = event.currentTarget.files![0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.addEventListener("load", () => {
|
||||||
|
const Uints = new Uint8Array(reader.result as ArrayBuffer);
|
||||||
|
setDb(new SQL.Database(Uints));
|
||||||
|
navigate("/overview");
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".sqlite"
|
||||||
|
onChange={onFileChange}
|
||||||
|
></input>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
5
src/pages/index.tsx
Normal file
5
src/pages/index.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { lazy } from "solid-js";
|
||||||
|
|
||||||
|
export { Home } from "./home";
|
||||||
|
|
||||||
|
export const Overview = lazy(() => import("./overview"));
|
47
src/pages/overview.tsx
Normal file
47
src/pages/overview.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import type { RouteSectionProps } from "@solidjs/router";
|
||||||
|
import type { Component } from "solid-js";
|
||||||
|
import { overallSentMessagesStmt, roomOverviewStmt, SELF_ID, type RoomOverviewColumn } from "~/db";
|
||||||
|
|
||||||
|
type RoomOverview = {
|
||||||
|
recipientId: number;
|
||||||
|
active: boolean;
|
||||||
|
archived: boolean;
|
||||||
|
messageCount: number;
|
||||||
|
lastMessageDate: number;
|
||||||
|
title: string;
|
||||||
|
isGroup: boolean;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export const Overview: Component<RouteSectionProps> = () => {
|
||||||
|
const allSelfSentMessagesCount = overallSentMessagesStmt().getAsObject({
|
||||||
|
":recipient_id": SELF_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
const roomOverviewRaw: RoomOverviewColumn[] = [];
|
||||||
|
|
||||||
|
while (roomOverviewStmt().step()) {
|
||||||
|
roomOverviewRaw.push(roomOverviewStmt().getAsObject() as RoomOverviewColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
roomOverviewStmt().free();
|
||||||
|
|
||||||
|
const roomOverview: RoomOverview = roomOverviewRaw.map((column) => {
|
||||||
|
const isGroup = Boolean(column.title);
|
||||||
|
|
||||||
|
return {
|
||||||
|
recipientId: column.recipient_id,
|
||||||
|
active: Boolean(column.active),
|
||||||
|
archived: Boolean(column.archived),
|
||||||
|
messageCount: column.message_count,
|
||||||
|
lastMessageDate: column.last_message_date,
|
||||||
|
title: isGroup ? column.title! : (column.system_joined_name ?? column.profile_joined_name)!,
|
||||||
|
isGroup,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(roomOverview);
|
||||||
|
|
||||||
|
return <p>All messages: {allSelfSentMessagesCount.message_count as number}</p>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Overview;
|
104
tailwind.config.js
Normal file
104
tailwind.config.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
darkMode: ["variant", [".dark &", '[data-kb-theme="dark"] &']],
|
||||||
|
content: ["./src/**/*.{ts,tsx}"],
|
||||||
|
prefix: "",
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: "2rem",
|
||||||
|
screens: {
|
||||||
|
"2xl": "1400px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: "hsl(var(--border))",
|
||||||
|
input: "hsl(var(--input))",
|
||||||
|
ring: "hsl(var(--ring))",
|
||||||
|
background: "hsl(var(--background))",
|
||||||
|
foreground: "hsl(var(--foreground))",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary))",
|
||||||
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
DEFAULT: "hsl(var(--info))",
|
||||||
|
foreground: "hsl(var(--info-foreground))",
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
DEFAULT: "hsl(var(--success))",
|
||||||
|
foreground: "hsl(var(--success-foreground))",
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
DEFAULT: "hsl(var(--warning))",
|
||||||
|
foreground: "hsl(var(--warning-foreground))",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
DEFAULT: "hsl(var(--error))",
|
||||||
|
foreground: "hsl(var(--error-foreground))",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted))",
|
||||||
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent))",
|
||||||
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: "hsl(var(--popover))",
|
||||||
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card))",
|
||||||
|
foreground: "hsl(var(--card-foreground))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
xl: "calc(var(--radius) + 4px)",
|
||||||
|
lg: "var(--radius)",
|
||||||
|
md: "calc(var(--radius) - 2px)",
|
||||||
|
sm: "calc(var(--radius) - 4px)",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"accordion-down": {
|
||||||
|
from: { height: 0 },
|
||||||
|
to: { height: "var(--kb-accordion-content-height)" },
|
||||||
|
},
|
||||||
|
"accordion-up": {
|
||||||
|
from: { height: "var(--kb-accordion-content-height)" },
|
||||||
|
to: { height: 0 },
|
||||||
|
},
|
||||||
|
"content-show": {
|
||||||
|
from: { opacity: 0, transform: "scale(0.96)" },
|
||||||
|
to: { opacity: 1, transform: "scale(1)" },
|
||||||
|
},
|
||||||
|
"content-hide": {
|
||||||
|
from: { opacity: 1, transform: "scale(1)" },
|
||||||
|
to: { opacity: 0, transform: "scale(0.96)" },
|
||||||
|
},
|
||||||
|
"caret-blink": {
|
||||||
|
"0%,70%,100%": { opacity: "1" },
|
||||||
|
"20%,50%": { opacity: "0" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
|
"content-show": "content-show 0.2s ease-out",
|
||||||
|
"content-hide": "content-hide 0.2s ease-out",
|
||||||
|
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require("tailwindcss-animate"), require("@kobalte/tailwindcss")],
|
||||||
|
};
|
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js",
|
||||||
|
"types": [
|
||||||
|
"vite/client"
|
||||||
|
],
|
||||||
|
"noEmit": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"~/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// "files": [
|
||||||
|
// "./eslint.config.js",
|
||||||
|
// "./prettier.config.js",
|
||||||
|
// "./tailwind.config.js",
|
||||||
|
// "./postcss.config.js",
|
||||||
|
// ]
|
||||||
|
}
|
13
ui.config.json
Normal file
13
ui.config.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://solid-ui.com/schema.json",
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"css": "src/app.css",
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "~/components/ui",
|
||||||
|
"utils": "~/lib/utils"
|
||||||
|
}
|
||||||
|
}
|
19
vite.config.ts
Normal file
19
vite.config.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import solidPlugin from "vite-plugin-solid";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [solidPlugin()],
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
target: "esnext",
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"~": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue