Browse Source

Merge branch 'main' into 426-groups-emit-children-events

Alvaro Saburido 1 year ago
parent
commit
db30ac7bc4

+ 25 - 1
CHANGELOG.md

@@ -1,5 +1,29 @@
 
 
+## [3.6.0](https://github.com/Tresjs/tres/compare/3.5.2...3.6.0) (2023-12-12)
+
+
+### Features
+
+* enable devtools ([#465](https://github.com/Tresjs/tres/issues/465)) ([d61df05](https://github.com/Tresjs/tres/commit/d61df0597965e708581d1b32eafa2f14866bf9b4))
+
+## [3.6.0-next.0](https://github.com/Tresjs/tres/compare/3.5.1...3.6.0-next.0) (2023-12-06)
+
+
+### Features
+
+* devtools hook on window object ([df96c1f](https://github.com/Tresjs/tres/commit/df96c1f31aafe1b73cd344fde39420c1ae443d47))
+  
+
+## [3.5.2](https://github.com/Tresjs/tres/compare/3.5.1...3.5.2) (2023-12-12)
+
+
+### Bug Fixes
+
+* force memory free allocation when disposing materials and geometries ([#463](https://github.com/Tresjs/tres/issues/463)) ([1ef3533](https://github.com/Tresjs/tres/commit/1ef353363d02256275c483ec15ad424dc058991a))
+
+## [3.5.1](https://github.com/Tresjs/tres/compare/3.5.0...3.5.1) (2023-11-28)
+
 ## [3.5.0](https://github.com/Tresjs/tres/compare/3.4.1...3.5.0) (2023-11-05)
 
 
@@ -473,4 +497,4 @@
 - **core:** added handleHMR update ([594ab73](https://github.com/Tresjs/tres/commit/594ab738f60c6d1d4e6a9ac0339bb8a3c0d44eb8))
 - **core:** fixed type issues ([bd4be33](https://github.com/Tresjs/tres/commit/bd4be33ab77372307ef59b0ff3bc8989fb40151f))
 - **core:** nodeOps no op ([9044c99](https://github.com/Tresjs/tres/commit/9044c99878f312c6e4c120e9eeee61ea675754e8))
-- useTexture to show indentation for code snippet ([e983c5d](https://github.com/Tresjs/tres/commit/e983c5d945fe2d72083edf03cf08152fe517cbe9))
+- useTexture to show indentation for code snippet ([e983c5d](https://github.com/Tresjs/tres/commit/e983c5d945fe2d72083edf03cf08152fe517cbe9))

+ 5 - 5
README.md

@@ -39,11 +39,11 @@ Checkout the [docs](https://tresjs.org)
 
 | Package                     | Version                                                                                            |
 | --------------------------- | :------------------------------------------------------------------------------------------------- |
-| [Tres](packages/tres)       | ![tres version](https://img.shields.io/npm/v/@tresjs/core/latest.svg?label=%20&color=%2382DBCA)    |
-| [Cientos](packages/cientos) | ![cientos version](https://img.shields.io/npm/v/@tresjs/cientos/latest.svg?label=%20&color=%23f19b00) |
-| [Post-processing](packages/post-processing) | ![post-processing version](https://img.shields.io/npm/v/@tresjs/post-processing/latest.svg?label=%20&color=ff7bac) |
-| [Nuxt](packages/nuxt) | ![nuxt version](https://img.shields.io/npm/v/@tresjs/nuxt/latest.svg?label=%20&color=4f4f4f&logo=nuxt.js) |
-| [TresLeches 🍰](packages/leches) | ![tresleches version](https://img.shields.io/npm/v/@tresjs/leches/latest.svg?label=%20&color=ffffff) |
+| [Tres](https://github.com/TresJS/tres)       | ![tres version](https://img.shields.io/npm/v/@tresjs/core/latest.svg?label=%20&color=%2382DBCA)    |
+| [Cientos](https://github.com/TresJS/cientos) | ![cientos version](https://img.shields.io/npm/v/@tresjs/cientos/latest.svg?label=%20&color=%23f19b00) |
+| [Post-processing](https://github.com/TresJS/post-processing) | ![post-processing version](https://img.shields.io/npm/v/@tresjs/post-processing/latest.svg?label=%20&color=ff7bac) |
+| [Nuxt](https://github.com/TresJS/nuxt) | ![nuxt version](https://img.shields.io/npm/v/@tresjs/nuxt/latest.svg?label=%20&color=4f4f4f&logo=nuxt.js) |
+| [TresLeches 🍰](https://github.com/TresJS/leches) | ![tresleches version](https://img.shields.io/npm/v/@tresjs/leches/latest.svg?label=%20&color=ffffff) |
 
 ## Contribution
 

+ 4 - 4
docs/package.json

@@ -8,11 +8,11 @@
     "build": "vitepress build",
     "preview": "vitepress preview"
   },
-  "dependencies": {
-    "@tresjs/core": "workspace:^3.1.1"
-  },
   "devDependencies": {
-    "unocss": "^0.57.7",
+    "unocss": "^0.58.0",
     "vite-svg-loader": "^5.1.0"
+  },
+  "dependencies": {
+    "@tresjs/core": "workspace:3.6.0-next.0"
   }
 }

+ 22 - 22
package.json

@@ -1,7 +1,7 @@
 {
   "name": "@tresjs/core",
   "type": "module",
-  "version": "3.5.0",
+  "version": "3.6.0",
   "packageManager": "pnpm@8.10.2",
   "description": "Declarative ThreeJS using Vue Components",
   "author": "Alvaro Saburido <hola@alvarosaburido.dev> (https://github.com/alvarosabu/)",
@@ -66,45 +66,45 @@
   },
   "dependencies": {
     "@alvarosabu/utils": "^3.1.1",
-    "@vueuse/core": "^10.5.0"
+    "@vueuse/core": "^10.7.0"
   },
   "devDependencies": {
     "@release-it/conventional-changelog": "^8.0.1",
     "@stackblitz/sdk": "^1.9.0",
     "@tresjs/cientos": "3.6.0",
     "@tresjs/eslint-config-vue": "^0.2.1",
-    "@types/three": "^0.158.3",
-    "@typescript-eslint/eslint-plugin": "^6.13.0",
-    "@typescript-eslint/parser": "^6.13.0",
-    "@vitejs/plugin-vue": "^4.5.0",
+    "@types/three": "^0.159.0",
+    "@typescript-eslint/eslint-plugin": "^6.14.0",
+    "@typescript-eslint/parser": "^6.14.0",
+    "@vitejs/plugin-vue": "^4.5.2",
     "@vitest/coverage-c8": "^0.33.0",
-    "@vitest/ui": "^0.34.6",
-    "@vue/test-utils": "^2.4.2",
-    "eslint": "^8.54.0",
-    "eslint-plugin-vue": "^9.18.1",
+    "@vitest/ui": "^1.0.4",
+    "@vue/test-utils": "^2.4.3",
+    "eslint": "^8.55.0",
+    "eslint-plugin-vue": "^9.19.2",
     "esno": "^4.0.0",
-    "gsap": "^3.12.2",
-    "jsdom": "^23.0.0",
+    "gsap": "^3.12.3",
+    "jsdom": "^23.0.1",
     "kolorist": "^1.8.0",
     "ohmyfetch": "^0.4.21",
     "pathe": "^1.1.1",
-    "release-it": "^17.0.0",
+    "release-it": "^17.0.1",
     "rollup-plugin-analyzer": "^4.0.0",
     "rollup-plugin-copy": "^3.5.0",
-    "rollup-plugin-visualizer": "^5.9.3",
-    "three": "^0.158.0",
-    "unocss": "^0.57.7",
+    "rollup-plugin-visualizer": "^5.11.0",
+    "three": "^0.159.0",
+    "unocss": "^0.58.0",
     "unplugin": "^1.5.1",
-    "unplugin-vue-components": "^0.25.2",
-    "vite": "^5.0.2",
+    "unplugin-vue-components": "^0.26.0",
+    "vite": "^5.0.8",
     "vite-plugin-banner": "^0.7.1",
-    "vite-plugin-dts": "3.6.3",
-    "vite-plugin-inspect": "^0.8.0",
+    "vite-plugin-dts": "3.6.4",
+    "vite-plugin-inspect": "^0.8.1",
     "vite-plugin-require-transform": "^1.0.21",
     "vite-svg-loader": "^5.1.0",
     "vitepress": "1.0.0-rc.31",
-    "vitest": "^0.34.6",
-    "vue": "^3.3.9",
+    "vitest": "^1.0.4",
+    "vue": "^3.3.11",
     "vue-demi": "^0.14.6"
   }
 }

+ 6 - 5
playground/package.json

@@ -13,11 +13,12 @@
     "vue-router": "^4.2.5"
   },
   "devDependencies": {
-    "@tresjs/leches": "^0.13.0",
+    "@tresjs/leches": "0.15.0-next.3",
     "@tweakpane/plugin-essentials": "^0.2.0",
-    "unplugin-auto-import": "^0.17.1",
-    "vite-plugin-glsl": "^1.2.0",
-    "vite-plugin-qrcode": "^0.2.2",
-    "vue-tsc": "^1.8.22"
+    "vite-plugin-vue-devtools": "1.0.0-rc.6",
+    "unplugin-auto-import": "^0.17.2",
+    "vite-plugin-glsl": "^1.2.1",
+    "vite-plugin-qrcode": "^0.2.3",
+    "vue-tsc": "^1.8.25"
   }
 }

+ 7 - 0
playground/src/pages/empty.vue

@@ -0,0 +1,7 @@
+<script setup>
+
+</script>
+
+<template>
+  <div />
+</template>

+ 49 - 0
playground/src/pages/perf/AkuAku.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+import { useGLTF } from '@tresjs/cientos'
+import { useControls } from '@tresjs/leches'
+
+const { nodes } = await useGLTF(
+  'https://raw.githubusercontent.com/Tresjs/assets/main/models/gltf/aku-aku/AkuAku.gltf',
+  { draco: true },
+)
+
+const model = nodes.AkuAku
+
+/* useControls({
+  button: {
+    label: 'Manual dispose',
+    type: 'button',
+    onClick() {
+      disposeModel()
+    },
+  },
+}) */
+
+function disposeModel() {
+  console.log('disposingModel')
+  model.traverse((child) => {
+    if (child.isMesh) {
+      // Dispose of the material
+      if (child.material) {
+        child.material.dispose()
+      }
+        
+      // Dispose of the geometry
+      if (child.geometry) {
+        child.geometry.dispose()
+      }
+    }
+  })
+  console.log('disposingModel Finished')
+}
+
+model.traverse((child) => {
+  if (child.material) {
+    console.log('child.material', child.material.uuid)
+  }
+})
+</script>
+
+<template>
+  <primitive :object="model" />
+</template>

+ 65 - 0
playground/src/pages/perf/index.vue

@@ -0,0 +1,65 @@
+<script setup lang="ts">
+import { TresCanvas } from '@tresjs/core'
+import { BasicShadowMap, SRGBColorSpace, NoToneMapping } from 'three'
+import { TresLeches, Perf, useControls } from '@tresjs/leches'
+import '@tresjs/leches/styles'
+import { OrbitControls } from '@tresjs/cientos'
+import { useRouter } from 'vue-router'
+import AkuAku from './AkuAku.vue'
+
+const gl = {
+  clearColor: '#82DBC5',
+  shadows: true,
+  alpha: false,
+  shadowMapType: BasicShadowMap,
+  outputColorSpace: SRGBColorSpace,
+  toneMapping: NoToneMapping,
+}
+
+const router = useRouter()
+
+const { sphere } = useControls({
+  sphere: true,
+})
+
+const ctx = ref(null)
+
+watchEffect(() => {
+  if (!ctx.value) return
+  console.log('ctx', ctx.value)
+})
+
+useControls({
+  button: {
+    label: 'Render dispose',
+    type: 'button',
+    onClick() {
+      ctx.value.dispose()
+    },
+  },
+
+})
+</script>
+
+<template>
+  <TresLeches />
+  <TresCanvas
+    v-bind="gl"
+    ref="ctx"
+  >
+    <Perf />
+    <TresPerspectiveCamera :position="[3, 3, 3]" />
+    <OrbitControls />
+    <Suspense> 
+      <AkuAku v-if="sphere" />
+    </Suspense>
+    <!--  <TresMesh
+      v-if="sphere.value"
+      :position="[0, 0, 0]"
+    >
+      <TresSphereGeometry />
+      <TresMeshStandardMaterial color="teal" />
+    </TresMesh> -->
+    <TresAmbientLight :intensity="1" />
+  </TresCanvas>
+</template>

+ 10 - 0
playground/src/router.ts

@@ -71,6 +71,16 @@ const routes = [
     name: 'Click Blocking Box',
     component: () => import('./pages/click-blocking-box.vue'),
   },
+  {
+    path: '/perf',
+    name: 'Perf',
+    component: () => import('./pages/perf/index.vue'),
+  },
+  {
+    path: '/empty',
+    name: 'empty',
+    component: () => import('./pages/empty.vue'),
+  },
 ]
 export const router = createRouter({
   history: createWebHistory(),

+ 2 - 0
playground/vite.config.ts

@@ -7,11 +7,13 @@ import glsl from 'vite-plugin-glsl'
 import UnoCSS from 'unocss/vite'
 import { templateCompilerOptions } from '@tresjs/core'
 import { qrcode } from 'vite-plugin-qrcode'
+import VueDevTools from 'vite-plugin-vue-devtools'
 
 // https://vitejs.dev/config/
 export default defineConfig({
   plugins: [
     glsl(),
+    VueDevTools(),
     vue({
       script: {
         propsDestructure: true,

File diff suppressed because it is too large
+ 285 - 200
pnpm-lock.yaml


+ 7 - 2
src/components/TresCanvas.vue

@@ -101,8 +101,13 @@ const mountCustomRenderer = (context: TresContext) => {
   render(h(InternalComponent), scene.value as unknown as TresObject)
 }
 
-const dispose = (context: TresContext) => {
+const dispose = (context: TresContext, force = false) => {
   scene.value.children = []
+  if (force) {
+    context.renderer.value.dispose()
+    context.renderer.value.renderLists.dispose()
+    context.renderer.value.forceContextLoss()
+  }
   mountCustomRenderer(context)
   resume()
 }
@@ -111,7 +116,7 @@ const disableRender = computed(() => props.disableRender)
 
 const context = shallowRef<TresContext | null>(null)
 
-defineExpose({ context })
+defineExpose({ context, dispose: () => dispose(context.value as TresContext, true) })
 
 onMounted(() => {
   const existingCanvas = canvas as Ref<HTMLCanvasElement>

+ 95 - 2
src/composables/useTresContextProvider/index.ts

@@ -1,8 +1,9 @@
-import { toValue, useElementSize, useWindowSize } from '@vueuse/core'
-import { inject, provide, readonly, shallowRef, computed, ref } from 'vue'
+import { toValue, useElementSize, useFps, useMemory, useRafFn, useWindowSize } from '@vueuse/core'
+import { inject, provide, readonly, shallowRef, computed, ref, onUnmounted } from 'vue'
 import type { Camera, EventDispatcher, Scene, WebGLRenderer } from 'three'
 import { Raycaster } from 'three'
 import type { ComputedRef, DeepReadonly, MaybeRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
+import { calculateMemoryUsage } from '../../utils/perf'
 import { useCamera } from '../useCamera'
 import type { UseRendererOptions } from '../useRenderer'
 import { useRenderer } from '../useRenderer'
@@ -17,6 +18,18 @@ export interface TresContext {
   controls: Ref<(EventDispatcher & { enabled: boolean }) | null>
   renderer: ShallowRef<WebGLRenderer>
   raycaster: ShallowRef<Raycaster>
+  perf: {
+    maxFrames: number
+    fps: {
+      value: number
+      accumulator: number[]
+    }
+    memory: {
+      currentMem: number
+      allocatedMem: number
+      accumulator: number[]
+    }
+  }
   registerCamera: (camera: Camera) => void
   setCameraActive: (cameraOrUuid: Camera | string) => void
   deregisterCamera: (camera: Camera) => void
@@ -78,6 +91,18 @@ export function useTresContextProvider({
     renderer,
     raycaster: shallowRef(new Raycaster()),
     controls: ref(null),
+    perf: {
+      maxFrames: 160,
+      fps: {
+        value: 0,
+        accumulator: [],
+      },
+      memory: {
+        currentMem: 0,
+        allocatedMem: 0,
+        accumulator: [],
+      },
+    },
     extend,
     registerCamera,
     setCameraActive,
@@ -86,6 +111,74 @@ export function useTresContextProvider({
 
   provide('useTres', toProvide)
 
+  // Performance
+  const updateInterval = 100 // Update interval in milliseconds
+  const fps = useFps({ every: updateInterval }) 
+  const { isSupported, memory } = useMemory({ interval: updateInterval })
+  const maxFrames = 160
+  let lastUpdateTime = performance.now()
+
+  const updatePerformanceData = ({ timestamp }: { timestamp: number }) => {
+
+    // Update WebGL Memory Usage (Placeholder for actual logic)
+    // perf.memory.value = calculateMemoryUsage(gl)
+    if (toProvide.scene.value) {
+      toProvide.perf.memory.allocatedMem = calculateMemoryUsage(toProvide.scene.value as unknown as TresObject)
+    }
+    
+    // Update memory usage
+    if (timestamp - lastUpdateTime >= updateInterval) {
+      lastUpdateTime = timestamp
+
+      // Update FPS
+      toProvide.perf.fps.accumulator.push(fps.value as never)
+
+      if (toProvide.perf.fps.accumulator.length > maxFrames) {
+        toProvide.perf.fps.accumulator.shift()
+      }
+
+      toProvide.perf.fps.value = fps.value
+
+      // Update memory
+      if (isSupported.value && memory.value) {
+        toProvide.perf.memory.accumulator.push(memory.value.usedJSHeapSize / 1024 / 1024 as never)
+
+        if (toProvide.perf.memory.accumulator.length > maxFrames) {
+          toProvide.perf.memory.accumulator.shift()
+        }
+
+        toProvide.perf.memory.currentMem 
+        = toProvide.perf.memory.accumulator.reduce((a, b) => a + b, 0) / toProvide.perf.memory.accumulator.length
+        
+      }
+    }
+  }
+
+  // Devtools
+  let accumulatedTime = 0
+  const interval = 1 // Interval in milliseconds, e.g., 1000 ms = 1 second
+    
+  const { pause, resume } = useRafFn(({ delta }) => {
+    if (!window.__TRES__DEVTOOLS__) return
+
+    updatePerformanceData({ timestamp: performance.now() })
+    
+    // Accumulate the delta time
+    accumulatedTime += delta
+    
+    // Check if the accumulated time is greater than or equal to the interval
+    if (accumulatedTime >= interval) {
+      window.__TRES__DEVTOOLS__.cb(toProvide)
+    
+      // Reset the accumulated time
+      accumulatedTime = 0
+    }
+  }, { immediate: true }) 
+  
+  onUnmounted(() => {
+    pause()
+  })
+
   return toProvide
 }
 

+ 24 - 15
src/core/nodeOps.ts

@@ -16,6 +16,13 @@ let scene: TresScene | null = null
 
 const { logError } = useLogger()
 
+const supportedPointerEvents = [
+  'onClick',
+  'onPointerMove',
+  'onPointerEnter',
+  'onPointerLeave',
+]
+
 export const nodeOps: RendererOptions<TresObject, TresObject> = {
   createElement(tag, _isSVG, _anchor, props) {
     if (!props) props = {}
@@ -75,7 +82,7 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
   },
   insert(child, parent) {
     if (parent && parent.isScene) scene = parent as unknown as TresScene
-    
+
     const parentObject = parent || scene
 
     if (child?.isObject3D) {
@@ -87,10 +94,7 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
       }
 
       if (
-        child?.onClick
-        || child?.onPointerMove
-        || child?.onPointerEnter
-        || child?.onPointerLeave
+        child && supportedPointerEvents.some(eventName => child[eventName])
       ) {
         if (!scene?.userData.tres__registerAtPointerEventHandler)
           throw 'could not find tres__registerAtPointerEventHandler on scene\'s userData'
@@ -123,9 +127,15 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
       const disposeMaterialsAndGeometries = (object3D: Object3D) => {
         const tresObject3D = object3D as TresObject3D
 
-        if (!object3D.userData.tres__materialViaProp) tresObject3D.material?.dispose()
-        if (!object3D.userData.tres__geometryViaProp)
+        if (!object3D.userData.tres__materialViaProp) {
+          tresObject3D.material?.dispose()
+          tresObject3D.material = undefined
+        }
+
+        if (!object3D.userData.tres__geometryViaProp) {
           tresObject3D.geometry?.dispose()
+          tresObject3D.geometry = undefined
+        }
       }
 
       const deregisterAtPointerEventHandler = scene?.userData.tres__deregisterAtPointerEventHandler
@@ -143,10 +153,7 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
           throw 'could not find tres__deregisterAtPointerEventHandler on scene\'s userData'
 
         if (
-          object?.onClick
-          || object?.onPointerMove
-          || object?.onPointerEnter
-          || object?.onPointerLeave
+          object && supportedPointerEvents.some(eventName => object[eventName])
         )
           deregisterAtPointerEventHandler?.(object as Object3D)
       }
@@ -161,6 +168,7 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
           deregisterCamera?.(object as Camera)
       }
 
+      node.removeFromParent?.()
       object3D.traverse((child: Object3D) => {
         disposeMaterialsAndGeometries(child)
         deregisterCameraIfRequired(child)
@@ -172,8 +180,6 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
       deregisterAtPointerEventHandlerIfRequired?.(object3D as TresObject)
     }
 
-    node.removeFromParent?.()
-
     node.dispose?.()
   },
   patchProp(node, prop, _prevValue, nextValue) {
@@ -225,8 +231,11 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
       if (value === '') value = true
       // Set prop, prefer atomic methods if applicable
       if (isFunction(target)) {
-        if (Array.isArray(value)) node[finalKey](...value)
-        else node[finalKey](value)
+        //don't call pointer event callback functions
+        if (!supportedPointerEvents.includes(prop)) {
+          if (Array.isArray(value)) node[finalKey](...value)
+          else node[finalKey](value)
+        }
         return
       }
       if (!target?.set && !isFunction(target)) root[finalKey] = value

+ 7 - 0
src/env.d.ts

@@ -7,5 +7,12 @@ declare module '*.vue' {
   export default component
 }
 
+interface Window {
+  __TRES__DEVTOOLS__?: {
+    cb: Function;
+    // You can add other properties of __TRES__DEVTOOLS__ here if needed
+  };
+}
+
 declare module '*.glsl' {}
 declare module '*.json' {}

+ 1 - 1
src/types/index.ts

@@ -126,7 +126,7 @@ export interface EventHandlers {
 }
 
 interface MathRepresentation {
-  set(...args: number[]): any
+  set(...args: number[] | [THREE.ColorRepresentation]): any
 }
 interface VectorRepresentation extends MathRepresentation {
   setScalar(s: number): any

+ 34 - 0
src/utils/perf.ts

@@ -0,0 +1,34 @@
+import type { Scene } from 'three'
+import type { TresObject } from './../types'
+
+export function calculateMemoryUsage(object: TresObject | Scene) {
+  let totalMemory = 0
+
+  object.traverse((node: TresObject) => {
+    if (node.isMesh && node.geometry) {
+      const geometry = node.geometry
+      const verticesMemory = geometry.attributes.position.count * 3 * Float32Array.BYTES_PER_ELEMENT
+      const facesMemory = geometry.index ? geometry.index.count * Uint32Array.BYTES_PER_ELEMENT : 0
+      const normalsMemory 
+        = geometry.attributes.normal ? geometry.attributes.normal.count * 3 * Float32Array.BYTES_PER_ELEMENT : 0
+      const uvsMemory = geometry.attributes.uv ? geometry.attributes.uv.count * 2 * Float32Array.BYTES_PER_ELEMENT : 0
+
+      const geometryMemory = verticesMemory + facesMemory + normalsMemory + uvsMemory
+      totalMemory += geometryMemory
+    }
+  })
+
+  return totalMemory
+}
+
+export function bytesToKB(bytes: number): string {
+  return (bytes / 1024).toFixed(2)
+}
+
+export function bytesToMB(bytes: number): string {
+  return (bytes / 1024 / 1024).toFixed(2)
+}
+
+export function bytesToGB(bytes: number): string {
+  return (bytes / 1024 / 1024 / 1024).toFixed(2)
+}

Some files were not shown because too many files changed in this diff