Sfoglia il codice sorgente

chore: 1059 fix typecheck errors in docs (#1060)

* refactor: fixed type errors in app.config.ts

* refactor: improve type safety and null handling in useGraph and related components

* refactor: enhance type safety and null handling in Experience and graph utilities

* refactor: enhance type safety in HologramCube component

* chore: added typecheck to ci job

* refactor: move type check for Mesh to a separate function in HologramCube component for improved clarity

* fix: smaller lint fixes
Tino Koch 3 giorni fa
parent
commit
575ed04911

+ 2 - 0
.github/workflows/ci.yml

@@ -40,3 +40,5 @@ jobs:
         run: pnpm run lint
       - name: Unit tests
         run: pnpm run test
+      - name: Docs typecheck
+        run: pnpm run docs:typecheck

+ 1 - 0
docs/.gitignore

@@ -5,6 +5,7 @@
 .nitro
 .cache
 dist
+.vitepress
 
 # Node dependencies
 node_modules

+ 6 - 13
docs/app/app.config.ts

@@ -1,8 +1,9 @@
-export default defineAppConfig({
+import type { AppConfigInput } from 'nuxt/schema'
+
+const newLocal = {
   ui: {
     colors: {
       primary: 'teal',
-      accent: 'yellow',
       neutral: 'zinc',
     },
     card: {
@@ -33,16 +34,6 @@ export default defineAppConfig({
     },
     search: true,
     colorMode: true,
-    navigation: [
-      {
-        label: 'Guide',
-        to: '/getting-started/installation',
-      },
-      {
-        label: 'API',
-        to: '/api',
-      },
-    ],
     links: [
       {
         icon: 'i-simple-icons-github',
@@ -101,4 +92,6 @@ export default defineAppConfig({
       }],
     },
   },
-})
+} satisfies AppConfigInput
+
+export default defineAppConfig(newLocal)

+ 18 - 10
docs/app/components/AppHeader.vue

@@ -1,8 +1,21 @@
 <script setup lang="ts">
+import type { NavigationMenuItem } from '@nuxt/ui'
+
 const navigation = inject(navigationInjectionKey)
 
 const { header } = useAppConfig()
 
+const navItems: NavigationMenuItem[] = [
+  {
+    label: 'Guide',
+    to: '/getting-started/installation',
+  },
+  {
+    label: 'API',
+    to: '/api',
+  },
+]
+
 const route = useRoute()
 
 const version = useRuntimeConfig().public.pkgVersion
@@ -41,7 +54,10 @@ const version = useRuntimeConfig().public.pkgVersion
       v-else
       #left
     >
-      <NuxtLink :to="header?.to || '/'" class="mr-2">
+      <NuxtLink
+        :to="header?.to || '/'"
+        class="mr-2"
+      >
         <TheLogo class="w-auto h-6 shrink-0" />
       </NuxtLink>
       <UBadge
@@ -60,17 +76,9 @@ const version = useRuntimeConfig().public.pkgVersion
         variant="subtle"
       />
 
-      <!-- <template v-if="header?.navigation">
-        <UButton
-          v-for="(item, index) of header.navigation"
-          :key="index"
-          v-bind="{ color: 'neutral', variant: 'ghost', ...item }"
-        />
-      </template> -->
-
       <UNavigationMenu
         v-if="route.path === '/'"
-        :items="header?.navigation"
+        :items="navItems"
         class="hidden md:flex"
       />
 

+ 10 - 4
docs/app/components/examples/on-demand/Experience.vue

@@ -4,6 +4,7 @@ import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
 import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
 import { computed } from 'vue'
 import { Environment, OrbitControls } from '@tresjs/cientos'
+import type { MeshPhysicalMaterial, Object3D } from 'three'
 import { Color } from 'three'
 
 // Setup DRACO loader for compressed GLTFs
@@ -24,13 +25,18 @@ const { state: model } = useLoader<GLTF>(
 )
 
 // Extract the scene and graph
-const scene = computed(() => model.value?.scene)
+const scene = computed(() => model.value?.scene as unknown as Object3D | undefined)
 const graph = useGraph(scene)
 
-const nodes = computed(() => graph.value.nodes)
+const nodes = computed(() => graph.value?.nodes)
 
-watch(graph, ({ materials }) => {
-  if (materials?.Material) {
+watch(graph, (graph) => {
+  const materials = graph?.materials
+
+  const isMeshPhysicalMaterial = (maybeMaterial: unknown): maybeMaterial is MeshPhysicalMaterial =>
+    typeof maybeMaterial === 'object' && !!maybeMaterial && 'isMeshPhysicalMaterial' in maybeMaterial && !!(maybeMaterial.isMeshPhysicalMaterial)
+
+  if (materials?.Material && isMeshPhysicalMaterial(materials.Material)) {
     materials.Material.color = new Color('gold')
     materials.Material.roughness = 0.1
     materials.Material.metalness = 0.9

+ 3 - 2
docs/app/components/examples/use-loader-gltf/Experience.vue

@@ -4,6 +4,7 @@ import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
 import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
 import { computed } from 'vue'
 import { OrbitControls } from '@tresjs/cientos'
+import type { Object3D } from 'three'
 
 // Setup DRACO loader for compressed GLTFs
 const dracoLoader = new DRACOLoader()
@@ -23,10 +24,10 @@ const { state: model } = useLoader<GLTF>(
 )
 
 // Extract the scene and graph
-const scene = computed(() => model.value?.scene)
+const scene = computed(() => model.value?.scene as unknown as Object3D | undefined)
 const graph = useGraph(scene)
 
-const nodes = computed(() => graph.value.nodes)
+const nodes = computed(() => graph.value?.nodes)
 </script>
 
 <template>

+ 5 - 2
docs/app/components/examples/web-gpu/HologramCube.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import { useGLTF } from '@tresjs/cientos'
 import { add, cameraProjectionMatrix, cameraViewMatrix, color, Fn, hash, mix, normalView, positionWorld, sin, timerGlobal, uniform, varying, vec3, vec4 } from 'three/tsl'
+import type { Mesh, Object3D } from 'three/webgpu'
 import { AdditiveBlending, DoubleSide, MeshBasicNodeMaterial } from 'three/webgpu'
 
 const { nodes } = useGLTF('https://raw.githubusercontent.com/Tresjs/assets/main/models/gltf/blender-cube.glb', { draco: true })
@@ -45,8 +46,10 @@ material.colorNode = Fn(() => {
 })()
 
 watch(model, (newModel) => {
-  newModel.traverse((child) => {
-    if (child.isMesh) {
+  const isMesh = (child: Object3D): child is Mesh => 'isMesh' in child && !!(child.isMesh)
+
+  newModel?.traverse((child) => {
+    if (isMesh(child)) {
       child.material = material
     }
   })

+ 1 - 1
docs/app/pages/index.vue

@@ -6,7 +6,7 @@ if (!page.value) {
 
 const title = page.value.seo?.title || page.value.title
 const description = page.value.seo?.description || page.value.description
-const siteName = page.value.seo?.siteName || 'TresJS'
+const siteName = typeof page.value.seo?.siteName === 'string' ? page.value.seo.siteName : 'TresJS'
 
 useSeoMeta({
   titleTemplate: title,

+ 12 - 8
docs/nuxt.config.ts

@@ -1,6 +1,6 @@
-import { templateCompilerOptions } from '@tresjs/core'
 import { existsSync, readFileSync } from 'node:fs'
 import { dirname, resolve } from 'node:path'
+import { templateCompilerOptions } from '@tresjs/core'
 
 function findMonorepoRootPackageJson(startDir: string): string | undefined {
   let dir = startDir
@@ -9,10 +9,14 @@ function findMonorepoRootPackageJson(startDir: string): string | undefined {
     if (existsSync(pkgPath)) {
       const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
       // Check for a field unique to your monorepo root
-      if (pkg.workspaces || pkg.name === '@tresjs/core') { return pkgPath }
+      if (pkg.workspaces || pkg.name === '@tresjs/core') {
+        return pkgPath
+      }
     }
     const parentDir = dirname(dir)
-    if (parentDir === dir) { break }
+    if (parentDir === dir) {
+      break
+    }
     dir = parentDir
   }
   return undefined
@@ -34,11 +38,6 @@ export default defineNuxtConfig({
   devtools: {
     enabled: true,
   },
-  runtimeConfig: {
-    public: {
-      pkgVersion: pkg.version,
-    },
-  },
   css: ['~/assets/css/main.css'],
 
   vue: {
@@ -54,6 +53,11 @@ export default defineNuxtConfig({
       },
     },
   },
+  runtimeConfig: {
+    public: {
+      pkgVersion: pkg.version,
+    },
+  },
 
   future: {
     compatibilityVersion: 4,

+ 1 - 0
package.json

@@ -69,6 +69,7 @@
     "docs:dev": "pnpm --filter='./docs' dev",
     "docs:generate": "pnpm --filter='./docs' generate",
     "docs:prepare": "pnpm --filter='./docs' prepare",
+    "docs:typecheck": "pnpm --filter='./docs' typecheck",
     "postinstall": "pnpm run build && pnpm --filter='./docs' prepare"
   },
   "peerDependencies": {

+ 6 - 2
src/composables/useGraph/index.ts

@@ -3,6 +3,10 @@ import { buildGraph } from '../../utils/graph'
 import { computed, toValue } from 'vue'
 import type { TresObject } from '../../types'
 
-export function useGraph(object: MaybeRef<TresObject>) {
-  return computed(() => buildGraph(toValue(object)))
+export const useGraph = (object: MaybeRef<TresObject | undefined>) => {
+  return computed(() => {
+    const obj = toValue(object)
+    if (!obj) { return undefined }
+    return buildGraph(obj)
+  })
 }

+ 12 - 5
src/utils/graph.ts

@@ -1,5 +1,6 @@
-import type { Mesh, Scene } from 'three'
+import type { Mesh, Object3D, Scene } from 'three'
 import type { TresMaterial, TresObject } from '../types'
+import { isMesh } from './is'
 
 export interface TresObjectMap {
   nodes: { [name: string]: TresObject }
@@ -8,13 +9,19 @@ export interface TresObjectMap {
   scene?: Scene
 }
 
-export function buildGraph(object: TresObject): TresObjectMap {
+export function buildGraph(object: Object3D | TresObject): TresObjectMap {
   const data: TresObjectMap = { nodes: {}, materials: {}, meshes: {} }
   if (object) {
-    object.traverse((obj: any) => {
+    object.traverse((obj: Object3D) => {
       if (obj.name) { data.nodes[obj.name] = obj }
-      if (obj.material && !data.materials[obj.material.name]) { data.materials[obj.material.name] = obj.material }
-      if (obj.isMesh && !data.meshes[obj.name]) { data.meshes[obj.name] = obj }
+
+      if (isMesh(obj)) {
+        if (!data.meshes[obj.name]) { data.meshes[obj.name] = obj }
+
+        (Array.isArray(obj.material) ? obj.material : [obj.material]).forEach((material) => {
+          if (material.name && !data.materials[material.name]) { data.materials[material.name] = material }
+        })
+      }
     })
   }
   return data

+ 5 - 1
src/utils/is.ts

@@ -1,5 +1,5 @@
 import type { TresCamera, TresInstance, TresObject, TresPrimitive } from 'src/types'
-import type { BufferGeometry, Color, ColorRepresentation, Fog, Light, Material, Object3D, OrthographicCamera, PerspectiveCamera, Scene } from 'three'
+import type { BufferGeometry, Color, ColorRepresentation, Fog, Light, Material, Mesh, Object3D, OrthographicCamera, PerspectiveCamera, Scene } from 'three'
 import { Layers } from 'three'
 
 /**
@@ -145,6 +145,10 @@ export function isObject3D(value: unknown): value is Object3D {
   return isObject(value) && !!(value.isObject3D)
 }
 
+export function isMesh(value: unknown): value is Mesh {
+  return isObject3D(value) && 'isMesh' in value && !!(value.isMesh)
+}
+
 /**
  * Type guard to check if a value is a Three.js Camera
  * @param value - The value to check