clan-app: Add knip dead code analyzer

This commit is contained in:
Qubasa
2025-06-18 21:10:18 +02:00
parent f47db5f7c1
commit 0ac445facc
6 changed files with 436 additions and 289 deletions

View File

@@ -0,0 +1,9 @@
{
"ignore": [
"gtk.webview.js",
"stylelint.config.js",
"util.ts",
"src/components/v2/**",
"api/**"
]
}

View File

@@ -10,6 +10,7 @@
"convert-html": "node gtk.webview.js",
"serve": "vite preview",
"check": "tsc --noEmit --skipLibCheck && eslint ./src",
"knip": "knip",
"test": "vitest run --project unit --typecheck",
"storybook": "storybook",
"storybook-build": "storybook build",
@@ -43,6 +44,7 @@
"eslint-plugin-tailwindcss": "^3.17.0",
"http-server": "^14.1.1",
"jsdom": "^26.1.0",
"knip": "^5.61.2",
"playwright": "~1.52.0",
"postcss": "^8.4.38",
"postcss-url": "^10.1.3",

View File

@@ -23,7 +23,6 @@ import { ApiTester } from "./api_test";
import { IconVariant } from "./components/icon";
import { Components } from "./routes/components";
import { VarsPage } from "./routes/machines/install/vars-step";
import { ThreePlayground } from "./three";
import { ClanProvider } from "./contexts/clan";
export const client = new QueryClient();
@@ -160,28 +159,12 @@ export const routes: AppRoute[] = [
path: "/internal-dev",
label: "Internal (Only visible in dev mode)",
children: [
{
path: "/hosts",
label: "Local Hosts",
component: () => <HostList />,
},
{
path: "/3d",
label: "3D-Playground",
component: () => <ThreePlayground />,
},
{
path: "/api_testing",
label: "api_testing",
hidden: false,
component: () => <ApiTester />,
},
{
path: "/components",
label: "Components",
hidden: false,
component: () => <Components />,
},
],
},
];

View File

@@ -1,271 +0,0 @@
import { createEffect, createSignal, onCleanup, onMount, Show } from "solid-js";
import * as THREE from "three";
import { Button } from "./components/Button/Button";
import Icon from "./components/icon";
function addCubesSpiral({
scene,
count,
gap,
selected,
}: {
scene: THREE.Scene;
count: number;
gap: number;
selected?: string;
}) {
const cubeSize = 1;
const baseSize = 1.4;
const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
const baseGeometry = new THREE.BoxGeometry(baseSize, 0.05, baseSize);
const cubeMaterial = new THREE.MeshStandardMaterial({
color: 0xe0e0e0,
roughness: 0.6,
metalness: 0.1,
});
const baseMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff,
roughness: 0.8,
metalness: 0,
});
let placed = 0;
const visited = new Set<string>();
let x = 0;
let z = 0;
let dx = 1;
let dz = 0;
let segmentLength = 1;
let segmentPassed = 0;
let stepsTaken = 0;
let turnCounter = 0;
while (placed < count) {
const key = `${x},${z}`;
if (!visited.has(key)) {
if ((x + z) % 2 === 0) {
// Place base
const base = new THREE.Mesh(baseGeometry, baseMaterial);
base.position.set(x * gap, 0, z * gap);
base.receiveShadow = true;
base.castShadow = true;
scene.add(base);
// Place cube
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
if (selected && +selected === placed) {
console.log("Selected", placed);
cube.material = new THREE.MeshStandardMaterial({
color: 0x99e0ff,
roughness: 0.6,
metalness: 0.1,
});
base.material = new THREE.MeshStandardMaterial({
color: 0x99e0ff,
roughness: 0.6,
metalness: 0.1,
});
}
// Store
cube.userData = { id: placed };
cube.position.set(x * gap, 0.55, z * gap);
cube.castShadow = true;
scene.add(cube);
placed++;
}
visited.add(key);
}
x += dx;
z += dz;
segmentPassed++;
stepsTaken++;
if (segmentPassed === segmentLength) {
segmentPassed = 0;
// Turn right: [1,0] → [0,1] → [-1,0] → [0,-1]
const temp = dx;
dx = -dz;
dz = temp;
turnCounter++;
if (turnCounter % 2 === 0) {
segmentLength++;
}
}
// Fail-safe to prevent infinite loops
if (stepsTaken > count * 20) break;
}
// Clean up geometry
cubeGeometry.dispose();
baseGeometry.dispose();
}
interface ViewProps {
count: number;
onCubeClick: (id: number) => void;
selected?: string;
}
const View = (props: ViewProps) => {
let container: HTMLDivElement | undefined;
onMount(() => {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
container!.clientWidth / container!.clientHeight,
0.1,
1000,
);
// Transparent renderer
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(container!.clientWidth, container!.clientHeight);
renderer.setClearColor(0x000000, 0); // Transparent background
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container!.appendChild(renderer.domElement);
// Cube (casts shadow)
const cubeGeometry = new THREE.BoxGeometry();
const cubeMaterial = new THREE.MeshStandardMaterial({
color: 0xb0c0c2,
roughness: 0.4,
metalness: 0.1,
});
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.castShadow = true;
cube.position.y = 1;
// scene.add(cube);
addCubesSpiral({
scene,
count: props.count,
gap: 1.5,
selected: props.selected,
});
const factor = Math.log10(props.count) / 10 + 1;
camera.position.set(5 * factor, 6 * factor, 5 * factor); // from above and to the side
camera.lookAt(0, 0, 0);
// Floor (receives shadow)
const floorGeometry = new THREE.PlaneGeometry(100, 100);
const floorMaterial = new THREE.ShadowMaterial({ opacity: 0.1 });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.position.y = -0.1;
floor.receiveShadow = true;
scene.add(floor);
// Light (casts shadow)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(-20, 30, 20); // above & behind the cube
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048; // higher res = smoother shadow
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.radius = 6;
// directionalLight.shadow.radius
scene.add(directionalLight);
// Optional ambient light for slight scene illumination
scene.add(new THREE.AmbientLight(0xffffff, 0.2));
// Animate
// const animate = () => {
// animationId = requestAnimationFrame(animate);
// };
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const handleClick = (event: MouseEvent) => {
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
const cube = intersects.find((i) => i.object.userData?.id !== undefined);
if (cube) {
props.onCubeClick(cube.object.userData.id);
}
};
renderer.domElement.addEventListener("click", handleClick);
renderer.render(scene, camera);
// let animationId = requestAnimationFrame(animate);
onCleanup(() => {
// cancelAnimationFrame(animationId);
renderer.dispose();
cubeGeometry.dispose();
cubeMaterial.dispose();
floorGeometry.dispose();
floorMaterial.dispose();
container?.removeChild(renderer.domElement);
renderer.domElement.removeEventListener("click", handleClick);
});
});
return (
<div
ref={container}
style={{ width: "100%", height: "100%", overflow: "hidden" }}
/>
);
};
export const ThreePlayground = () => {
const [count, setCount] = createSignal(1);
const [selected, setSelected] = createSignal<string>("");
const onCubeClick = (id: number) => {
console.log(`Cube ${id} clicked`);
setSelected(`${id}`);
};
return (
<div class="relative size-full">
<Show when={selected() || !selected()} keyed>
<Show when={count()} keyed>
{(c) => (
<View count={c} onCubeClick={onCubeClick} selected={selected()} />
)}
</Show>
</Show>
<div class="absolute bottom-4 right-0 z-10 flex w-full items-center justify-center">
<div class="flex w-fit items-center justify-between gap-4 rounded-xl border px-8 py-2 text-white shadow-2xl bg-inv-1 border-inv-1">
<Button startIcon={<Icon icon="Edit" />}></Button>
<Button startIcon={<Icon icon="Grid" />}></Button>
<Button
startIcon={<Icon icon="Plus" />}
onClick={() => {
setCount((c) => c + 1);
}}
></Button>
<Button
startIcon={<Icon icon="Trash" />}
onClick={() => {
setCount((c) => c - 1);
}}
></Button>
</div>
</div>
</div>
);
};

View File

@@ -51,4 +51,4 @@ export default defineConfig({
// assetsDi
manifest: true,
},
});
});