Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ const Hierarchy: React.FC = () => {
canDuplicate,
canDrag,
canReorder,
centerViewOnEntity
centerViewOnEntity,
isRoot
} = useTree()
const selectedEntities = useEntitiesWith((components) => components.Selection)
const [lastSelectedItem, setLastSelectedItem] = useState<Entity | undefined>(undefined)
Expand Down Expand Up @@ -173,7 +174,8 @@ const Hierarchy: React.FC = () => {
canDuplicate: canDuplicate,
canDrag: canDrag,
canReorder: canReorder,
onLastSelectedChange: handleLastSelectedChange
onLastSelectedChange: handleLastSelectedChange,
isRoot: isRoot
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,32 @@
.Metrics > .Overlay {
display: flex;
flex-direction: column;
width: 250px;
width: 300px;
position: absolute;
overflow-y: auto;
left: 0;
bottom: calc(var(--metrics-bottom) + var(--metrics-button-height) + 8px);
background-color: var(--base-19);
padding: 13px 12px;
border-radius: 4px;
gap: 16px;
}

.Metrics > .Overlay h2.Header {
.Metrics > .Overlay .Description {
display: flex;
font-size: 14px;
font-size: 12px;
font-weight: 500;
line-height: 17px;
color: var(--base-01);
color: var(--base-09);
margin-bottom: 0;
gap: 4px;
}

.Metrics > .Overlay .Header {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
}

.Metrics > .Overlay .Item .Title,
.Metrics > .Overlay .Item .Description,
.Metrics > .Overlay .Item .Description .Key {
Expand Down Expand Up @@ -112,6 +117,18 @@
line-height: 14px;
}

.Metrics > .Overlay .WarningItem {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
font-weight: 500;
line-height: 17px;
color: var(--warning-main);
margin-bottom: 0;
gap: 4px;
}

.Metrics > .Overlay .Item .Description .Key {
display: flex;
align-items: center;
Expand All @@ -125,9 +142,31 @@
.Metrics .Buttons .Button.LimitExceeded svg,
.Metrics > .Overlay .Item .Description.LimitExceeded,
.Metrics > .Overlay .Item .Description.LimitExceeded .secondary {
color: var(--error-dark);
color: var(--warning-main);
}

.Metrics .Buttons .Button.Active.LimitExceeded svg {
color: var(--error-main);
color: var(--warning-main);
}

.Metrics > .Overlay .WarningsContainer {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 12px;
}

.Metrics > .Overlay .WarningIcon {
color: var(--warning-main);
width: 16px;
height: 16px;
flex-shrink: 0;
}

.Metrics > .Overlay .WarningText {
font-size: 12px;
font-weight: 400;
color: var(--warning-main);
line-height: 16px;
margin: 0;
}
92 changes: 73 additions & 19 deletions packages/@dcl/inspector/src/components/Renderer/Metrics/Metrics.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import React, { useCallback, useEffect, useMemo } from 'react'
import cx from 'classnames'
import { IoGridOutline as SquaresGridIcon, IoAlertCircleOutline as AlertIcon } from 'react-icons/io5'
import { IoGridOutline as SquaresGridIcon } from 'react-icons/io5'
import { FiAlertTriangle as WarningIcon } from 'react-icons/fi'

import { Material } from '@babylonjs/core'
import { CrdtMessageType } from '@dcl/ecs'

import { withSdk, WithSdkProps } from '../../../hoc/withSdk'
import { useChange } from '../../../hooks/sdk/useChange'
import { useOutsideClick } from '../../../hooks/useOutsideClick'
import { useAppDispatch, useAppSelector } from '../../../redux/hooks'
import { getMetrics, getLimits, setEntitiesOutOfBoundaries, setMetrics, setLimits } from '../../../redux/scene-metrics'
import {
getMetrics,
getLimits,
getEntitiesOutOfBoundaries,
setEntitiesOutOfBoundaries,
setMetrics,
setLimits
} from '../../../redux/scene-metrics'
import { SceneMetrics } from '../../../redux/scene-metrics/types'
import type { Layout } from '../../../lib/utils/layout'
import { GROUND_MESH_PREFIX, PARCEL_SIZE } from '../../../lib/utils/scene'
import { getLayoutManager } from '../../../lib/babylon/decentraland/layout-manager'
import { Button } from '../../Button'
import { getSceneLimits } from './utils'

Expand Down Expand Up @@ -55,6 +65,7 @@ const Metrics = withSdk<WithSdkProps>(({ sdk }) => {
const dispatch = useAppDispatch()
const metrics = useAppSelector(getMetrics)
const limits = useAppSelector(getLimits)
const entitiesOutOfBoundaries = useAppSelector(getEntitiesOutOfBoundaries)
const [showMetrics, setShowMetrics] = React.useState(false)
const [sceneLayout, setSceneLayout] = React.useState<Layout>({
base: { x: 0, y: 0 },
Expand Down Expand Up @@ -107,12 +118,21 @@ const Metrics = withSdk<WithSdkProps>(({ sdk }) => {

const handleSceneChange = useCallback(() => {
const nodes = getNodes()
const entitiesOutOfBoundaries = nodes.reduce((count, node) => {
const { isEntityOutsideLayout } = getLayoutManager(sdk.scene)

const entitiesOutOfBoundariesArray: number[] = []

nodes.forEach((node) => {
const entity = sdk.sceneContext.getEntityOrNull(node.entity)
return entity && entity.isOutOfBoundaries() ? count + 1 : count
}, 0)
if (entity && entity.boundingInfoMesh) {
const isOutside = isEntityOutsideLayout(entity.boundingInfoMesh)
if (isOutside) {
entitiesOutOfBoundariesArray.push(node.entity)
}
}
})

dispatch(setEntitiesOutOfBoundaries(entitiesOutOfBoundaries))
dispatch(setEntitiesOutOfBoundaries(entitiesOutOfBoundariesArray))
}, [sdk, dispatch, getNodes, setEntitiesOutOfBoundaries])

useEffect(() => {
Expand Down Expand Up @@ -155,6 +175,10 @@ const Metrics = withSdk<WithSdkProps>(({ sdk }) => {
)
}, [metrics, limits])

const isAnyLimitExceeded = (limitsExceeded: Record<string, any>): boolean => {
return Object.values(limitsExceeded).length > 0 || entitiesOutOfBoundaries.length > 0
}

const handleToggleMetricsOverlay = useCallback(
(e: React.MouseEvent<HTMLButtonElement> | MouseEvent) => {
e.preventDefault()
Expand All @@ -166,30 +190,49 @@ const Metrics = withSdk<WithSdkProps>(({ sdk }) => {

const overlayRef = useOutsideClick(handleToggleMetricsOverlay)

const getWarningMessages = (): string[] => {
const baseMessage = 'Your scene contains too many'
const warnings: string[] = []

Object.entries(limitsExceeded).forEach(([key, isExceeded]) => {
if (isExceeded) {
warnings.push(`${baseMessage} ${key}`)
}
})

if (entitiesOutOfBoundaries.length > 0) {
warnings.push(
`${entitiesOutOfBoundaries.length} entit${
entitiesOutOfBoundaries.length === 1 ? 'y is' : 'ies are'
} out of bounds and may not display correctly in-world.`
)
}

return warnings
}

const warningMessages = getWarningMessages()

return (
<div className="Metrics">
<div className="Buttons">
<Button
className={cx({ Active: showMetrics, LimitExceeded: Object.values(limitsExceeded).length > 0 })}
className={cx({ Active: showMetrics, LimitExceeded: isAnyLimitExceeded(limitsExceeded) })}
onClick={handleToggleMetricsOverlay}
>
<SquaresGridIcon size={ICON_SIZE} />
</Button>
</div>
{Object.values(limitsExceeded).length > 0 && (
<div className="LimitExceeded">
<AlertIcon />
Too many {Object.keys(limitsExceeded)[0].toUpperCase()}
</div>
)}
{showMetrics && (
<div ref={overlayRef} className="Overlay">
<h2 className="Header">
{sceneLayout.parcels.length} Parcels
<span className="secondary">
{sceneLayout.parcels.length * PARCEL_SIZE}m<sup>2</sup>
</span>
</h2>
<h2 className="Header">Scene Optimization</h2>
<div className="Description">Suggested Specs per Parcel</div>
<div className="Description">
{sceneLayout.parcels.length} Parcels = {sceneLayout.parcels.length * PARCEL_SIZE}
<div>
m<sup>2</sup>
</div>
</div>
<div className="Items">
{Object.entries(metrics).map(([key, value]) => (
<div className="Item" key={key}>
Expand All @@ -202,6 +245,17 @@ const Metrics = withSdk<WithSdkProps>(({ sdk }) => {
</div>
))}
</div>
{warningMessages.length > 0 && (
<div className="WarningsContainer">
<div className="Description">WARNINGS</div>
{warningMessages.map((message, index) => (
<div className="WarningItem" key={index}>
<WarningIcon className="WarningIcon" />
<span className="WarningText">{message}</span>
</div>
))}
</div>
)}
</div>
)}
</div>
Expand Down
4 changes: 4 additions & 0 deletions packages/@dcl/inspector/src/components/Tree/Tree.css
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,7 @@
.Tree .contexify_item {
width: 100%;
}

.Tree .WarningIcon {
color: var(--warning-main);
}
37 changes: 37 additions & 0 deletions packages/@dcl/inspector/src/components/Tree/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { XYCoord, useDrag, useDrop } from 'react-dnd'
import { IoIosArrowDown, IoIosArrowForward } from 'react-icons/io'
import cx from 'classnames'
import { Entity } from '@dcl/ecs'
import { FiAlertTriangle as WarningIcon } from 'react-icons/fi'

import { withContextMenu } from '../../hoc/withContextMenu'
import { Input } from '../Input'
Expand All @@ -11,6 +12,9 @@ import { ActionArea } from './ActionArea'
import { Edit as EditInput } from './Edit'
import { DropType, calculateDropType } from './utils'
import { useSdk } from '../../hooks/sdk/useSdk'
import { useAppSelector } from '../../redux/hooks'
import { getEntitiesOutOfBoundaries } from '../../redux/scene-metrics'
import { InfoTooltip } from '../ui'

import './Tree.css'

Expand Down Expand Up @@ -44,6 +48,7 @@ type Props<T> = {
getDragContext?: () => unknown
dndType?: string
onLastSelectedChange?: (value: T) => void
isRoot?: (value: T) => boolean
}

type EmptyString = ''
Expand Down Expand Up @@ -83,6 +88,7 @@ export function Tree<T>() {
onDuplicate,
onDoubleSelect,
onSetOpen,
isRoot,
getDragContext = () => ({}),
dndType = 'tree',
onLastSelectedChange
Expand Down Expand Up @@ -227,6 +233,7 @@ export function Tree<T>() {
}

const sdk = useSdk()
const entitiesOutOfBoundaries = useAppSelector(getEntitiesOutOfBoundaries)

const [, drag] = useDrag(
() => ({
Expand Down Expand Up @@ -288,6 +295,29 @@ export function Tree<T>() {
return typeof value !== 'string'
}, [value])

const isEntityOutOfBoundaries = useMemo(() => {
if (typeof value === 'string') return false

if (entitiesOutOfBoundaries.includes(value as Entity)) {
return true
}

const checkChildrenOutOfBoundaries = (entity: Entity): boolean => {
const children = getChildren(entity as T)
for (const child of children) {
if (entitiesOutOfBoundaries.includes(child as Entity)) {
return true
}
if (checkChildrenOutOfBoundaries(child as Entity)) {
return true
}
}
return false
}

return checkChildrenOutOfBoundaries(value as Entity)
}, [value, entitiesOutOfBoundaries, getChildren])

drag(drop(ref))

const controlsProps = {
Expand Down Expand Up @@ -319,6 +349,13 @@ export function Tree<T>() {
{props.getIcon && props.getIcon(value)}
<div>{label || id}</div>
{isEntity && <ActionArea entity={value as Entity} />}
{!isRoot?.(value as T) && isEntityOutOfBoundaries && (
<InfoTooltip
text="This entity is out of bounds and might not display correctly in-world."
trigger={<WarningIcon className="WarningIcon" />}
position="right center"
/>
)}
</div>
</div>
{editMode && typeof label === 'string' && (
Expand Down
1 change: 1 addition & 0 deletions packages/@dcl/inspector/src/hooks/sdk/useTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export const useTree = () => {
getLabel,
setOpen,
isOpen,
isRoot,
isHidden,
canRename,
canRemove,
Expand Down
Loading
Loading