diff --git a/.env.local.default b/.env.local.default
index b55b6652d9..475a978a8d 100644
--- a/.env.local.default
+++ b/.env.local.default
@@ -32,6 +32,7 @@ VITE_APP_HOST=localhost
VITE_APP_PORT=3000
VITE_ZENDESK_ENABLED=false
VITE_ZENDESK_KEY=
+VITE_ZENDESK_AUTHENTICATION_ENABLED=false
# Use following value for minio s3 provider
#VITE_FILE_SERVER=https://localhost:9000/etherealengine-static-resources
#VITE_TEST_FILE_SERVER=https://localhost:9000/etherealengine-static-resources-test
@@ -220,3 +221,8 @@ OPENSEARCH_HOST=http://localhost:9200
# Switch to `true` to enable local file system operations
FS_PROJECT_SYNC_ENABLED=true
+
+# Zendesk key for user authentication
+ZENDESK_KEY_NAME=
+ZENDESK_SECRET=
+ZENDESK_KID=
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index c9f3725848..39b1794a21 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -66,6 +66,8 @@ ARG VITE_AVATURN_URL
ARG VITE_AVATURN_API
ARG VITE_ZENDESK_ENABLED
ARG VITE_ZENDESK_KEY
+ARG VITE_ZENDESK_AUTHENTICATION_ENABLED
+
ENV MYSQL_HOST=$MYSQL_HOST
ENV MYSQL_PORT=$MYSQL_PORT
ENV MYSQL_USER=$MYSQL_USER
@@ -92,6 +94,7 @@ ENV VITE_AVATURN_URL=$VITE_AVATURN_URL
ENV VITE_AVATURN_API=$VITE_AVATURN_API
ENV VITE_ZENDESK_ENABLED=$VITE_ZENDESK_ENABLED
ENV VITE_ZENDESK_KEY=$VITE_ZENDESK_KEY
+ENV VITE_ZENDESK_AUTHENTICATION_ENABLED=$VITE_ZENDESK_AUTHENTICATION_ENABLED
ARG CACHE_DATE
RUN npx cross-env ts-node --swc scripts/check-db-exists.ts
diff --git a/dockerfiles/api/Dockerfile-api-client b/dockerfiles/api/Dockerfile-api-client
index 05783a8696..b94d74a80b 100755
--- a/dockerfiles/api/Dockerfile-api-client
+++ b/dockerfiles/api/Dockerfile-api-client
@@ -83,6 +83,7 @@ ARG VITE_AVATURN_URL
ARG VITE_AVATURN_API
ARG VITE_ZENDESK_ENABLED
ARG VITE_ZENDESK_KEY
+ARG VITE_ZENDESK_AUTHENTICATION_ENABLED
ENV KUBERNETES=$KUBERNETES
ENV AUTH_SECRET=$AUTH_SECRET
ENV STORAGE_CLOUDFRONT_DOMAIN=$STORAGE_CLOUDFRONT_DOMAIN
@@ -114,6 +115,7 @@ ENV VITE_AVATURN_URL=$VITE_AVATURN_URL
ENV VITE_AVATURN_API=$VITE_AVATURN_API
ENV VITE_ZENDESK_ENABLED=$VITE_ZENDESK_ENABLED
ENV VITE_ZENDESK_KEY=$VITE_ZENDESK_KEY
+ENV VITE_ZENDESK_AUTHENTICATION_ENABLED=$VITE_ZENDESK_AUTHENTICATION_ENABLED
RUN npm run build-client
diff --git a/dockerfiles/client/Dockerfile-client b/dockerfiles/client/Dockerfile-client
index 2c343f08f1..06e9efe87e 100755
--- a/dockerfiles/client/Dockerfile-client
+++ b/dockerfiles/client/Dockerfile-client
@@ -61,6 +61,7 @@ ARG VITE_AVATURN_API
ARG AUTH_SECRET
ARG VITE_ZENDESK_ENABLED
ARG VITE_ZENDESK_KEY
+ARG VITE_ZENDESK_AUTHENTICATION_ENABLED
ENV KUBERNETES=$KUBERNETES
ENV AUTH_SECRET=$AUTH_SECRET
ENV STORAGE_CLOUDFRONT_DOMAIN=$STORAGE_CLOUDFRONT_DOMAIN
@@ -92,6 +93,7 @@ ENV VITE_AVATURN_URL=$VITE_AVATURN_URL
ENV VITE_AVATURN_API=$VITE_AVATURN_API
ENV VITE_ZENDESK_ENABLED=$VITE_ZENDESK_ENABLED
ENV VITE_ZENDESK_KEY=$VITE_ZENDESK_KEY
+ENV VITE_ZENDESK_AUTHENTICATION_ENABLED=$VITE_ZENDESK_AUTHENTICATION_ENABLED
RUN npm run build-client
diff --git a/dockerfiles/client/Dockerfile-client-serve-static b/dockerfiles/client/Dockerfile-client-serve-static
index 5ebfee8a0e..43bda5fd57 100755
--- a/dockerfiles/client/Dockerfile-client-serve-static
+++ b/dockerfiles/client/Dockerfile-client-serve-static
@@ -60,6 +60,7 @@ ARG VITE_AVATURN_URL
ARG VITE_AVATURN_API
ARG VITE_ZENDESK_ENABLED
ARG VITE_ZENDESK_KEY
+ARG VITE_ZENDESK_AUTHENTICATION_ENABLED
ARG AUTH_SECRET
ENV KUBERNETES=$KUBERNETES
ENV AUTH_SECRET=$AUTH_SECRET
@@ -93,6 +94,7 @@ ENV VITE_AVATURN_URL=$VITE_AVATURN_URL
ENV VITE_AVATURN_API=$VITE_AVATURN_API
ENV VITE_ZENDESK_ENABLED=$VITE_ZENDESK_ENABLED
ENV VITE_ZENDESK_KEY=$VITE_ZENDESK_KEY
+ENV VITE_ZENDESK_AUTHENTICATION_ENABLED=$VITE_ZENDESK_AUTHENTICATION_ENABLED
RUN npm run build-client
diff --git a/packages/client-core/i18n/en/admin.json b/packages/client-core/i18n/en/admin.json
index 161eb33b9d..b4c83b4884 100755
--- a/packages/client-core/i18n/en/admin.json
+++ b/packages/client-core/i18n/en/admin.json
@@ -548,7 +548,13 @@
"processInterval": "Process Interval"
},
- "plugins": "Plugins"
+ "plugins": "Plugins",
+ "zendesk": {
+ "header": "Zendesk",
+ "subtitle": "Edit Zendesk Settings"
+ },
+ "keyName": "key Name",
+ "kid": "Key Id"
},
"avatar": {
"columns": {
diff --git a/packages/client-core/i18n/en/editor.json b/packages/client-core/i18n/en/editor.json
index d11117e591..95a7fe60bd 100755
--- a/packages/client-core/i18n/en/editor.json
+++ b/packages/client-core/i18n/en/editor.json
@@ -27,6 +27,7 @@
"lbl-return": "Return",
"loadingScenes": "Loading Scenes",
"loadingScenesWithProgress": "Scene Loading... {{progress}}% ({{assetsLeft}} assets left)",
+ "help": "Help",
"menubar": {
"newScene": "New Scene",
"saveScene": "Save Scene",
@@ -469,14 +470,18 @@
"lbl-interactionText": "Interaction Text",
"lbl-interactionType": "Interaction Type",
"transform": {
+ "lodLevels": "LOD Levels",
+ "lodLevelNumber": "LOD Level {{index}}",
"compress": "Compress",
+ "applyPresetConfirmation": "Would you like to apply this preset?",
+ "savePreset": "Save Preset",
"useDraco": "Use DRACO Mesh Compression",
"useMeshopt": "Use Meshoptimizer",
"useQuantization": "Use Mesh Quantization",
"textureFormat": "Image Format",
"modelFormat": "Model Format",
- "resourceUri": "Resource URI",
- "dst": "File Name",
+ "resourceUri": "Resource URL",
+ "dst": "File name",
"resampleAnimations": "Resample Animations",
"maxTextureSize": "Max Texture Size",
"simplifyRatio": "Simplify Ratio",
@@ -666,6 +671,7 @@
"error-url": "Error Loading From URL"
},
"pointLight": {
+ "name": "Point Light",
"description": "A light which emits in all directions from a single point.",
"lbl-color": "Color",
"lbl-intensity": "Intensity",
@@ -986,33 +992,33 @@
},
"text": {
"textGroup": "Text",
- "textOpacity": "opacity",
- "textWidth": "width",
- "textIndent": "indent",
- "textAlign": "align",
- "textWrap": "wrap",
- "textAnchor": "anchor",
- "textDepthOffset": "depthOffset",
- "textCurveRadius": "curveRadius",
- "letterSpacing": "letterSpacing",
- "lineHeightGroup": "lineHeight",
- "lineHeight": "height",
- "textDirection": "direction",
+ "textOpacity": "Opacity",
+ "textWidth": "Width",
+ "textIndent": "Indent",
+ "textAlign": "Align",
+ "textWrap": "Wrap",
+ "textAnchor": "Anchor",
+ "textDepthOffset": "Depth off set",
+ "textCurveRadius": "Curve radius",
+ "letterSpacing": "Letter spacing",
+ "lineHeightGroup": "Line height",
+ "lineHeight": "Height",
+ "textDirection": "Direction",
"fontGroup": "Font",
- "fontFamily": "family",
- "fontSize": "size",
- "fontColor": "color",
- "fontMaterial": "material",
+ "fontFamily": "Family",
+ "fontSize": "Size",
+ "fontColor": "Color",
+ "fontMaterial": "Material",
"outlineGroup": "Outline",
- "outlineColor": "color",
- "outlineOpacity": "opacity",
- "outlineWidth": "width",
- "outlineBlur": "blur",
- "outlineOffset": "offset",
+ "outlineColor": "Color",
+ "outlineOpacity": "Opacity",
+ "outlineWidth": "Width",
+ "outlineBlur": "Blur",
+ "outlineOffset": "Offset",
"strokeGroup": "Stroke",
- "strokeColor": "color",
- "strokeOpacity": "opacity",
- "strokeWidth": "width",
+ "strokeColor": "Color",
+ "strokeOpacity": "Opacity",
+ "strokeWidth": "Width",
"advancedActive": "Show Advanced",
"advancedGroup": "Advanced",
"clippingActive": "clip.active",
@@ -1214,8 +1220,11 @@
"convert": "Convert",
"downloadProject": "Download Project",
"uploadAssets": "Upload Assets",
+ "uploadFiles": "Upload Files",
"search-placeholder": "Search folders",
"generatingThumbnails": "Generating Thumbnails ({{count}} remaining)",
+ "file": "File",
+ "directory": "Directory",
"fileProperties": {
"name": "Name:",
"type": "Type:",
@@ -1244,6 +1253,12 @@
"dateModified": "Date Modified",
"size": "Size"
}
+ },
+ "image-convert": {
+ "format": "Format",
+ "resize": "Resize",
+ "width": "Width",
+ "height": "Height"
}
},
"scene-assets": {
diff --git a/packages/client-core/i18n/en/user.json b/packages/client-core/i18n/en/user.json
index f149ea41ed..ed22e4b4ba 100755
--- a/packages/client-core/i18n/en/user.json
+++ b/packages/client-core/i18n/en/user.json
@@ -262,7 +262,8 @@
"userIdCopied": "User ID copied",
"apiKeyCopied": "API Key copied",
"refreshApiKey": "Refresh API Key",
- "privacyPolicy": "Privacy Policy"
+ "privacyPolicy": "Privacy Policy",
+ "helpChat": "Help Chat"
},
"oauth": {
"authenticating": "Authenticating...",
diff --git a/packages/client-core/src/admin/components/project/ManageUserPermissionModal.tsx b/packages/client-core/src/admin/components/project/ManageUserPermissionModal.tsx
index 286eac578b..e55e646d14 100644
--- a/packages/client-core/src/admin/components/project/ManageUserPermissionModal.tsx
+++ b/packages/client-core/src/admin/components/project/ManageUserPermissionModal.tsx
@@ -32,22 +32,21 @@ import { PopoverState } from '@etherealengine/client-core/src/common/services/Po
import { ProjectService } from '@etherealengine/client-core/src/common/services/ProjectService'
import { AuthState } from '@etherealengine/client-core/src/user/services/AuthService'
import { userHasAccess } from '@etherealengine/client-core/src/user/userHasAccess'
-import { InviteCode, ProjectPermissionType, ProjectType } from '@etherealengine/common/src/schema.type.module'
+import {
+ InviteCode,
+ ProjectPermissionType,
+ ProjectType,
+ projectPermissionPath
+} from '@etherealengine/common/src/schema.type.module'
import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
+import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks'
import Button from '@etherealengine/ui/src/primitives/tailwind/Button'
import Input from '@etherealengine/ui/src/primitives/tailwind/Input'
import Modal from '@etherealengine/ui/src/primitives/tailwind/Modal'
import Text from '@etherealengine/ui/src/primitives/tailwind/Text'
import Toggle from '@etherealengine/ui/src/primitives/tailwind/Toggle'
-export default function ManageUserPermissionModal({
- project,
- projectPermissions
-}: {
- project: ProjectType
- projectPermissions: readonly ProjectPermissionType[]
-}) {
- console.log('ManageUserPermissionModal', project, projectPermissions)
+export default function ManageUserPermissionModal({ project }: { project: ProjectType }) {
const { t } = useTranslation()
const selfUser = useHookstate(getMutableState(AuthState)).user
const userInviteCode = useHookstate('' as InviteCode)
@@ -58,6 +57,13 @@ export default function ManageUserPermissionModal({
? 'owner'
: 'user'
+ const projectPermissionsFindQuery = useFind(projectPermissionPath, {
+ query: {
+ projectId: project.id,
+ paginate: false
+ }
+ })
+
const handleCreatePermission = async () => {
if (!userInviteCode.value) {
userInviteCodeError.set(t('admin:components.project.inviteCodeCantEmpty'))
@@ -65,6 +71,7 @@ export default function ManageUserPermissionModal({
}
try {
await ProjectService.createPermission(userInviteCode.value, project.id, 'reviewer')
+ projectPermissionsFindQuery.refetch()
} catch (err) {
NotificationService.dispatchNotify(err.message, { variant: 'error' })
}
@@ -73,6 +80,7 @@ export default function ManageUserPermissionModal({
const handlePatchPermission = async (permission: ProjectPermissionType) => {
try {
await ProjectService.patchPermission(permission.id, permission.type === 'owner' ? 'user' : 'owner')
+ projectPermissionsFindQuery.refetch()
} catch (err) {
NotificationService.dispatchNotify(err.message, { variant: 'error' })
}
@@ -81,6 +89,7 @@ export default function ManageUserPermissionModal({
const handleRemovePermission = async (id: string) => {
try {
await ProjectService.removePermission(id)
+ projectPermissionsFindQuery.refetch()
} catch (err) {
NotificationService.dispatchNotify(err.message, { variant: 'error' })
}
@@ -105,7 +114,7 @@ export default function ManageUserPermissionModal({
/>
)}
- {projectPermissions?.map((permission) => (
+ {projectPermissionsFindQuery.data.map((permission) => (
{permission.userId === selfUser.id.value ? `${permission.user?.name} (you)` : permission.user?.name}
@@ -119,7 +128,7 @@ export default function ManageUserPermissionModal({
disabled={
selfUserPermission !== 'owner' ||
selfUser.id.value === permission.userId ||
- projectPermissions?.length === 1
+ projectPermissionsFindQuery.data.length === 1
}
/>
+ {!isWidgetVisible && (
+
}
+ onClick={openChat}
+ hidden={true}
+ >
+ {t('editor:help')}
+
+ )}
)
}
diff --git a/packages/editor/src/components/EditorContainer.tsx b/packages/editor/src/components/EditorContainer.tsx
index 4bdb5f8528..81be1d1b40 100755
--- a/packages/editor/src/components/EditorContainer.tsx
+++ b/packages/editor/src/components/EditorContainer.tsx
@@ -221,7 +221,7 @@ const onImportAsset = async () => {
if (projectName) {
try {
- await inputFileWithAddToScene({ projectName })
+ await inputFileWithAddToScene({ projectName, directoryPath: 'projects/' + projectName + '/assets/' })
} catch (err) {
NotificationService.dispatchNotify(err.message, { variant: 'error' })
}
diff --git a/packages/editor/src/components/assets/FileBrowser/FileBrowserContentPanel.tsx b/packages/editor/src/components/assets/FileBrowser/FileBrowserContentPanel.tsx
index e35f0e4934..6cbef20824 100644
--- a/packages/editor/src/components/assets/FileBrowser/FileBrowserContentPanel.tsx
+++ b/packages/editor/src/components/assets/FileBrowser/FileBrowserContentPanel.tsx
@@ -75,7 +75,6 @@ import { ToolButton } from '../../toolbar/ToolButton'
import { AssetSelectionChangePropsType } from '../AssetsPreviewPanel'
import ImageCompressionPanel from '../ImageCompressionPanel'
import ImageConvertPanel from '../ImageConvertPanel'
-import ModelCompressionPanel from '../ModelCompressionPanel'
import styles from '../styles.module.scss'
import { FileBrowserItem, FileTableWrapper, canDropItemOverFolder } from './FileBrowserGrid'
import { FilesViewModeSettings, FilesViewModeState, availableTableColumns } from './FileBrowserState'
@@ -251,9 +250,13 @@ const FileBrowserContentPanel: React.FC
= (props)
try {
const name = processFileName(file.name)
await uploadToFeathersService(fileBrowserUploadPath, [file], {
- project: projectName,
- path: relativePath + name,
- contentType: file.type
+ args: [
+ {
+ project: projectName,
+ path: relativePath + name,
+ contentType: file.type
+ }
+ ]
}).promise
} catch (err) {
NotificationService.dispatchNotify(err.message, { variant: 'error' })
@@ -638,7 +641,7 @@ const FileBrowserContentPanel: React.FC = (props)
{
- await inputFileWithAddToScene({ directoryPath: selectedDirectory.value })
+ await inputFileWithAddToScene({ projectName, directoryPath: selectedDirectory.value })
.then(refreshDirectory)
.catch((err) => {
NotificationService.dispatchNotify(err.message, { variant: 'error' })
@@ -688,14 +691,6 @@ const FileBrowserContentPanel: React.FC = (props)
/>
)}
- {openCompress.value && fileProperties.value && fileConsistsOfContentType(fileProperties.value, 'model') && (
-
- )}
-
{openCompress.value && fileProperties.value && fileConsistsOfContentType(fileProperties.value, 'image') && (
{
const entity = createEntity()
@@ -125,15 +124,13 @@ export const createLODVariants = async (
const variant = createTempEntity('LOD Variant', result)
setComponent(variant, ModelComponent, { src: modelSrc })
setComponent(variant, VariantComponent, {
- levels: lods.map((lod, lodIndex) => {
- return {
- src: `${LoaderUtils.extractUrlBase(lod.params.src)}${lod.params.dst}.${lod.params.modelFormat}`,
- metadata: {
- ...lod.variantMetadata,
- ...transformMetadata[lodIndex]
- }
+ levels: lods.map((lod, lodIndex) => ({
+ src: `${LoaderUtils.extractUrlBase(lod.params.src)}${lod.params.dst}.${lod.params.modelFormat}`,
+ metadata: {
+ ...lod.variantMetadata,
+ ...transformMetadata[lodIndex]
}
- }),
+ })),
heuristic
})
@@ -143,86 +140,86 @@ export const createLODVariants = async (
}
export default function ModelCompressionPanel({
- openCompress,
- fileProperties,
- onRefreshDirectory
+ selectedFile,
+ refreshDirectory
}: {
- openCompress: State
- fileProperties: State
- onRefreshDirectory: () => Promise
+ selectedFile: FileType
+ refreshDirectory: () => Promise
}) {
- const [compressionLoading, setCompressionLoading] = useState(false)
- const [isClientside, setIsClientSide] = useState(true)
- const [isIntegratedPrefab, setIsIntegratedPrefab] = useState(true)
- const [selectedLODIndex, setSelectedLODIndex] = useState(0)
- const [modalOpen, setModalOpen] = useState(false)
- const [selectedPreset, setSelectedPreset] = useState(defaultParams)
- const [presetList, setPresetList] = useState(LODList)
+ const { t } = useTranslation()
+ const compressionLoading = useHookstate(false)
+ const selectedLODIndex = useHookstate(0)
+ const selectedPreset = useHookstate(defaultParams)
+ const presetList = useHookstate(structuredClone(LODList))
useEffect(() => {
const presets = localStorage.getItem('presets')
if (presets !== null) {
- setPresetList(JSON.parse(presets))
+ presetList.set(JSON.parse(presets))
}
}, [])
const lods = useHookstate([])
const compressContentInBrowser = async () => {
- setCompressionLoading(true)
+ compressionLoading.set(true)
await compressModel()
- await onRefreshDirectory()
- setCompressionLoading(false)
- openCompress.set(false)
+ await refreshDirectory()
+ compressionLoading.set(false)
}
const applyPreset = (preset: ModelTransformParameters) => {
- setSelectedPreset(preset)
- setModalOpen(true)
+ selectedPreset.set(JSON.parse(JSON.stringify(preset)))
+ PopoverState.showPopupover(
+
+ )
}
const confirmPreset = () => {
- const lod = lods[selectedLODIndex].get(NO_PROXY)
+ const lod = lods[selectedLODIndex.value].get(NO_PROXY)
const src = lod.params.src
const dst = lod.params.dst
const modelFormat = lod.params.modelFormat
const uri = lod.params.resourceUri
- const presetParams = JSON.parse(JSON.stringify(selectedPreset)) as ModelTransformParameters
+ const presetParams = JSON.parse(JSON.stringify(selectedPreset.value)) as ModelTransformParameters
presetParams.src = src
presetParams.dst = dst
presetParams.modelFormat = modelFormat
presetParams.resourceUri = uri
- lods[selectedLODIndex].params.set(presetParams)
-
- setModalOpen(false)
+ lods[selectedLODIndex.value].params.set(presetParams)
}
- const savePresetList = (deleting: boolean) => {
- if (!deleting) {
- setPresetList([...presetList, lods[selectedLODIndex].value as LODVariantDescriptor])
- }
- localStorage.setItem('presets', JSON.stringify(presetList))
+ const savePresetList = () => {
+ presetList.merge([JSON.parse(JSON.stringify(lods[selectedLODIndex.value].value))])
+ localStorage.setItem('presets', JSON.stringify(presetList.value))
}
const compressModel = async () => {
- const modelSrc = fileProperties.url.value
- const clientside = isClientside
- const exportCombined = isIntegratedPrefab
+ const clientside = true
+ const exportCombined = true
const heuristic = Heuristic.BUDGET
await createLODVariants(lods.value as LODVariantDescriptor[], clientside, heuristic, exportCombined)
}
- const deletePreset = (idx: number) => {
- const newList = [...presetList]
- newList.splice(idx, 1)
- setPresetList(newList)
+ const deletePreset = (event: React.MouseEvent, idx: number) => {
+ event.stopPropagation()
+ presetList[idx].set(none)
+ // presetList.set(presetList.value.filter((_, i) => i !== idx))
+ localStorage.setItem('presets', JSON.stringify(presetList.value))
+ }
+
+ const handleRemoveLOD = (idx: number) => {
+ lods.set((currentLods) => currentLods.filter((_, i) => i !== idx))
+ if (selectedLODIndex.value >= lods.length) {
+ selectedLODIndex.set(lods.length - 1)
+ }
}
useEffect(() => {
- const fullSrc = fileProperties.url.value
+ const fullSrc = selectedFile.url
const fileName = fullSrc.split('/').pop()!.split('.').shift()!
const defaults = defaultLODs.map((defaultLOD) => {
@@ -235,16 +232,12 @@ export default function ModelCompressionPanel({
})
lods.set(defaults)
- }, [fileProperties.url])
+ }, [selectedFile.url])
- const handleLODSelect = (index) => {
- setSelectedLODIndex(Math.min(index, lods.length - 1))
- }
-
- const handleLODAdd = () => {
- const params = JSON.parse(JSON.stringify(lods[selectedLODIndex].params.value)) as ModelTransformParameters
+ const handleAddLOD = () => {
+ const params = JSON.parse(JSON.stringify(lods[selectedLODIndex.value].params.value)) as ModelTransformParameters
const suffix = '-LOD' + lods.length
- params.dst = params.dst.replace(lods[selectedLODIndex].suffix.value, suffix)
+ params.dst = params.dst.replace(lods[selectedLODIndex.value].suffix.value, suffix)
lods.merge([
{
params: params,
@@ -252,130 +245,93 @@ export default function ModelCompressionPanel({
variantMetadata: {}
}
])
- setSelectedLODIndex(lods.length - 1)
- }
-
- const handleLodRemove = () => {
- lods.set((lods) => {
- lods.pop()
- return lods
- })
- setSelectedLODIndex(Math.min(selectedLODIndex, lods.length - 1))
+ selectedLODIndex.set(lods.length - 1)
}
return (
-