chore: organise project structure and slowly switch out solid for react
This commit is contained in:
parent
37e8e1eaf8
commit
303ac31756
51 changed files with 3980 additions and 4142 deletions
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
|
|
@ -3,7 +3,8 @@
|
|||
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||
},
|
||||
"files.readonlyInclude": {
|
||||
"**/node_modules/**": true
|
||||
"**/node_modules/**": true,
|
||||
"**/routeTree.gen.ts": true
|
||||
},
|
||||
"files.watcherExclude": {
|
||||
"**/_build": true,
|
||||
|
|
@ -18,7 +19,8 @@
|
|||
"**/node_modules": true,
|
||||
"**/.rustup": true,
|
||||
"**/.cargo": true,
|
||||
"**/.crates": true
|
||||
"**/.crates": true,
|
||||
"**/routeTree.gen.ts": true
|
||||
},
|
||||
"rust-analyzer.procMacro.enable": false,
|
||||
"rust-analyzer.procMacro.attributes.enable": false,
|
||||
|
|
|
|||
5
apps/oneclient/frontend/.gitignore
vendored
Normal file
5
apps/oneclient/frontend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
3
apps/oneclient/frontend/.prettierignore
Normal file
3
apps/oneclient/frontend/.prettierignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
20
apps/oneclient/frontend/index.html
Normal file
20
apps/oneclient/frontend/index.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-tsrouter-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<title>OneClient</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
36
apps/oneclient/frontend/package.json
Normal file
36
apps/oneclient/frontend/package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@oneclient/frontend",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --port 3000",
|
||||
"start": "vite --port 3000",
|
||||
"build": "vite build && tsc",
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint",
|
||||
"format": "prettier",
|
||||
"check": "prettier --write . && eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
"@tanstack/match-sorter-utils": "^8.19.4",
|
||||
"@tanstack/react-query": "^5.66.5",
|
||||
"@tanstack/react-query-devtools": "^5.66.5",
|
||||
"@tanstack/react-router": "^1.114.3",
|
||||
"@tanstack/react-router-devtools": "^1.114.3",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@tanstack/router-plugin": "^1.114.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwindcss": "^4.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/eslint-config": "^0.1.0",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.1.0"
|
||||
}
|
||||
}
|
||||
10
apps/oneclient/frontend/prettier.config.js
Normal file
10
apps/oneclient/frontend/prettier.config.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// @ts-check
|
||||
|
||||
/** @type {import('prettier').Config} */
|
||||
const config = {
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
};
|
||||
|
||||
export default config;
|
||||
21
apps/oneclient/frontend/src/components/Header.tsx
Normal file
21
apps/oneclient/frontend/src/components/Header.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { Link } from '@tanstack/react-router';
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<header className="flex justify-between gap-2 bg-white p-2 text-black">
|
||||
<nav className="flex flex-row">
|
||||
<div className="px-2 font-bold">
|
||||
<Link to="/">Home</Link>
|
||||
</div>
|
||||
|
||||
<div className="px-2 font-bold">
|
||||
<Link to="/demo/table">TanStack Table</Link>
|
||||
</div>
|
||||
|
||||
<div className="px-2 font-bold">
|
||||
<Link to="/demo/tanstack-query">TanStack Query</Link>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
|
||||
export default function LayoutAddition() {
|
||||
return <ReactQueryDevtools buttonPosition="bottom-right" />
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
export function getContext() {
|
||||
return {
|
||||
queryClient,
|
||||
}
|
||||
}
|
||||
|
||||
export function Provider({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
44
apps/oneclient/frontend/src/logo.svg
Normal file
44
apps/oneclient/frontend/src/logo.svg
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.9 595.3">
|
||||
<!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 146) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #9ae7fc;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #61dafb;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<path class="st1" d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4,14.4-63.6,8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6,0,8.3.9,11.4,2.6,13.6,7.8,19.5,37.5,14.9,75.7-1.1,9.4-2.9,19.3-5.1,29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50,32.6-30.3,63.2-46.9,84-46.9v-22.3c-27.5,0-63.5,19.6-99.9,53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6-14,14.7-28,31.4-41.3,49.9-22.6,2.4-44,6.1-63.6,11-2.3-10-4-19.7-5.2-29-4.7-38.2,1.1-67.9,14.6-75.8,3-1.8,6.9-2.6,11.5-2.6v-22.3c-8.4,0-16,1.8-22.6,5.6-28.1,16.2-34.4,66.7-19.9,130.1-62.2,19.2-102.7,49.9-102.7,82.3s40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4,6.5,3.8,14.1,5.6,22.5,5.6,27.5,0,63.5-19.6,99.9-53.6,36.4,33.8,72.4,53.2,99.9,53.2,8.4,0,16-1.8,22.6-5.6,28.1-16.2,34.4-66.7,19.9-130.1,62-19.1,102.5-49.9,102.5-82.3zm-130.2-66.7c-3.7,12.9-8.3,26.2-13.5,39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4,14.2,2.1,27.9,4.7,41,7.9zm-45.8,106.5c-7.8,13.5-15.8,26.3-24.1,38.2-14.9,1.3-30,2-45.2,2s-30.2-.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8,6.2-13.4,13.2-26.8,20.7-39.9,7.8-13.5,15.8-26.3,24.1-38.2,14.9-1.3,30-2,45.2-2s30.2.7,45,1.9c8.3,11.9,16.4,24.6,24.2,38,7.6,13.1,14.5,26.4,20.8,39.8-6.3,13.4-13.2,26.8-20.7,39.9zm32.3-13c5.4,13.4,10,26.8,13.8,39.8-13.1,3.2-26.9,5.9-41.2,8,4.9-7.7,9.8-15.6,14.4-23.7,4.6-8,8.9-16.1,13-24.1zm-101.4,106.7c-9.3-9.6-18.6-20.3-27.8-32,9,.4,18.2.7,27.5.7s18.7-.2,27.8-.7c-9,11.7-18.3,22.4-27.5,32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9,3.7-12.9,8.3-26.2,13.5-39.5,4.1,8,8.4,16,13.1,24s9.5,15.8,14.4,23.4zm73.9-208.1c9.3,9.6,18.6,20.3,27.8,32-9-.4-18.2-.7-27.5-.7s-18.7.2-27.8.7c9-11.7,18.3-22.4,27.5-32zm-74,58.9c-4.9,7.7-9.8,15.6-14.4,23.7-4.6,8-8.9,16-13,24-5.4-13.4-10-26.8-13.8-39.8,13.1-3.1,26.9-5.8,41.2-7.9zm-90.5,125.2c-35.4-15.1-58.3-34.9-58.3-50.6s22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1,5.7,19.6,13.2,40,22.5,60.9-9.2,20.8-16.6,41.1-22.2,60.6-9.9-3.1-19.3-6.5-28-10.2zm53.8,142.9c-13.6-7.8-19.5-37.5-14.9-75.7,1.1-9.4,2.9-19.3,5.1-29.4,19.6,4.8,41,8.5,63.5,10.9,13.5,18.5,27.5,35.3,41.6,50-32.6,30.3-63.2,46.9-84,46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7,38.2-1.1,67.9-14.6,75.8-3,1.8-6.9,2.6-11.5,2.6-20.7,0-51.4-16.5-84-46.6,14-14.7,28-31.4,41.3-49.9,22.6-2.4,44-6.1,63.6-11,2.3,10.1,4.1,19.8,5.2,29.1zm38.5-66.7c-8.6,3.7-18,7-27.7,10.1-5.7-19.6-13.2-40-22.5-60.9,9.2-20.8,16.6-41.1,22.2-60.6,9.9,3.1,19.3,6.5,28.1,10.2,35.4,15.1,58.3,34.9,58.3,50.6,0,15.7-23,35.6-58.4,50.6zm-264.9-268.7z"/>
|
||||
<circle class="st1" cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path class="st1" d="M520.5,78.1"/>
|
||||
</g>
|
||||
<circle class="st0" cx="420.8" cy="296.6" r="43"/>
|
||||
<path class="st1" d="M466.1,296.6c0,25-20.2,45.2-45.2,45.2s-45.2-20.2-45.2-45.2,20.2-45.2,45.2-45.2,45.2,20.2,45.2,45.2ZM386,295.6v-6.3c0-1.1,1.2-5.1,1.8-6.2,1-1.9,2.9-3.5,4.6-4.7l-3.4-3.4c4-3.6,9.4-3.7,13.7-.7,1.9-4.7,6.6-7.1,11.6-6.7l-.8,4.2c5.9.2,13.1,4.1,13.1,10.8s0,.5-.7.7c-1.7.3-3.4-.4-5-.6s-1.2-.4-1.2.3,2.5,4.1,3,5.5,1,3.5.8,5.3c-5.6-.8-10.5-3.2-14.8-6.7.3,2.6,4.1,21.7,5.3,21.9s.8-.6,1-1.1,1.3-6.3,1.3-6.7c0-1-1.7-1.8-2.2-2.8-1.2-2.7,1.3-4.7,3.7-3.3s5.2,6.2,7.5,7.3,13,1.4,14.8,3.3-2.9,4.6-1.5,7.6c6.7-2.6,13.5-3.3,20.6-2.5,3.1-9.7,3.1-20.3-.9-29.8-7.3,0-14.7-3.6-17.2-10.8-2.5-7.2-.7-8.6-1.3-9.3-.8-1-6.3.6-7.4-1.5s.3-1.1-.2-1.4-1.9-.6-2.6-.8c-26-6.4-51.3,15.7-49.7,42.1,0,1.6,1.6,10.3,2.4,11.1s4.8,0,6.3,0,3.7.3,5,.5c2.9.4,7.2,2.4,9.4,2.5s2.4-.8,2.7-2.4c.4-2.6.5-7.4.5-10.1s-1-7.8-1.3-11.6c-.9-.2-.7,0-.9.5-.7,1.3-1.1,3.2-1.9,4.8s-5.2,8.7-5.7,9-.7-.5-.8-.8c-1.6-3.5-2-7.9-1.9-11.8-.9-1-5.4,4.9-6.7,5.3l-.8-.4v-.3h-.2ZM455.6,276.4c1.1-1.2-6-8.9-7.2-10-3-2.7-5.4-4.5-3.5,1.4s5.7,7.8,10.6,8.5h.1ZM410.9,270.1c-.4-.5-6.1,2.9-5.5,4.6,1.9-1.3,5.9-1.7,5.5-4.6ZM400.4,276.4c-.3-2.4-6.3-2.7-7.2-1s1.6,1.4,1.9,1.4c1.8.3,3.5-.6,5.2-.4h.1ZM411.3,276.8c3.8,1.3,6.6,3.6,10.9,3.7s0-3-1.2-3.9c-2.2-1.7-5.1-2.4-7.8-2.4s-1.6-.3-1.4.4c2.8.6,7.3.7,8.4,3.8-2.3-.3-3.9-1.6-6.2-2s-2.5-.5-2.6.3h0ZM420.6,290.3c-.8-5.1-5.7-10.8-10.9-11.6s-1.3-.4-.8.5,4.7,3.2,5.7,4,4.5,4.2,2.1,3.8-8.4-7.8-9.4-6.7c.2.9,1.1,1.9,1.7,2.7,3,3.8,6.9,6.8,11.8,7.4h-.2ZM395.3,279.8c-5,1.1-6.9,6.3-6.7,11,.7.8,5-3.8,5.4-4.5s2.7-4.6,1.1-4-2.9,4.4-4.2,4.6.2-2.1.4-2.5c1.1-1.6,2.9-3.1,4-4.6h0ZM400.4,281.5c-.4-.5-2,1.3-2.3,1.7-2.9,3.9-2.6,10.2-1.5,14.8.8.2.8-.3,1.2-.7,3-3.8,5.5-10.5,4.5-15.4-2.1,3.1-3.1,7.3-3.6,11h-1.3c0-4,1.9-7.7,3-11.4h0ZM426.9,305.9c0-1.7-1.7-1.4-2.5-1.9s-1.3-1.9-3-1.4c1.3,2.1,3,3.2,5.5,3.4h0ZM417.2,308.5c7.6.7,5.5-1.9,1.4-5.5-1.3-.3-1.5,4.5-1.4,5.5ZM437,309.7c-3.5-.3-7.8-2-11.2-2.1s-1.3,0-1.9.7c4,1.3,8.4,1.7,12.1,4l1-2.5h0ZM420.5,312.8c-7.3,0-15.1,3.7-20.4,8.8s-4.8,5.3-4.8,6.2c0,1.8,8.6,6.2,10.5,6.8,12.1,4.8,27.5,3.5,38.2-4.2s3.1-2.7,0-6.2c-5.7-6.6-14.7-11.4-23.4-11.3h-.1ZM398.7,316.9c-1.4-1.4-5-1.9-7-2.1s-5.3-.3-6.9.6l13.9,1.4h0ZM456.9,314.8h-7.4c-.9,0-4.9,1.1-6,1.6s-.8.6,0,.5c2.4,0,5.1-1,7.6-1.3s3.5.2,5.1,0,1.3-.3.6-.8h0Z"/>
|
||||
<path class="st0" d="M386,295.6l.8.4c1.3-.3,5.8-6.2,6.7-5.3,0,3.9.3,8.3,1.9,11.8s0,1.2.8.8,5.1-7.8,5.7-9,1.3-3.5,1.9-4.8,0-.7.9-.5c.3,3.8,1.2,7.8,1.3,11.6s0,7.5-.5,10.1-1.1,2.4-2.7,2.4-6.5-2.1-9.4-2.5-3.7-.5-5-.5-5.4,1.1-6.3,0-2.2-9.5-2.4-11.1c-1.5-26.4,23.7-48.5,49.7-42.1s2.2.4,2.6.8,0,1,.2,1.4c1.1,2,6.5.5,7.4,1.5s.4,6.9,1.3,9.3c2.5,7.2,10,10.9,17.2,10.8,4,9.4,4,20.1.9,29.8-7.2-.7-13.9,0-20.6,2.5-1.3-3.1,4.1-5.1,1.5-7.6s-11.8-1.9-14.8-3.3-5.4-6.1-7.5-7.3-4.9.6-3.7,3.3,2.1,1.8,2.2,2.8-1,6.2-1.3,6.7-.3,1.3-1,1.1c-1.1-.3-5-19.3-5.3-21.9,4.3,3.5,9.2,5.9,14.8,6.7.2-1.9-.3-3.5-.8-5.3s-3-5.1-3-5.5c0-.8.9-.3,1.2-.3,1.6,0,3.3.8,5,.6s.7.3.7-.7c0-6.6-7.2-10.6-13.1-10.8l.8-4.2c-5.1-.3-9.6,2-11.6,6.7-4.3-3-9.8-3-13.7.7l3.4,3.4c-1.8,1.3-3.5,2.8-4.6,4.7s-1.8,5.1-1.8,6.2v6.6h.2ZM431.6,265c7.8,2.1,8.7-3.5.2-1.3l-.2,1.3ZM432.4,270.9c.3.6,6.4-.4,5.8-2.3s-4.6.6-5.7.6l-.2,1.7h.1ZM434.5,276c.8,1.2,5.7-1.8,5.5-2.7-.4-1.9-6.6,1.2-5.5,2.7ZM442.9,276.4c-.9-.9-5,2.8-4.6,4,.6,2.4,5.7-3,4.6-4ZM445.1,279.9c-.3.2-3.1,4.6-1.5,5s3.5-3.4,3.5-4-1.3-1.3-2-.9h0ZM448.9,287.4c2.1.8,3.8-5.1,2.3-5.5-1.9-.6-2.6,5.1-2.3,5.5ZM457.3,288.6c.5-1.7,1.1-4.7-1-5.5-1,.3-.6,3.9-.6,4.8l.3.5,1.3.2h0Z"/>
|
||||
<path class="st0" d="M455.6,276.4c-5-.8-9.1-3.6-10.6-8.5s.5-4,3.5-1.4,8.3,8.7,7.2,10h-.1Z"/>
|
||||
<path class="st0" d="M420.6,290.3c-4.9-.6-8.9-3.6-11.8-7.4s-1.5-1.8-1.7-2.7c1-1,8.5,6.6,9.4,6.7,2.4.4-1.8-3.5-2.1-3.8-1-.8-5.4-3.5-5.7-4-.4-.8.5-.5.8-.5,5.2.8,10.1,6.6,10.9,11.6h.2Z"/>
|
||||
<path class="st0" d="M400.4,281.5c-1.1,3.7-3,7.3-3,11.4h1.3c.5-3.7,1.5-7.8,3.6-11,1,4.8-1.5,11.6-4.5,15.4s-.4.8-1.2.7c-1.1-4.5-1.3-10.8,1.5-14.8s1.9-2.2,2.3-1.7h0Z"/>
|
||||
<path class="st0" d="M411.3,276.8c0-.8,2.1-.4,2.6-.3,2.4.4,4,1.7,6.2,2-1.2-3.1-5.7-3.2-8.4-3.8,0-.8.9-.4,1.4-.4,2.8,0,5.6.7,7.8,2.4,2.2,1.7,4,4,1.2,3.9-4.3,0-7.1-2.4-10.9-3.7h0Z"/>
|
||||
<path class="st0" d="M395.3,279.8c-1.1,1.6-3,3-4,4.6s-1.9,2.8-.4,2.5,2.8-4,4.2-4.6-.9,3.6-1.1,4c-.4.7-4.7,5.2-5.4,4.5-.2-4.6,1.8-9.9,6.7-11h0Z"/>
|
||||
<path class="st0" d="M437,309.7l-1,2.5c-3.6-2.3-8-2.8-12.1-4,.5-.7,1.1-.7,1.9-.7,3.4,0,7.8,1.8,11.2,2.1h0Z"/>
|
||||
<path class="st0" d="M417.2,308.5c0-1,0-5.8,1.4-5.5,4,3.5,6.1,6.2-1.4,5.5Z"/>
|
||||
<path class="st0" d="M400.4,276.4c-1.8-.3-3.5.7-5.2.4s-2.3-.8-1.9-1.4c.8-1.6,6.9-1.4,7.2,1h-.1Z"/>
|
||||
<path class="st0" d="M410.9,270.1c.4,3-3.6,3.3-5.5,4.6-.6-1.8,5-5.1,5.5-4.6Z"/>
|
||||
<path class="st0" d="M426.9,305.9c-2.5-.2-4.1-1.3-5.5-3.4,1.7-.4,2,.8,3,1.4s2.6.3,2.5,1.9h0Z"/>
|
||||
<path class="st1" d="M432.4,270.9l.2-1.7c1.1,0,5.1-2.2,5.7-.6s-5.5,2.9-5.8,2.3h-.1Z"/>
|
||||
<path class="st1" d="M431.6,265l.2-1.3c8.4-2.1,7.7,3.4-.2,1.3Z"/>
|
||||
<path class="st1" d="M434.5,276c-1.1-1.5,5.1-4.6,5.5-2.7s-4.6,4-5.5,2.7Z"/>
|
||||
<path class="st1" d="M442.9,276.4c1.1,1.1-4,6.4-4.6,4s3.7-4.9,4.6-4Z"/>
|
||||
<path class="st1" d="M445.1,279.9c.7-.4,2.1,0,2,.9s-2.4,4.4-3.5,4,1.3-4.8,1.5-5h0Z"/>
|
||||
<path class="st1" d="M448.9,287.4c-.3-.3.4-6.1,2.3-5.5,1.4.4-.2,6.2-2.3,5.5Z"/>
|
||||
<path class="st1" d="M457.3,288.6l-1.3-.2-.3-.5c0-.9-.4-4.6.6-4.8,2.1.8,1.5,3.8,1,5.5h0Z"/>
|
||||
<path class="st0" d="M420.5,312.8c8.9,0,17.9,4.7,23.4,11.3,5.6,6.6,3.8,3.5,0,6.2-10.7,7.7-26.1,9-38.2,4.2-1.9-.8-10.5-5.1-10.5-6.8s4-5.3,4.8-6.2c5.3-5,13.1-8.6,20.4-8.8h.1Z"/>
|
||||
<path class="st0" d="M398.7,316.9l-13.9-1.4c1.7-1,5-.8,6.9-.6s5.6.7,7,2.1h0Z"/>
|
||||
<path class="st0" d="M456.9,314.8c.7.5,0,.8-.6.8-1.6.2-3.5-.2-5.1,0-2.4.3-5.2,1.2-7.6,1.3s-1.1,0,0-.5,5.1-1.6,6-1.6h7.4,0Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.4 KiB |
47
apps/oneclient/frontend/src/main.tsx
Normal file
47
apps/oneclient/frontend/src/main.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { StrictMode } from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { RouterProvider, createRouter } from '@tanstack/react-router'
|
||||
|
||||
import * as TanstackQuery from './integrations/tanstack-query/root-provider'
|
||||
|
||||
// Import the generated route tree
|
||||
import { routeTree } from './routeTree.gen'
|
||||
|
||||
import './styles.css'
|
||||
|
||||
// Create a new router instance
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
context: {
|
||||
...TanstackQuery.getContext(),
|
||||
},
|
||||
defaultPreload: 'intent',
|
||||
scrollRestoration: true,
|
||||
defaultStructuralSharing: true,
|
||||
defaultPreloadStaleTime: 0,
|
||||
})
|
||||
|
||||
// Register the router instance for type safety
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: typeof router
|
||||
}
|
||||
}
|
||||
|
||||
// Render the app
|
||||
const rootElement = document.getElementById('app')
|
||||
if (rootElement && !rootElement.innerHTML) {
|
||||
const root = ReactDOM.createRoot(rootElement)
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<TanstackQuery.Provider>
|
||||
<RouterProvider router={router} />
|
||||
</TanstackQuery.Provider>
|
||||
</StrictMode>,
|
||||
)
|
||||
}
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals()
|
||||
25
apps/oneclient/frontend/src/routes/__root.tsx
Normal file
25
apps/oneclient/frontend/src/routes/__root.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router'
|
||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
||||
|
||||
import Header from '../components/Header'
|
||||
|
||||
import TanstackQueryLayout from '../integrations/tanstack-query/layout'
|
||||
|
||||
import type { QueryClient } from '@tanstack/react-query'
|
||||
|
||||
interface MyRouterContext {
|
||||
queryClient: QueryClient
|
||||
}
|
||||
|
||||
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
||||
component: () => (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<Outlet />
|
||||
<TanStackRouterDevtools />
|
||||
|
||||
<TanstackQueryLayout />
|
||||
</>
|
||||
),
|
||||
})
|
||||
39
apps/oneclient/frontend/src/routes/index.tsx
Normal file
39
apps/oneclient/frontend/src/routes/index.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import logo from '../logo.svg'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: App,
|
||||
})
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<header className="min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)]">
|
||||
<img
|
||||
src={logo}
|
||||
className="h-[40vmin] pointer-events-none animate-[spin_20s_linear_infinite]"
|
||||
alt="logo"
|
||||
/>
|
||||
<p>
|
||||
Edit <code>src/routes/index.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="text-[#61dafb] hover:underline"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
<a
|
||||
className="text-[#61dafb] hover:underline"
|
||||
href="https://tanstack.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn TanStack
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
15
apps/oneclient/frontend/src/styles.css
Normal file
15
apps/oneclient/frontend/src/styles.css
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
body {
|
||||
@apply m-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;
|
||||
}
|
||||
29
apps/oneclient/frontend/tsconfig.json
Normal file
29
apps/oneclient/frontend/tsconfig.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"types": ["vite/client"],
|
||||
"allowImportingTsExtensions": true,
|
||||
"strict": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noEmit": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
|
||||
/* Linting */
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", "prettier.config.js", "vite.config.js"]
|
||||
}
|
||||
20
apps/oneclient/frontend/vite.config.js
Normal file
20
apps/oneclient/frontend/vite.config.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { defineConfig } from "vite";
|
||||
import viteReact from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [TanStackRouterVite({ autoCodeSplitting: true }), viteReact(), tailwindcss()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
},
|
||||
}
|
||||
});
|
||||
5
apps/onelauncher/frontend/dist/.gitignore
vendored
5
apps/onelauncher/frontend/dist/.gitignore
vendored
|
|
@ -1,5 +0,0 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
# This is done so that Tauri never complains that '../dist does not exist'
|
||||
|
|
@ -44,10 +44,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@sentry/vite-plugin": "^2.22.4",
|
||||
"@solidjs/testing-library": "^0.8.10",
|
||||
"@tauri-apps/cli": "^2.0.4",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"jsdom": "^25.0.1",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.79.4",
|
||||
"unocss": "^0.63.6",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { getProgramInfo } from '@onelauncher/client';
|
||||
import { type ParentProps, Show, createMemo } from 'solid-js';
|
||||
import { Transition } from 'solid-transition-group';
|
||||
import type {TransitionProps} from 'solid-transition-group';
|
||||
import useSettings from '~ui/hooks/useSettings';
|
||||
import { createMemo, type ParentProps, Show } from 'solid-js';
|
||||
import { Transition, type TransitionProps } from 'solid-transition-group';
|
||||
|
||||
type AnimationTypes = 'default' | 'fade';
|
||||
export interface AnimatedProps {
|
||||
animation?: AnimationTypes;
|
||||
};
|
||||
|
||||
const animations: { [key in AnimationTypes]: { before: Keyframe; after: Keyframe } } = {
|
||||
const animations: Record<AnimationTypes, { before: Keyframe; after: Keyframe }> = {
|
||||
default: {
|
||||
before: {
|
||||
transform: 'translateX(-85px)',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
|
|
@ -12,11 +12,8 @@
|
|||
"paths": {
|
||||
"~*": ["./src/*"]
|
||||
},
|
||||
|
||||
"resolveJsonModule": true,
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"@testing-library/jest-dom",
|
||||
"vite-plugin-solid-svg/types"
|
||||
],
|
||||
"allowImportingTsExtensions": true,
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
import shared from '../../shared.config';
|
||||
|
||||
export default shared`frontend`.vitest;
|
||||
29
eslint.config.js
Normal file
29
eslint.config.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { defineConfig } from '@flowr/eslint';
|
||||
// @ts-ignore -- No types for this package
|
||||
import { tanstackConfig } from '@tanstack/eslint-config';
|
||||
|
||||
export default defineConfig(
|
||||
{
|
||||
typescript: true,
|
||||
react: true,
|
||||
query: true,
|
||||
ignores: ['*.rs', '**/migrations/**'],
|
||||
rules: {
|
||||
'ts/no-use-before-define': 'off',
|
||||
},
|
||||
toml: {
|
||||
overrides: {
|
||||
'toml/array-element-newline': ['error', 'always'],
|
||||
'toml/no-mixed-type-in-array': ['off'],
|
||||
},
|
||||
},
|
||||
},
|
||||
// Hack to fix an eslint error with plugin names
|
||||
// @ts-ignore -- No types for this package
|
||||
tanstackConfig.map((config) => {
|
||||
if (config.rules && config.rules['@stylistic/js/spaced-comment'])
|
||||
delete config.rules['@stylistic/js/spaced-comment'];
|
||||
|
||||
return config;
|
||||
}),
|
||||
);
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import { defineConfig } from '@flowr/eslint-config';
|
||||
|
||||
export default defineConfig({
|
||||
typescript: true,
|
||||
solid: true,
|
||||
unocss: true,
|
||||
ignores: ['*.rs', '**/migrations/**'],
|
||||
rules: {
|
||||
'ts/no-use-before-define': 'off',
|
||||
},
|
||||
toml: {
|
||||
overrides: {
|
||||
'toml/array-element-newline': ['error', 'always'],
|
||||
'toml/no-mixed-type-in-array': ['off'],
|
||||
},
|
||||
},
|
||||
});
|
||||
51
package.json
51
package.json
|
|
@ -15,37 +15,30 @@
|
|||
"bugs": "https://github.com/polyfrost/onelauncher/issues",
|
||||
"sideEffects": false,
|
||||
"engines": {
|
||||
"pnpm": ">=9.0.0",
|
||||
"pnpm": ">=10.0.0",
|
||||
"npm": "pnpm",
|
||||
"yarn": "pnpm",
|
||||
"node": ">=18.18 <19 || >=20"
|
||||
"node": ">=22"
|
||||
},
|
||||
"scripts": {
|
||||
"prep": "pnpm scripts prep",
|
||||
"typecheck": "pnpm -r typecheck",
|
||||
"bootstrap": "cargo clean && ./packages/scripts/setup.sh && pnpm install && pnpm prep && pnpm codegen",
|
||||
"autoformat": "./packages/scripts/format.sh",
|
||||
"test": "pnpm test:vi && pnpm --filter=./* run test",
|
||||
"format": "pnpm format:es && pnpm format:rs",
|
||||
"lint": "pnpm lint:es && pnpm lint:rs",
|
||||
|
||||
"migrate": "sea-orm-cli migrate -d ./packages/migration",
|
||||
"entity:gen": "sea-orm-cli generate entity -o packages/entity/src/entities --with-serde both --model-extra-attributes 'onelauncher_macro::specta' -t",
|
||||
|
||||
"frontend": "pnpm --filter @onelauncher/frontend -- ",
|
||||
"onelauncher:frontend": "pnpm --filter @onelauncher/frontend -- ",
|
||||
"onelauncher:desktop": "pnpm --filter @onelauncher/desktop -- ",
|
||||
"oneclient:frontend": "pnpm --filter @oneclient/frontend -- ",
|
||||
"oneclient:desktop": "pnpm --filter @oneclient/desktop -- ",
|
||||
"client": "pnpm --filter @onelauncher/client -- ",
|
||||
"core": "pnpm --filter @onelauncher/core -- ",
|
||||
"scripts": "pnpm --filter @onelauncher/scripts -- ",
|
||||
"tauri": "pnpm desktop tauri-ext",
|
||||
|
||||
"postupdate": "cargo update",
|
||||
"test:vi": "pnpm vitest run",
|
||||
"test:update": "pnpm vitest -u",
|
||||
"test:ui": "pnpm vitest --ui",
|
||||
"lint:es": "eslint . --cache --flag unstable_ts_config",
|
||||
"format": "pnpm format:es && pnpm format:rs",
|
||||
"format:es": "pnpm lint:es --fix",
|
||||
"lint:rs": "cargo +nightly fmt --check && cargo clippy -- -D warnings",
|
||||
"format:rs": "cargo +nightly fmt && cargo clippy --fix"
|
||||
"format:rs": "cargo +nightly fmt && cargo clippy --fix",
|
||||
"lint": "pnpm lint:es && pnpm lint:rs",
|
||||
"lint:es": "eslint . --cache --flag unstable_ts_config",
|
||||
"lint:rs": "cargo +nightly fmt --check && cargo clippy -- -D warnings"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
|
|
@ -54,18 +47,20 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@flowr/eslint-config": "^4.4.1",
|
||||
"@eslint-react/eslint-plugin": "^1.48.5",
|
||||
"@flowr/eslint": "^6.0.0",
|
||||
"@tanstack/eslint-config": "^0.1.0",
|
||||
"@tanstack/eslint-plugin-query": "5.74.7",
|
||||
"@tanstack/eslint-plugin-router": "1.115.0",
|
||||
"@types/node": "^22.7.9",
|
||||
"@unocss/eslint-plugin": "^0.63.6",
|
||||
"@vitest/ui": "^2.1.3",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-plugin-solid": "^0.14.3",
|
||||
"@unocss/eslint-plugin": "^66.0.0",
|
||||
"eslint": "^9.20.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-solid": "^0.14.5",
|
||||
"jiti": "^2.3.3",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.10",
|
||||
"vitest": "^2.1.3"
|
||||
"tsx": "^4.19.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
# wip library expecting rspc but can work with tauri-specta too
|
||||
|
||||
h
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "@onelauncher/client",
|
||||
"type": "module",
|
||||
"version": "0.1.0-alpha.2",
|
||||
"private": true,
|
||||
"description": "RSPC Client and Utilities for OneLauncher",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:polyfrost/onelauncher",
|
||||
"directory": "packages/client"
|
||||
},
|
||||
"bugs": "https://github.com/polyfrost/onelauncher/issues",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts"
|
||||
},
|
||||
"./bindings": {
|
||||
"import": "./src/bindings.ts"
|
||||
},
|
||||
"./utils": {
|
||||
"import": "./src/utils/index.ts"
|
||||
}
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@solid-primitives/deep": "^0.2.9",
|
||||
"@tanstack/solid-query": "^5.56.2",
|
||||
"@tauri-apps/api": "^2.0.3",
|
||||
"highlight.js": "^11.10.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"plausible-tracker": "^0.3.9",
|
||||
"solid-js": "^1.9.1",
|
||||
"xss": "^1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^14.1.2"
|
||||
}
|
||||
}
|
||||
1237
packages/client/src/bindings.ts
generated
1237
packages/client/src/bindings.ts
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,2 +0,0 @@
|
|||
export const WEBSITE = 'https://polyfrost.org/';
|
||||
export const TRANSLATION_WEBSITE = 'https://weblate.polyfrost.org/';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export * from './useDebug';
|
||||
export * from './usePlausible';
|
||||
export * from './useQuery';
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import { createEffect, createSignal } from 'solid-js';
|
||||
import { createMutable } from 'solid-js/store';
|
||||
import { createPersistedMutable, useSolidStore } from '../library';
|
||||
|
||||
export interface DebugState {
|
||||
enabled: boolean;
|
||||
rspcLogger: boolean;
|
||||
shareFullTelemetry: boolean;
|
||||
telemetryLogging: boolean;
|
||||
}
|
||||
|
||||
export const debugState = createPersistedMutable(
|
||||
'onelauncher-debugState',
|
||||
createMutable<DebugState>({
|
||||
enabled: globalThis.isDevelopment,
|
||||
rspcLogger: false,
|
||||
shareFullTelemetry: false,
|
||||
telemetryLogging: false,
|
||||
}),
|
||||
);
|
||||
|
||||
export const useDebugState = () => useSolidStore(debugState);
|
||||
|
||||
export function useDebugToggle(): () => void {
|
||||
const [toggled, setToggled] = createSignal(0);
|
||||
|
||||
createEffect(() => {
|
||||
if (toggled() >= 5)
|
||||
debugState.enabled = true;
|
||||
|
||||
const timeout = setTimeout(() => setToggled(0), 1000);
|
||||
return () => clearTimeout(timeout);
|
||||
});
|
||||
|
||||
return () => setToggled(toggled() + 1);
|
||||
}
|
||||
|
|
@ -1,309 +0,0 @@
|
|||
import type { PlausibleOptions as PlausibleTrackerOptions } from 'plausible-tracker';
|
||||
|
||||
import type { ProgramInfo } from '../bindings';
|
||||
import Plausible from 'plausible-tracker';
|
||||
import { createEffect, createSignal } from 'solid-js';
|
||||
import { createMutable } from 'solid-js/store';
|
||||
import { createPersistedMutable, useSolidStore } from '../library';
|
||||
import { getProgramInfo } from '../utils';
|
||||
import { useDebugState } from './useDebug';
|
||||
|
||||
/**
|
||||
* Possible platform types for Plausible.
|
||||
*/
|
||||
export type PlausiblePlatformType = 'desktop' | 'unknown';
|
||||
|
||||
/**
|
||||
* The Plausible analytics state.
|
||||
*/
|
||||
export interface PlausibleState {
|
||||
shareFullTelemetry: boolean;
|
||||
platform: PlausiblePlatformType;
|
||||
programInfo: ProgramInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* The mutable solid {@link PlausibleState} holder.
|
||||
*/
|
||||
export const plausibleState: PlausibleState = createPersistedMutable(
|
||||
'onelauncher-layout',
|
||||
createMutable<PlausibleState>({
|
||||
shareFullTelemetry: false, // false by default
|
||||
platform: 'unknown',
|
||||
programInfo: getProgramInfo(),
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* A solid hook for using the solid store {@link plausibleState}.
|
||||
*/
|
||||
export const usePlausibleState = (): PlausibleState => useSolidStore(plausibleState);
|
||||
|
||||
const PlausibleProvider = Plausible({
|
||||
trackLocalhost: true,
|
||||
domain: 'app.polyfrost.org',
|
||||
apiHost: 'https://analytics.polyfrost.org',
|
||||
});
|
||||
|
||||
/**
|
||||
* Defines all possible options that may be provided by events upon submission.
|
||||
*
|
||||
* Extends the {@link PlausibleTrackerOptions} provided by the `plausible-tracker`
|
||||
* package with additional options for custom functionality.
|
||||
*/
|
||||
export type PlausibleOptions = PlausibleTrackerOptions & { telemetryOverride?: boolean };
|
||||
|
||||
/**
|
||||
* A base Plausible event, that all other events are derived from for type safety.
|
||||
*/
|
||||
interface BasePlausibleEventWithOption<T, O extends keyof PlausibleOptions> {
|
||||
type: T;
|
||||
plausibleOptions: Required<{ [K in O]: PlausibleOptions[O]; }>;
|
||||
}
|
||||
|
||||
interface BasePlausibleEventWithoutOption<T> { type: T }
|
||||
|
||||
type BasePlausibleEvent<T, O = void> = O extends keyof PlausibleOptions
|
||||
? BasePlausibleEventWithOption<T, O>
|
||||
: BasePlausibleEventWithoutOption<T>;
|
||||
|
||||
// Plausible events
|
||||
type PageViewEvent = BasePlausibleEvent<'pageview', 'url'>;
|
||||
type ClusterCreateEvent = BasePlausibleEvent<'clusterCreate'>;
|
||||
type ClusterDeleteEvent = BasePlausibleEvent<'clusterDelete'>;
|
||||
type ModAddedEvent = BasePlausibleEvent<'modAdded'>;
|
||||
type ModRemovedEvent = BasePlausibleEvent<'modRemoved'>;
|
||||
type PingEvent = BasePlausibleEvent<'ping'>;
|
||||
|
||||
/** All plausible events, added as goals for the currently active domain. */
|
||||
type PlausibleEvent =
|
||||
| PageViewEvent
|
||||
| ClusterCreateEvent
|
||||
| ClusterDeleteEvent
|
||||
| ModAddedEvent
|
||||
| ModRemovedEvent
|
||||
| PingEvent;
|
||||
|
||||
/**
|
||||
* A wrapper for Plausible events to be handled internally.
|
||||
*
|
||||
* Allows for in-depth event logging to console and Plausible.
|
||||
*/
|
||||
interface PlausibleTrackerEvent {
|
||||
eventName: string;
|
||||
props: {
|
||||
platform: PlausiblePlatformType;
|
||||
fullTelemetry: boolean;
|
||||
coreVersion: string;
|
||||
commit: string;
|
||||
debug: boolean;
|
||||
};
|
||||
options: PlausibleTrackerOptions;
|
||||
readonly callback?: () => void;
|
||||
}
|
||||
|
||||
interface SubmitEventProps {
|
||||
/** The {@link PlausibleEvent plausible event} to submit. */
|
||||
event: PlausibleEvent;
|
||||
/** The current {@link PlausiblePlatformType platform type}. */
|
||||
platformType: PlausiblePlatformType;
|
||||
/** An optional screen width. Defaults to `window.screen.width` */
|
||||
screenWidth?: number;
|
||||
/** Whether or not full telemetry is enabled for the current client. `usePlausibleState().shareFullTelemetry` */
|
||||
shareFullTelemetry: boolean;
|
||||
/** Whether or not debug is enabled for the current client. */
|
||||
debugState: {
|
||||
enabled: boolean;
|
||||
shareFullTelemetry: boolean;
|
||||
telemetryLogging: boolean;
|
||||
};
|
||||
programInfo: ProgramInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits an event directly to our Plausible server.
|
||||
*
|
||||
* **Avoid using this directly, only send telemetry when it is certain that it has been
|
||||
* allowed by the user. Prefer the {@link usePlausibleEvent `usePlausibleEvent`} hook.**
|
||||
*
|
||||
* @remarks
|
||||
* If any of the conditions are met, this will return and no data will be submitted:
|
||||
*
|
||||
* - The app is in debug or development mode.
|
||||
* - A telemetry override is present and is not true.
|
||||
* - No telemetry override is present, and telemetry sharing is not true.
|
||||
*
|
||||
* Telemetry sharing settings are never matched to `=== false` but rather to
|
||||
* `!== true`. This is so we can gaurentee that **nothing** will be sent
|
||||
* unless the user explicitly allows it.
|
||||
*
|
||||
* @see {@link https://plausible.io/docs/custom-event-goals Plausible custom events}
|
||||
* @see {@link https://plausible-tracker.netlify.app/#tracking-custom-events-and-goals Tracking custom Plausible events}
|
||||
*/
|
||||
async function submitPlausibleEvent({ event, debugState, ...props }: SubmitEventProps): Promise<void> {
|
||||
if (props.platformType === 'unknown')
|
||||
return;
|
||||
|
||||
if (debugState.enabled && debugState.shareFullTelemetry !== true)
|
||||
return;
|
||||
|
||||
if (
|
||||
'plausibleOptions' in event && 'telemetryOverride' in event.plausibleOptions
|
||||
? event.plausibleOptions.telemetryOverride !== true
|
||||
: props.shareFullTelemetry !== true && event.type !== 'ping'
|
||||
)
|
||||
return;
|
||||
|
||||
const fullEvent: PlausibleTrackerEvent = {
|
||||
eventName: event.type,
|
||||
props: {
|
||||
platform: props.platformType,
|
||||
fullTelemetry: props.shareFullTelemetry,
|
||||
coreVersion: props.programInfo.launcher_version,
|
||||
commit: props.programInfo.tauri_version,
|
||||
debug: debugState.enabled,
|
||||
},
|
||||
options: {
|
||||
domain: 'app.polyfrost.org',
|
||||
deviceWidth: props.screenWidth ?? window.screen.width,
|
||||
referrer: '',
|
||||
...('plausibleOptions' in event ? event.plausibleOptions : undefined),
|
||||
},
|
||||
callback: debugState.telemetryLogging
|
||||
? () => {
|
||||
const { callback: _, ...event } = fullEvent;
|
||||
// eslint-disable-next-line no-console -- debug info
|
||||
console.log(event);
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
PlausibleProvider.trackEvent(
|
||||
fullEvent.eventName,
|
||||
{
|
||||
props: fullEvent.props,
|
||||
callback: fullEvent.callback,
|
||||
},
|
||||
fullEvent.options,
|
||||
);
|
||||
}
|
||||
|
||||
interface EventSubmissionCallbackProps {
|
||||
/** The {@link PlausibleEvent Plausible event} to be submitted. */
|
||||
event: PlausibleEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a Plausible Analytics event with a hook.
|
||||
*
|
||||
* The returned callback will only the fired once as to avoid flooding analytics.
|
||||
*
|
||||
* Certain events provide functionality to override telemetry sharing. This is because
|
||||
* it should only be used in contexts where telemetry sharing must be allowed by external means.
|
||||
*
|
||||
* @remarks
|
||||
* If any of the conditions are met, this will return and no data will be submitted:
|
||||
*
|
||||
* - The app is in debug or development mode.
|
||||
* - A telemetry override is present and is not true.
|
||||
* - No telemetry override is present, and telemetry sharing is not true.
|
||||
*
|
||||
* @returns a callback that, once executed, will submit the event.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const submitPlausibleEvent = usePlausibleEvent();
|
||||
*
|
||||
* const createdCluster = commands.createCluster('myCluster').then(cluster => {
|
||||
* submitPlausibleEvent({
|
||||
* event: {
|
||||
* type: 'clusterCreate',
|
||||
* },
|
||||
* });
|
||||
* return cluster;
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function usePlausibleEvent(): (props: EventSubmissionCallbackProps) => Promise<void> {
|
||||
const debugState = useDebugState();
|
||||
const plausibleState = usePlausibleState();
|
||||
const [previousEvent, setPreviousEvent] = createSignal<BasePlausibleEvent<string> | null>(null);
|
||||
|
||||
return async (props: EventSubmissionCallbackProps) => {
|
||||
if (previousEvent() === props.event)
|
||||
return;
|
||||
setPreviousEvent(props.event);
|
||||
|
||||
await submitPlausibleEvent({
|
||||
debugState,
|
||||
shareFullTelemetry: plausibleState.shareFullTelemetry,
|
||||
platformType: plausibleState.platform,
|
||||
programInfo: plausibleState.programInfo,
|
||||
...props,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export interface PlausibleMonitorProps {
|
||||
/** A sanitized string containing the current active path the client is viewing. */
|
||||
currentPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a Plausible Analytics `'pageview'` event with a hook.
|
||||
*
|
||||
* This should watch the Solid router's current path, and send an event if the path is changed.
|
||||
*
|
||||
* @remarks
|
||||
* If any of the conditions are met, this will return and no data will be submitted:
|
||||
*
|
||||
* - The app is in debug or development mode.
|
||||
* - A telemetry override is present and is not true.
|
||||
* - No telemetry override is present, and telemetry sharing is not true.
|
||||
*/
|
||||
export function usePlausiblePageview({ currentPath }: PlausibleMonitorProps): void {
|
||||
const plausibleEvent = usePlausibleEvent();
|
||||
|
||||
createEffect(() => {
|
||||
plausibleEvent({
|
||||
event: {
|
||||
type: 'pageview',
|
||||
plausibleOptions: { url: currentPath },
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a Plausible Analytics `'ping'` event with a hook.
|
||||
*
|
||||
* This should watch the Solid router's current path, and send an event if the path is changed.
|
||||
*
|
||||
* @remarks
|
||||
* If any of the conditions are met, this will return and no data will be submitted:
|
||||
*
|
||||
* - The app is in debug or development mode.
|
||||
* - A telemetry override is present and is not true.
|
||||
* - No telemetry override is present, and telemetry sharing is not true.
|
||||
*/
|
||||
export function usePlausiblePing() {
|
||||
const plausibleEvent = usePlausibleEvent();
|
||||
|
||||
createEffect(() => {
|
||||
plausibleEvent({
|
||||
event: {
|
||||
type: 'ping',
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export interface PlausibleInitProps {
|
||||
platformType: PlausiblePlatformType;
|
||||
programInfo: ProgramInfo;
|
||||
}
|
||||
|
||||
export function initializePlausible(props: PlausibleInitProps): void {
|
||||
plausibleState.platform = props.platformType;
|
||||
plausibleState.programInfo = props.programInfo;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export { createQuery } from '@tanstack/solid-query';
|
||||
export { createQuery as default } from '@tanstack/solid-query';
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
declare global {
|
||||
// eslint-disable-next-line vars-on-top, no-var -- global patched variable
|
||||
var isDevelopment: boolean;
|
||||
// eslint-disable-next-line vars-on-top, no-var -- global patched variable
|
||||
var onHotReload: (cb: () => void) => void | undefined;
|
||||
}
|
||||
|
||||
if (globalThis.isDevelopment === undefined || globalThis.localStorage === undefined)
|
||||
throw new Error('please ensure you have patched `globalThis` before importing the client');
|
||||
|
||||
declare global {
|
||||
// global patched function because tauri/webkit does weird promise polyfills
|
||||
export function confirm(): boolean | Promise<boolean>;
|
||||
}
|
||||
|
||||
export * from './constants';
|
||||
export * from './hooks';
|
||||
export * from './library';
|
||||
export type * from './types';
|
||||
export * from './utils';
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
import type { StoreNode } from 'solid-js/store';
|
||||
import { trackDeep } from '@solid-primitives/deep';
|
||||
import { createEffect, createRoot } from 'solid-js';
|
||||
|
||||
export interface CreatePersistedMutableOpts<T> {
|
||||
onSave?: (value: T) => T;
|
||||
}
|
||||
|
||||
// copied from `makePersisted` in `@solid-primitives/storage` with `solid-js/store`.
|
||||
export function createPersistedMutable<T extends StoreNode>(
|
||||
key: string,
|
||||
mutable: T,
|
||||
opts?: CreatePersistedMutableOpts<T>,
|
||||
): T {
|
||||
try {
|
||||
const value = localStorage.getItem(key);
|
||||
if (value) {
|
||||
const persisted = JSON.parse(value);
|
||||
Object.assign(mutable, persisted);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`failed to load persisted state from localStorage['${key}']: ${err}`);
|
||||
}
|
||||
|
||||
const dispose = createRoot((dispose) => {
|
||||
createEffect(() => {
|
||||
trackDeep(mutable);
|
||||
const item = opts?.onSave
|
||||
? JSON.stringify(opts.onSave(mutable))
|
||||
: JSON.stringify(mutable);
|
||||
|
||||
localStorage.setItem(key, item);
|
||||
});
|
||||
return dispose;
|
||||
});
|
||||
|
||||
if ('onHotReload' in globalThis)
|
||||
globalThis?.onHotReload(dispose);
|
||||
|
||||
return mutable;
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export * from './createPersistedMutable';
|
||||
export * from './useObserver';
|
||||
export * from './useSolidStore';
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import type { Owner } from 'solid-js';
|
||||
import { createEffect, createReaction, createRoot, createSignal, onCleanup, runWithOwner } from 'solid-js';
|
||||
|
||||
export interface Observer {
|
||||
dispose: () => void;
|
||||
track: (fn: () => void) => void;
|
||||
};
|
||||
|
||||
// copied from `useObserver` in `react-solid-state`, ported to work with new versions.
|
||||
// https://github.com/solidjs/react-solid-state/issues/4
|
||||
export function useObserver<T>(fn: () => T): T {
|
||||
const [tick, setTick] = createSignal(0);
|
||||
const state = {
|
||||
onUpdate: (): void => {
|
||||
state.firedDuringRender = true;
|
||||
},
|
||||
firstRenderFired: false,
|
||||
firedDuringRender: false,
|
||||
};
|
||||
|
||||
let reaction: Observer | undefined;
|
||||
reaction = createRoot(dispose => ({
|
||||
dispose,
|
||||
track: createReaction(() => state.onUpdate()),
|
||||
}));
|
||||
|
||||
createEffect(() => {
|
||||
if (state.firedDuringRender)
|
||||
setTick(tick() + 1);
|
||||
|
||||
state.onUpdate = () => setTick(tick() + 1);
|
||||
state.firstRenderFired = true;
|
||||
|
||||
onCleanup(() => {
|
||||
state.onUpdate = () => {
|
||||
state.firedDuringRender = false;
|
||||
};
|
||||
|
||||
if (!state.firstRenderFired) {
|
||||
reaction?.dispose();
|
||||
reaction = undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let rendering!: T;
|
||||
reaction.track(() => (rendering = fn()));
|
||||
return rendering;
|
||||
}
|
||||
|
||||
export interface OwnedObserver { track: (fn: () => void) => void }
|
||||
|
||||
export function useObserverWithOwner<T>(owner: Owner, fn: () => T): T {
|
||||
const [tick, setTick] = createSignal(0);
|
||||
const state = { onUpdate: () => {} };
|
||||
|
||||
const reaction: OwnedObserver = runWithOwner(owner, () => ({
|
||||
track: createReaction(() => state.onUpdate()),
|
||||
}))!;
|
||||
|
||||
createEffect(() => {
|
||||
state.onUpdate = () => setTick(tick() + 1);
|
||||
});
|
||||
|
||||
let rendering!: T;
|
||||
reaction.track(() => (rendering = fn()));
|
||||
return rendering;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import type { Store } from 'solid-js/store';
|
||||
import { useObserver } from './useObserver';
|
||||
|
||||
/**
|
||||
* A wrapper for {@link useObserver} to integrate with `solid-js/store`.
|
||||
*/
|
||||
export function useSolidStore<T extends object = object>(store: Store<T>): T {
|
||||
const state = useObserver(() => ({ ...store }));
|
||||
return new Proxy(state, {
|
||||
get: (target, prop) => Reflect.get(target, prop),
|
||||
set: (_, prop, value) => Reflect.set(store, prop, value),
|
||||
});
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import hljs from 'highlight.js/lib/core';
|
||||
import { configuredXss, md, noop } from './markdown';
|
||||
|
||||
function langFactory(lang: string) {
|
||||
return (imp: typeof import('highlight.js/lib/languages/*')) => {
|
||||
hljs.registerLanguage(lang, imp.default);
|
||||
};
|
||||
}
|
||||
|
||||
export function registerLanguages() {
|
||||
const _languages = [
|
||||
import('highlight.js/lib/languages/javascript').then(langFactory('javascript')),
|
||||
import('highlight.js/lib/languages/python').then(langFactory('python')),
|
||||
import('highlight.js/lib/languages/lua').then(langFactory('lua')),
|
||||
import('highlight.js/lib/languages/kotlin').then(langFactory('kotlin')),
|
||||
import('highlight.js/lib/languages/scala').then(langFactory('scala')),
|
||||
import('highlight.js/lib/languages/groovy').then(langFactory('groovy')),
|
||||
import('highlight.js/lib/languages/gradle').then(langFactory('gradle')),
|
||||
import('highlight.js/lib/languages/json').then(langFactory('json')),
|
||||
import('highlight.js/lib/languages/ini').then(langFactory('ini')),
|
||||
import('highlight.js/lib/languages/yaml').then(langFactory('yaml')),
|
||||
import('highlight.js/lib/languages/properties').then(langFactory('properties')),
|
||||
];
|
||||
}
|
||||
|
||||
registerLanguages();
|
||||
hljs.registerAliases(['js'], { languageName: 'javascript' });
|
||||
hljs.registerAliases(['py'], { languageName: 'python' });
|
||||
hljs.registerAliases(['kt', 'kts'], { languageName: 'kotlin' });
|
||||
hljs.registerAliases(['json5'], { languageName: 'json' });
|
||||
hljs.registerAliases(['toml'], { languageName: 'ini' });
|
||||
hljs.registerAliases(['yml'], { languageName: 'yaml' });
|
||||
hljs.registerAliases(['html', 'htm', 'xhtml', 'mcui', 'fxml'], { languageName: 'xml' });
|
||||
|
||||
export function renderHighlightedString(src: string): string {
|
||||
return configuredXss.process(
|
||||
md({
|
||||
highlight(str, lang) {
|
||||
if (lang && hljs.getLanguage(lang))
|
||||
try {
|
||||
return hljs.highlight(str, { language: lang }).value;
|
||||
}
|
||||
catch (err) {
|
||||
noop(err);
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
}).render(src),
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
export * from './highlight';
|
||||
export * from './markdown';
|
||||
export * from './modrinth';
|
||||
export * from './programInfo';
|
||||
export * from './sorting';
|
||||
export * from './timer';
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
import MarkdownIt, { type Options as MarkdownOptions } from 'markdown-it';
|
||||
import { escapeAttrValue, FilterXSS, safeAttrValue, whiteList } from 'xss';
|
||||
|
||||
// from https://github.com/modrinth/code/blob/main/packages/utils/parse.ts for parsing modrinth style markdown
|
||||
export const configuredXss: FilterXSS = new FilterXSS({
|
||||
whiteList: {
|
||||
...whiteList,
|
||||
summary: [],
|
||||
h1: ['id'],
|
||||
h2: ['id'],
|
||||
h3: ['id'],
|
||||
h4: ['id'],
|
||||
h5: ['id'],
|
||||
h6: ['id'],
|
||||
kbd: ['id'],
|
||||
input: ['checked', 'disabled', 'type'],
|
||||
iframe: ['width', 'height', 'allowfullscreen', 'frameborder', 'start', 'end'],
|
||||
img: [...(whiteList.img || []), 'usemap', 'style', 'align'],
|
||||
map: ['name'],
|
||||
area: [...(whiteList.a || []), 'coords'],
|
||||
a: [...(whiteList.a || []), 'rel'],
|
||||
td: [...(whiteList.td || []), 'style'],
|
||||
th: [...(whiteList.th || []), 'style'],
|
||||
picture: [],
|
||||
source: ['media', 'sizes', 'src', 'srcset', 'type'],
|
||||
p: [...(whiteList.p || []), 'align'],
|
||||
div: [...(whiteList.p || []), 'align'],
|
||||
|
||||
// Other
|
||||
center: [],
|
||||
code: [],
|
||||
strike: [],
|
||||
strong: [],
|
||||
em: [],
|
||||
b: [],
|
||||
blockquote: [],
|
||||
table: [],
|
||||
tbody: [],
|
||||
tr: [],
|
||||
video: [],
|
||||
},
|
||||
css: {
|
||||
whiteList: {
|
||||
'image-rendering': /^pixelated$/,
|
||||
'text-align': /^center|left|right$/,
|
||||
'float': /^left|right$/,
|
||||
},
|
||||
},
|
||||
onIgnoreTagAttr: (tag, name, value) => {
|
||||
if (tag === 'iframe' && name === 'src') {
|
||||
const allowed = [
|
||||
{
|
||||
url: /^https?:\/\/(www\.)?youtube(-nocookie)?\.com\/embed\/[\w-]{11}/,
|
||||
allowedParameters: [/start=\d+/, /end=\d+/],
|
||||
},
|
||||
{
|
||||
url: /^https?:\/\/(www\.)?discord\.com\/widget/,
|
||||
allowedParameters: [/id=\d{18,19}/],
|
||||
},
|
||||
];
|
||||
|
||||
const url = new URL(value);
|
||||
for (const src of allowed) {
|
||||
if (!src.url.test(url.href))
|
||||
continue;
|
||||
|
||||
const newSearchParams = new URLSearchParams();
|
||||
url.searchParams.forEach((v, k) => {
|
||||
if (!src.allowedParameters.some(p => p.test(`${k}=${v}`)))
|
||||
newSearchParams.delete(k);
|
||||
});
|
||||
|
||||
url.search = newSearchParams.toString();
|
||||
return `${name}="${escapeAttrValue(url.toString())}"`;
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'class' && ['code', 'pre', 'span'].includes(tag)) {
|
||||
const allowedClasses: string[] = [];
|
||||
for (const className of value.split(/\s/g))
|
||||
if (className.startsWith('hljs-') || className.startsWith('language-'))
|
||||
allowedClasses.push(className);
|
||||
|
||||
return `${name}="${escapeAttrValue(allowedClasses.join(' '))}"`;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
safeAttrValue: (tag, name, value, cssFilter) => {
|
||||
if (tag === 'img' && name === 'src' && !value.startsWith('data:'))
|
||||
try {
|
||||
const url = new URL(value);
|
||||
if (url.hostname.includes('wsrv.nl'))
|
||||
url.searchParams.delete('errorredirect');
|
||||
|
||||
const allowed = [
|
||||
'imgur.com',
|
||||
'i.imgur.com',
|
||||
'cdn-raw.modrinth.com',
|
||||
'cdn.modrinth.com',
|
||||
'staging-cdn-raw.modrinth.com',
|
||||
'staging-cdn.modrinth.com',
|
||||
'github.com',
|
||||
'raw.githubusercontent.com',
|
||||
'img.shields.io',
|
||||
'wsrv.nl',
|
||||
'cf.way2muchnoise.eu',
|
||||
'bstats.org',
|
||||
];
|
||||
|
||||
if (!allowed.includes(url.hostname))
|
||||
return safeAttrValue(
|
||||
tag,
|
||||
name,
|
||||
`https://wsrv.nl/?url=${encodeURIComponent(
|
||||
url.toString().replaceAll('&', '&'),
|
||||
)}&n=-1`,
|
||||
cssFilter,
|
||||
);
|
||||
|
||||
return safeAttrValue(tag, name, url.toString(), cssFilter);
|
||||
}
|
||||
catch (err) {
|
||||
noop(err);
|
||||
}
|
||||
|
||||
return safeAttrValue(tag, name, value, cssFilter);
|
||||
},
|
||||
});
|
||||
|
||||
export function md(options: MarkdownOptions = {}): MarkdownIt {
|
||||
const md = new MarkdownIt('default', { html: true, linkify: true, ...options });
|
||||
|
||||
const defaultLinkOpenRenderer
|
||||
= md.renderer.rules.link_open
|
||||
|| ((tokens, idx, options, _env, self) => self.renderToken(tokens, idx, options));
|
||||
|
||||
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
|
||||
const token = tokens[idx]!;
|
||||
const index = token.attrIndex('href');
|
||||
|
||||
if (token.attrs && index !== -1) {
|
||||
const href = token.attrs[index]![1];
|
||||
|
||||
try {
|
||||
const url = new URL(href);
|
||||
const allowed = ['modrinth.com'];
|
||||
if (allowed.includes(url.hostname))
|
||||
return defaultLinkOpenRenderer(tokens, idx, options, env, self);
|
||||
}
|
||||
catch (err) {
|
||||
noop(err);
|
||||
}
|
||||
}
|
||||
|
||||
tokens[idx]!.attrSet('rel', 'noopener nofollow ugc');
|
||||
return defaultLinkOpenRenderer(tokens, idx, options, env, self);
|
||||
};
|
||||
|
||||
return md;
|
||||
}
|
||||
|
||||
export const renderString = (src: string): string => configuredXss.process(md().render(src));
|
||||
|
||||
export function noop(_t: any) {}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
export const MR_STAFF_ROLS = ['moderator', 'admin'];
|
||||
export const MR_APPROVED = ['approved', 'archived', 'unlisted', 'private'];
|
||||
export const MR_LISTED = ['approved', 'archived'];
|
||||
export const MR_UNLISTED = ['unlisted', 'withheld'];
|
||||
export const MR_PRIVATE = ['private', 'rejected', 'processing'];
|
||||
export const MR_REJECTED = ['rejected', 'withheld'];
|
||||
export const MR_REVIEWING = ['processing'];
|
||||
export const MR_DRAFT = ['draft'];
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import { commands, type ProgramInfo } from '../bindings';
|
||||
|
||||
let _programInfo: ProgramInfo = {
|
||||
arch: 'unknown',
|
||||
platform: 'unknown',
|
||||
dev_build: true,
|
||||
launcher_version: 'unknown',
|
||||
tauri_version: 'unknown',
|
||||
webview_version: 'unknown',
|
||||
};
|
||||
|
||||
export async function initProgramInfo() {
|
||||
const result = await commands.getProgramInfo();
|
||||
if (result.status === 'ok')
|
||||
_programInfo = result.data;
|
||||
|
||||
setBodyAttributes();
|
||||
}
|
||||
|
||||
export const getProgramInfo = () => _programInfo;
|
||||
|
||||
function setBodyAttributes() {
|
||||
document.body.setAttribute('data-platform', _programInfo.platform);
|
||||
if (_programInfo.platform === 'linux') {
|
||||
const minorVersion = Number.parseInt(_programInfo.webview_version.split('.')[1] || 'NaN');
|
||||
|
||||
// Check if WebKitGTK version is 2.46 or higher (Skia renderer implemented which fixes some issues)
|
||||
document.body.setAttribute('data-skia-renderer', (!Number.isNaN(minorVersion) && minorVersion >= 46).toString());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import { type Accessor, createSignal, type Setter } from 'solid-js';
|
||||
|
||||
export type SortingFunction<T> = (a: T, b: T) => number;
|
||||
export type Sortables<T> = Record<string, SortingFunction<T>>;
|
||||
|
||||
interface CreateSortable<T, S extends Sortables<T> = Sortables<T>> {
|
||||
list: Accessor<T[]>;
|
||||
key: Accessor<keyof S>;
|
||||
setList: Setter<T[]>;
|
||||
setKey: (val: keyof S | number) => void;
|
||||
sortables: S;
|
||||
}
|
||||
|
||||
export function createSortable<T, S extends Sortables<T> = Sortables<T>>(initList: T[], initSortables: S): CreateSortable<T, S> {
|
||||
const sortables = initSortables;
|
||||
const [list, setList] = createSignal<T[]>(initList);
|
||||
const [key, setKey] = createSignal<keyof S>(Object.keys(sortables)[0]!);
|
||||
|
||||
return {
|
||||
list,
|
||||
key,
|
||||
setList,
|
||||
setKey: (val: keyof S | number) => {
|
||||
if (typeof val === 'string') {
|
||||
setKey(val);
|
||||
}
|
||||
else if (typeof val === 'number') {
|
||||
const key = Object.keys(sortables).at(val);
|
||||
if (key === undefined)
|
||||
throw new Error('Key cannot be undefined!');
|
||||
|
||||
setKey(key);
|
||||
}
|
||||
},
|
||||
sortables,
|
||||
};
|
||||
}
|
||||
|
||||
export default createSortable;
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
export class PausableTimer {
|
||||
private _timeout: NodeJS.Timeout;
|
||||
private _paused: boolean = false;
|
||||
private _unixStarted: number = 0;
|
||||
private _unixStopped: number = 0;
|
||||
|
||||
constructor(
|
||||
private readonly callback: () => unknown,
|
||||
private readonly duration: number,
|
||||
private readonly interval: boolean = false,
|
||||
) {
|
||||
this._timeout = this._createTimeout();
|
||||
}
|
||||
|
||||
private _createTimeout(duration: number = this.duration): NodeJS.Timeout {
|
||||
this._unixStarted = Date.now();
|
||||
if (this.interval)
|
||||
return setInterval(this.callback, duration);
|
||||
else
|
||||
return setTimeout(this.callback, duration);
|
||||
}
|
||||
|
||||
private _clearTimeout() {
|
||||
this._unixStopped = Date.now();
|
||||
if (this.interval)
|
||||
clearInterval(this._timeout);
|
||||
else
|
||||
clearTimeout(this._timeout);
|
||||
}
|
||||
|
||||
public resume(restart: boolean = false) {
|
||||
const elapsed = this._unixStopped - this._unixStarted;
|
||||
const duration = this.interval || restart ? this.duration : this.duration - elapsed;
|
||||
|
||||
this._paused = false;
|
||||
this._timeout = this._createTimeout(duration);
|
||||
}
|
||||
|
||||
public pause() {
|
||||
this._clearTimeout();
|
||||
this._paused = true;
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this._clearTimeout();
|
||||
}
|
||||
|
||||
public get timeout() {
|
||||
return this._timeout;
|
||||
}
|
||||
|
||||
public get paused() {
|
||||
return this._paused;
|
||||
}
|
||||
}
|
||||
|
||||
export default function createPausableTimer(callback: () => unknown, duration: number): PausableTimer {
|
||||
return new PausableTimer(callback, duration);
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts"]
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import shared from '../../shared.config';
|
||||
|
||||
export default shared`client`.vitest;
|
||||
5501
pnpm-lock.yaml
generated
5501
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,5 @@
|
|||
packages:
|
||||
- apps/*
|
||||
- apps/oneclient/*
|
||||
- apps/onelauncher/*
|
||||
- packages/*
|
||||
- .github/actions/*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vitest/globals", "vite/client", "node"]
|
||||
"types": ["vite/client", "node"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export { default } from './apps/frontend/uno.config';
|
||||
export { default } from './apps/onelauncher/frontend/uno.config';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue