@vctrl/hooks 0.11.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/GLTFLoader-CygFefRr.cjs +1 -0
  3. package/GLTFLoader-NiJ7qzh1.js +1381 -0
  4. package/README.md +0 -20
  5. package/__vite-browser-external-Bn_csQuM.js +7 -0
  6. package/__vite-browser-external-CVmj8TrW.cjs +1 -0
  7. package/chunk-foFoljWS.js +23 -0
  8. package/chunk-gGpyby2o.cjs +1 -0
  9. package/index.cjs.js +1 -0
  10. package/index.d.ts +5 -0
  11. package/index.es.js +4 -0
  12. package/lib-DKn2MJ9X.js +4111 -0
  13. package/lib-DZ-MihdJ.cjs +2 -0
  14. package/model-exporter-SMSAY90N.cjs +1 -0
  15. package/model-exporter-cvguDGnn.js +6512 -0
  16. package/package.json +4 -4
  17. package/server-communication-Bj6ZxFw2.js +4659 -0
  18. package/server-communication-yXZR8eso.cjs +18 -0
  19. package/use-export-model/use-export-model.d.ts +14 -0
  20. package/use-export-model-CaijtW1c.cjs +1 -0
  21. package/use-export-model-DzVKdqnY.js +49 -0
  22. package/use-export-model.cjs.js +1 -0
  23. package/use-export-model.es.js +2 -0
  24. package/use-load-model/event-system.d.ts +7 -0
  25. package/use-load-model/index.d.ts +4 -0
  26. package/use-load-model/state.d.ts +19 -0
  27. package/use-load-model/types.d.ts +231 -0
  28. package/use-load-model/use-load-model.d.ts +30 -0
  29. package/use-load-model/utils/calculate-referenced-bytes.d.ts +8 -0
  30. package/use-load-model/utils/index.d.ts +4 -0
  31. package/use-load-model/utils/read-directory.d.ts +8 -0
  32. package/use-load-model/utils/reconstruct-files.d.ts +29 -0
  33. package/use-load-model/utils/resolve-scene-payload.d.ts +6 -0
  34. package/use-load-model-B6UXiFWh.js +517 -0
  35. package/use-load-model-CH-WPQ_s.cjs +1 -0
  36. package/use-load-model.cjs.js +1 -0
  37. package/use-load-model.es.js +18 -0
  38. package/{src/use-optimize-model/index.ts → use-optimize-model/index.d.ts} +2 -2
  39. package/use-optimize-model/state.d.ts +13 -0
  40. package/use-optimize-model/types.d.ts +39 -0
  41. package/use-optimize-model/use-calc-optimization-info.d.ts +6 -0
  42. package/use-optimize-model/use-optimize-model.d.ts +119 -0
  43. package/use-optimize-model/utils/index.d.ts +2 -0
  44. package/use-optimize-model/utils/texture-optimization.d.ts +17 -0
  45. package/use-optimize-model/utils/validation.d.ts +8 -0
  46. package/use-optimize-model-C-fzzKh6.cjs +1 -0
  47. package/use-optimize-model-Cb3WMvDZ.js +299 -0
  48. package/use-optimize-model.cjs.js +1 -0
  49. package/use-optimize-model.es.js +2 -0
  50. package/utils/server-communication.d.ts +122 -0
  51. package/project.json +0 -55
  52. package/src/index.ts +0 -38
  53. package/src/use-export-model/use-export-model.ts +0 -113
  54. package/src/use-load-model/event-system.ts +0 -46
  55. package/src/use-load-model/index.ts +0 -15
  56. package/src/use-load-model/model-context.tsx +0 -174
  57. package/src/use-load-model/state.ts +0 -75
  58. package/src/use-load-model/types.ts +0 -284
  59. package/src/use-load-model/use-load-model.ts +0 -682
  60. package/src/use-load-model/utils/calculate-referenced-bytes.ts +0 -123
  61. package/src/use-load-model/utils/index.ts +0 -7
  62. package/src/use-load-model/utils/read-directory.ts +0 -35
  63. package/src/use-load-model/utils/reconstruct-files.ts +0 -85
  64. package/src/use-load-model/utils/resolve-scene-payload.ts +0 -151
  65. package/src/use-optimize-model/state.ts +0 -40
  66. package/src/use-optimize-model/types.ts +0 -37
  67. package/src/use-optimize-model/use-calc-optimization-info.ts +0 -97
  68. package/src/use-optimize-model/use-optimize-model.ts +0 -467
  69. package/src/use-optimize-model/utils/index.ts +0 -29
  70. package/src/use-optimize-model/utils/texture-optimization.ts +0 -292
  71. package/src/use-optimize-model/utils/validation.ts +0 -62
  72. package/src/utils/server-communication.ts +0 -351
  73. package/tsconfig.json +0 -21
  74. package/tsconfig.lib.json +0 -16
  75. package/vite.config.ts +0 -77
  76. /package/{src/use-export-model/index.ts → use-export-model/index.d.ts} +0 -0
@@ -1,123 +0,0 @@
1
- import {
2
- buildAssetLookupKeys,
3
- getSerializedAssetByteSize,
4
- normalizeAssetUri
5
- } from '@vctrl/core'
6
-
7
- import type { ServerSceneData } from '../types'
8
-
9
- type ReferencedBytes = {
10
- sourcePackageBytes: number
11
- textureBytes: number
12
- }
13
-
14
- const resolveSizeFromAssets = (
15
- uri: string,
16
- assets: Array<{ fileName: string; size: number }>
17
- ) => {
18
- const normalizedUri = normalizeAssetUri(uri)
19
- const uriBasename = normalizedUri.split('/').pop() || normalizedUri
20
-
21
- for (const asset of assets) {
22
- const assetKeys = buildAssetLookupKeys(asset.fileName)
23
- if (assetKeys.has(normalizedUri) || assetKeys.has(uriBasename)) {
24
- return asset.size
25
- }
26
- }
27
-
28
- return 0
29
- }
30
-
31
- const collectReferencedUris = (gltfJson: unknown) => {
32
- const bufferUris = new Set<string>()
33
- const imageUris = new Set<string>()
34
- const document = gltfJson as {
35
- images?: Array<{ uri?: string }>
36
- buffers?: Array<{ uri?: string }>
37
- }
38
-
39
- const images = Array.isArray(document.images)
40
- ? (document.images as Array<{ uri?: string }>)
41
- : []
42
- for (const image of images) {
43
- if (typeof image.uri === 'string' && !image.uri.startsWith('data:')) {
44
- imageUris.add(image.uri)
45
- }
46
- }
47
-
48
- const buffers = Array.isArray(document.buffers)
49
- ? (document.buffers as Array<{ uri?: string }>)
50
- : []
51
- for (const buffer of buffers) {
52
- if (typeof buffer.uri === 'string' && !buffer.uri.startsWith('data:')) {
53
- bufferUris.add(buffer.uri)
54
- }
55
- }
56
-
57
- return { bufferUris, imageUris }
58
- }
59
-
60
- export async function calculateReferencedBytesFromFiles(
61
- gltfFile: File,
62
- assetFiles: File[]
63
- ): Promise<ReferencedBytes> {
64
- const gltfText = await gltfFile.text()
65
- let gltfJson: Record<string, unknown>
66
-
67
- try {
68
- gltfJson = JSON.parse(gltfText) as Record<string, unknown>
69
- } catch {
70
- return {
71
- sourcePackageBytes: gltfFile.size,
72
- textureBytes: 0
73
- }
74
- }
75
-
76
- const assets = assetFiles.map((file) => ({
77
- fileName: file.name,
78
- size: file.size
79
- }))
80
-
81
- const { bufferUris, imageUris } = collectReferencedUris(gltfJson)
82
-
83
- const bufferBytes = Array.from(bufferUris).reduce(
84
- (total, uri) => total + resolveSizeFromAssets(uri, assets),
85
- 0
86
- )
87
- const textureBytes = Array.from(imageUris).reduce(
88
- (total, uri) => total + resolveSizeFromAssets(uri, assets),
89
- 0
90
- )
91
-
92
- return {
93
- sourcePackageBytes: gltfFile.size + bufferBytes + textureBytes,
94
- textureBytes
95
- }
96
- }
97
-
98
- export function calculateReferencedBytesFromServerScene(
99
- data: ServerSceneData
100
- ): ReferencedBytes {
101
- const gltfJson = data.gltfJson
102
- const assets = Object.values(data.assetData || {}).map((asset) => ({
103
- fileName: asset.fileName,
104
- size: getSerializedAssetByteSize(asset.data)
105
- }))
106
-
107
- const gltfSize = new TextEncoder().encode(JSON.stringify(gltfJson)).byteLength
108
- const { bufferUris, imageUris } = collectReferencedUris(gltfJson)
109
-
110
- const bufferBytes = Array.from(bufferUris).reduce(
111
- (total, uri) => total + resolveSizeFromAssets(uri, assets),
112
- 0
113
- )
114
- const textureBytes = Array.from(imageUris).reduce(
115
- (total, uri) => total + resolveSizeFromAssets(uri, assets),
116
- 0
117
- )
118
-
119
- return {
120
- sourcePackageBytes: gltfSize + bufferBytes + textureBytes,
121
- textureBytes
122
- }
123
- }
@@ -1,7 +0,0 @@
1
- export { default as readDirectory } from './read-directory'
2
- export { reconstructGltfFiles } from './reconstruct-files'
3
- export {
4
- calculateReferencedBytesFromFiles,
5
- calculateReferencedBytesFromServerScene
6
- } from './calculate-referenced-bytes'
7
- export { resolveServerSceneDataContract } from './resolve-scene-payload'
@@ -1,35 +0,0 @@
1
- /**
2
- * Recursively reads a directory
3
- *
4
- * @param directoryHandle - The directory handle to read
5
- * @returns - An array of file objects
6
- */
7
- async function readDirectory(
8
- directoryHandle: FileSystemDirectoryHandle
9
- ): Promise<File[]> {
10
- const files: File[] = []
11
-
12
- async function* getFilesRecursively(
13
- entry: FileSystemDirectoryHandle
14
- ): AsyncGenerator<File> {
15
- const asyncEntries = entry as unknown as AsyncIterable<
16
- [string, FileSystemHandle]
17
- >
18
-
19
- for await (const [, handle] of asyncEntries) {
20
- if (handle.kind === 'file') {
21
- yield await (handle as FileSystemFileHandle).getFile()
22
- } else if (handle.kind === 'directory') {
23
- yield* getFilesRecursively(handle as FileSystemDirectoryHandle)
24
- }
25
- }
26
- }
27
-
28
- for await (const file of getFilesRecursively(directoryHandle)) {
29
- files.push(file)
30
- }
31
-
32
- return files
33
- }
34
-
35
- export default readDirectory
@@ -1,85 +0,0 @@
1
- /* vectreal-core | vctrl/hooks
2
- Copyright (C) 2024 Moritz Becker
3
-
4
- This program is free software: you can redistribute it and/or modify
5
- it under the terms of the GNU Affero General Public License as published by
6
- the Free Software Foundation, either version 3 of the License, or
7
- (at your option) any later version.
8
-
9
- This program is distributed in the hope that it will be useful,
10
- but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- GNU Affero General Public License for more details.
13
-
14
- You should have received a copy of the GNU Affero General Public License
15
- along with this program. If not, see <http://www.gnu.org/licenses/>. */
16
-
17
- import { toSerializedAssetBytes } from '@vctrl/core'
18
-
19
- import type { InputFileOrDirectory, ServerSceneData } from '../types'
20
-
21
- /**
22
- * Reconstructs GLTF and asset files from scene data received from the server.
23
- *
24
- * This function converts the server's scene data format (GLTF JSON + binary assets)
25
- * back into File objects that can be loaded by the model loader. This is the reverse
26
- * operation of what happens when a scene is saved to the server.
27
- *
28
- * The function:
29
- * 1. Converts binary asset data (stored as number arrays) back into File objects
30
- * 2. Creates a GLTF file from the JSON structure
31
- * 3. Returns an array of files compatible with the model loading system
32
- *
33
- * @param data - Scene data from the server containing GLTF JSON and assets
34
- * @returns Array of File objects ready to be loaded (GLTF file + asset files)
35
- *
36
- * @example
37
- * ```typescript
38
- * // Load scene data from server
39
- * const sceneData = await ServerCommunicationService.loadScene('scene-123')
40
- *
41
- * // Reconstruct files
42
- * const files = reconstructGltfFiles(sceneData)
43
- *
44
- * // Load into the viewer
45
- * await modelLoader.load(files)
46
- * ```
47
- */
48
- export function reconstructGltfFiles(
49
- data: ServerSceneData
50
- ): InputFileOrDirectory {
51
- const assetFiles: File[] = []
52
-
53
- // Convert asset data back to File objects
54
- if (data.assetData && typeof data.assetData === 'object') {
55
- for (const [, assetInfo] of Object.entries(data.assetData)) {
56
- const { fileName, mimeType } = assetInfo
57
- const uint8Array = toSerializedAssetBytes(assetInfo)
58
-
59
- // Create a Blob from the binary data
60
- const blobBytes = new Uint8Array(uint8Array.byteLength)
61
- blobBytes.set(uint8Array)
62
- const blob = new Blob([blobBytes], { type: mimeType })
63
-
64
- // Create a File object
65
- const file = new File([blob], fileName, { type: mimeType })
66
-
67
- assetFiles.push(file)
68
- }
69
- }
70
-
71
- // Create GLTF file from JSON structure
72
- const gltfJsonString = JSON.stringify(data.gltfJson)
73
- const gltfBlob = new Blob([gltfJsonString], { type: 'model/gltf+json' })
74
-
75
- // Determine filename from metadata or use default
76
- const sceneName = data.meta?.name
77
- const gltfFileName = sceneName ? `${sceneName}.gltf` : 'scene.gltf'
78
-
79
- const gltfFile = new File([gltfBlob], gltfFileName, {
80
- type: 'model/gltf+json'
81
- })
82
-
83
- // Return GLTF file first, followed by all asset files
84
- return [gltfFile, ...assetFiles]
85
- }
@@ -1,151 +0,0 @@
1
- import {
2
- buildAssetLookupKeys,
3
- isValidBase64,
4
- normalizeAssetUri
5
- } from '@vctrl/core'
6
-
7
- import type {
8
- SceneSettings,
9
- SerializedSceneAssetDataMap,
10
- ServerSceneData,
11
- ServerScenePayload
12
- } from '@vctrl/core'
13
-
14
- function isRecord(value: unknown): value is Record<string, unknown> {
15
- return typeof value === 'object' && value !== null && !Array.isArray(value)
16
- }
17
-
18
- function toSceneSettings(payload: ServerScenePayload): SceneSettings {
19
- const nestedSettings = isRecord(payload.settings)
20
- ? (payload.settings as SceneSettings)
21
- : {}
22
-
23
- return {
24
- ...nestedSettings,
25
- bounds: payload.bounds ?? nestedSettings.bounds,
26
- camera: payload.camera ?? nestedSettings.camera,
27
- controls: payload.controls ?? nestedSettings.controls,
28
- environment: payload.environment ?? nestedSettings.environment,
29
- shadows: payload.shadows ?? nestedSettings.shadows
30
- }
31
- }
32
-
33
- function collectReferencedUris(gltfJson: unknown): Set<string> {
34
- const referencedUris = new Set<string>()
35
- const document = gltfJson as {
36
- images?: Array<{ uri?: string }>
37
- buffers?: Array<{ uri?: string }>
38
- }
39
-
40
- const images = Array.isArray(document.images)
41
- ? (document.images as Array<{ uri?: string }>)
42
- : []
43
- for (const image of images) {
44
- if (typeof image.uri === 'string' && !image.uri.startsWith('data:')) {
45
- referencedUris.add(image.uri)
46
- }
47
- }
48
-
49
- const buffers = Array.isArray(document.buffers)
50
- ? (document.buffers as Array<{ uri?: string }>)
51
- : []
52
- for (const buffer of buffers) {
53
- if (typeof buffer.uri === 'string' && !buffer.uri.startsWith('data:')) {
54
- referencedUris.add(buffer.uri)
55
- }
56
- }
57
-
58
- return referencedUris
59
- }
60
-
61
- function validateAssetDataMap(assetData: SerializedSceneAssetDataMap): void {
62
- for (const [assetId, asset] of Object.entries(assetData)) {
63
- if (!asset || typeof asset !== 'object') {
64
- throw new Error(
65
- `Scene payload contains invalid asset entry for ${assetId}`
66
- )
67
- }
68
-
69
- if (
70
- typeof asset.fileName !== 'string' ||
71
- asset.fileName.trim().length === 0
72
- ) {
73
- throw new Error(
74
- `Scene payload asset ${assetId} is missing a valid fileName`
75
- )
76
- }
77
-
78
- if (
79
- typeof asset.mimeType !== 'string' ||
80
- asset.mimeType.trim().length === 0
81
- ) {
82
- throw new Error(
83
- `Scene payload asset ${assetId} is missing a valid mimeType`
84
- )
85
- }
86
-
87
- if (typeof asset.data === 'string' && asset.encoding === 'base64') {
88
- if (!isValidBase64(asset.data)) {
89
- throw new Error(
90
- `Scene payload asset ${asset.fileName} has invalid base64 data`
91
- )
92
- }
93
- }
94
- }
95
- }
96
-
97
- function validateReferencedAssets(
98
- gltfJson: ServerSceneData['gltfJson'],
99
- assetData: SerializedSceneAssetDataMap
100
- ): void {
101
- const referencedUris = collectReferencedUris(gltfJson)
102
-
103
- if (referencedUris.size === 0) {
104
- return
105
- }
106
-
107
- const lookup = new Set<string>()
108
- for (const asset of Object.values(assetData)) {
109
- for (const key of buildAssetLookupKeys(asset.fileName)) {
110
- lookup.add(key)
111
- }
112
- }
113
-
114
- const missingUris: string[] = []
115
- for (const uri of referencedUris) {
116
- const normalized = normalizeAssetUri(uri)
117
- const basename = normalized.split('/').pop() || normalized
118
- if (!lookup.has(uri) && !lookup.has(normalized) && !lookup.has(basename)) {
119
- missingUris.push(uri)
120
- }
121
- }
122
-
123
- if (missingUris.length > 0) {
124
- throw new Error(
125
- `Scene payload is missing required referenced assets: ${missingUris.slice(0, 5).join(', ')}`
126
- )
127
- }
128
- }
129
-
130
- /**
131
- * Resolves raw scene payload from the API into normalized scene data used by hooks.
132
- * This keeps payload-shape adaptation in one explicit contract boundary.
133
- */
134
- export function resolveServerSceneDataContract(
135
- payload: ServerScenePayload
136
- ): ServerSceneData {
137
- if (!isRecord(payload.gltfJson)) {
138
- throw new Error('Scene payload is missing a valid glTF document')
139
- }
140
-
141
- const assetData = payload.assetData ?? {}
142
- validateAssetDataMap(assetData)
143
- validateReferencedAssets(payload.gltfJson, assetData)
144
-
145
- return {
146
- meta: payload.meta,
147
- gltfJson: payload.gltfJson,
148
- assetData,
149
- ...toSceneSettings(payload)
150
- }
151
- }
@@ -1,40 +0,0 @@
1
- import { Action, OptimizationState } from './types'
2
-
3
- /**
4
- * Initial state for the reducer.
5
- */
6
- export const initialState: OptimizationState = {
7
- info: null,
8
- error: null,
9
- loading: false,
10
- report: null
11
- }
12
-
13
- /**
14
- * Reducer function to manage state transitions.
15
- *
16
- * @param state - Current state.
17
- * @param action - Action to perform.
18
- * @returns New state after applying the action.
19
- */
20
- export const reducer = (
21
- state: OptimizationState,
22
- action: Action
23
- ): OptimizationState => {
24
- switch (action.type) {
25
- case 'LOAD_START':
26
- return { ...state, loading: true, error: null }
27
- case 'LOAD_SUCCESS':
28
- return {
29
- ...state,
30
- loading: false,
31
- report: action.payload.report
32
- }
33
- case 'LOAD_ERROR':
34
- return { ...state, loading: false, error: action.payload }
35
- case 'RESET':
36
- return { ...initialState }
37
- default:
38
- return state
39
- }
40
- }
@@ -1,37 +0,0 @@
1
- import type { OptimizationReport } from '@vctrl/core/model-optimizer'
2
-
3
- interface ModelTotals {
4
- verticesCount: number
5
- primitivesCount: number
6
- texturesCount: number
7
- meshesCount: number
8
- sceneBytes: number
9
- }
10
-
11
- export interface OptimizationInfo {
12
- initial: ModelTotals
13
- optimized: ModelTotals
14
- improvement: ModelTotals
15
- }
16
-
17
- /**
18
- * Interface representing the state of the model optimizer.
19
- */
20
- export interface OptimizationState {
21
- report: OptimizationReport | null
22
- info: OptimizationInfo | null
23
- error: Error | null
24
- loading: boolean
25
- }
26
-
27
- /**
28
- * Types of actions for the reducer.
29
- */
30
- export type Action =
31
- | { type: 'LOAD_START' }
32
- | {
33
- type: 'LOAD_SUCCESS'
34
- payload: { report: OptimizationReport }
35
- }
36
- | { type: 'LOAD_ERROR'; payload: Error }
37
- | { type: 'RESET' }
@@ -1,97 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
-
3
- import { OptimizationInfo, OptimizationState } from './types'
4
-
5
- import type { OptimizationReport } from '@vctrl/core/model-optimizer'
6
-
7
- /**
8
- * Helper function to safely get stats from optimization report.
9
- */
10
- function getStatsFromReport(report: OptimizationReport | null): {
11
- vertices: number
12
- primitives: number
13
- meshes: number
14
- textures: number
15
- materials: number
16
- totalSize: number
17
- } {
18
- if (!report) {
19
- return {
20
- vertices: 0,
21
- primitives: 0,
22
- meshes: 0,
23
- textures: 0,
24
- materials: 0,
25
- totalSize: 0
26
- }
27
- }
28
-
29
- return {
30
- vertices: report.stats.vertices.after || 0,
31
- primitives: report.stats.triangles.after || 0,
32
- meshes: report.stats.meshes.after || 0,
33
- textures: report.stats.textures.after || 0,
34
- materials: report.stats.materials.after || 0,
35
- totalSize: report.optimizedSize || 0
36
- }
37
- }
38
-
39
- export interface OptimizationInfoData {
40
- info: OptimizationInfo
41
- reset: () => void
42
- }
43
-
44
- export const useCalcOptimizationInfo = (
45
- state: OptimizationState
46
- ): OptimizationInfoData => {
47
- const initialReport = useRef<OptimizationReport | null>(null)
48
- const [initialCaptured, setInitialCaptured] = useState<boolean>(false)
49
- const report = state.report
50
-
51
- // Capture initial state
52
- useEffect(() => {
53
- if (!initialCaptured && report) {
54
- initialReport.current = report
55
- setInitialCaptured(true)
56
- }
57
- }, [initialCaptured, report])
58
-
59
- const info = useMemo(() => {
60
- const initial = getStatsFromReport(initialReport.current)
61
- const current = getStatsFromReport(report)
62
-
63
- return {
64
- initial: {
65
- verticesCount: initial.vertices,
66
- primitivesCount: initial.primitives,
67
- meshesCount: initial.meshes,
68
- texturesCount: initial.textures,
69
- sceneBytes: initial.totalSize
70
- },
71
- optimized: {
72
- verticesCount: current.vertices,
73
- primitivesCount: current.primitives,
74
- meshesCount: current.meshes,
75
- texturesCount: current.textures,
76
- sceneBytes: current.totalSize
77
- },
78
- improvement: {
79
- verticesCount: initial.vertices - current.vertices,
80
- primitivesCount: initial.primitives - current.primitives,
81
- meshesCount: initial.meshes - current.meshes,
82
- texturesCount: initial.textures - current.textures,
83
- sceneBytes: initial.totalSize - current.totalSize
84
- }
85
- }
86
- }, [report])
87
-
88
- const reset = useCallback(() => {
89
- initialReport.current = null
90
- setInitialCaptured(false)
91
- }, [])
92
-
93
- return {
94
- info,
95
- reset
96
- }
97
- }