From eb8cdcb77c27d2c895186f4268ddf6bb4e99e8f3 Mon Sep 17 00:00:00 2001 From: Nicolas Echezarreta Date: Wed, 19 Apr 2023 13:25:07 -0300 Subject: [PATCH] feat: support dropping folders on Gltf UI component --- .../GltfInspector/GltfInspector.tsx | 42 +++++++++++++++---- .../ProjectAssetExplorer/ProjectView.tsx | 2 +- .../components/ProjectAssetExplorer/utils.ts | 13 ++++-- packages/@dcl/inspector/src/lib/logic/once.ts | 6 +-- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/packages/@dcl/inspector/src/components/EntityInspector/GltfInspector/GltfInspector.tsx b/packages/@dcl/inspector/src/components/EntityInspector/GltfInspector/GltfInspector.tsx index 2b42ba94d..bafb52894 100644 --- a/packages/@dcl/inspector/src/components/EntityInspector/GltfInspector/GltfInspector.tsx +++ b/packages/@dcl/inspector/src/components/EntityInspector/GltfInspector/GltfInspector.tsx @@ -1,10 +1,11 @@ -import { useCallback, useState } from 'react' +import { useCallback } from 'react' import { Menu, Item } from 'react-contexify' import { useDrop } from 'react-dnd' import { AiFillDelete as DeleteIcon } from 'react-icons/ai' import cx from 'classnames' -import { AssetNodeItem } from '../../ProjectAssetExplorer/types' +import { memoize } from '../../../lib/logic/once' +import { TreeNode } from '../../ProjectAssetExplorer/ProjectView' import { withContextMenu } from '../../../hoc/withContextMenu' import { WithSdkProps, withSdk } from '../../../hoc/withSdk' @@ -18,14 +19,23 @@ import { Container } from '../../Container' import { TextField } from '../TextField' import { Props } from './types' import { fromGltf, toGltf, isValidInput } from './utils' +import { getFullNodePath, isAssetNode } from '../../ProjectAssetExplorer/utils' const DROP_TYPES = ['project-asset-gltf'] interface IDrop { value: string; - context: { tree: Map } + context: { tree: Map } } +const getUniqueLastChild = memoize((node: TreeNode, tree: Map) => { + let _node = node + while ((_node.children || []).length === 1) { + _node = tree.get(_node.children![0])! + } + return _node +}) + export default withSdk( withContextMenu(({ sdk, entity, contextMenuId }) => { const { files } = useFileSystem() @@ -37,8 +47,8 @@ export default withSdk( const getInputProps = useComponentInput(entity, GltfContainer, fromGltf, toGltf, handleInputValidation) const handleRemove = useCallback(() => GltfContainer.deleteFrom(entity), []) - const handleDrop = useCallback((value: AssetNodeItem) => { - GltfContainer.createOrReplace(entity, { src: value.asset.src }) + const handleDrop = useCallback((src: string) => { + GltfContainer.createOrReplace(entity, { src }) }, []) const [{ isHover }, drop] = useDrop( @@ -46,10 +56,28 @@ export default withSdk( accept: DROP_TYPES, drop: ({ value, context }: IDrop, monitor) => { if (monitor.didDrop()) return - handleDrop(context.tree.get(value)!) + const node = context.tree.get(value)! + + if (isAssetNode(node)) return handleDrop(node.asset.src) + + const child = getUniqueLastChild(node, context.tree) + + // double-checking just to be sure (canDrop already does this...) + if (isAssetNode(child)) { + const path = getFullNodePath(child) + handleDrop(path) + } + }, + canDrop: ({ value, context }: IDrop) => { + const node = context.tree.get(value)! + if (node.type === 'folder') { + const child = getUniqueLastChild(node, context.tree) + return isAssetNode(child) + } + return true }, collect: (monitor) => ({ - isHover: monitor.canDrop() && monitor.isOver() + isHover: monitor.isOver() && monitor.canDrop() }) }), [files] diff --git a/packages/@dcl/inspector/src/components/ProjectAssetExplorer/ProjectView.tsx b/packages/@dcl/inspector/src/components/ProjectAssetExplorer/ProjectView.tsx index 2c93606d0..ac469efb1 100644 --- a/packages/@dcl/inspector/src/components/ProjectAssetExplorer/ProjectView.tsx +++ b/packages/@dcl/inspector/src/components/ProjectAssetExplorer/ProjectView.tsx @@ -16,7 +16,7 @@ type Props = { const ROOT = 'File System' -type TreeNode = Omit & { children?: string[] } +export type TreeNode = Omit & { children?: string[] } function ProjectView({ folders }: Props) { const [open, setOpen] = useState(new Set()) diff --git a/packages/@dcl/inspector/src/components/ProjectAssetExplorer/utils.ts b/packages/@dcl/inspector/src/components/ProjectAssetExplorer/utils.ts index 7c77994c7..70812dd77 100644 --- a/packages/@dcl/inspector/src/components/ProjectAssetExplorer/utils.ts +++ b/packages/@dcl/inspector/src/components/ProjectAssetExplorer/utils.ts @@ -1,4 +1,5 @@ -import { AssetNode, AssetNodeFolder } from './types' +import { TreeNode } from './ProjectView' +import { AssetNode, AssetNodeFolder, AssetNodeItem } from './types' export function AssetNodeRootNull(): AssetNodeFolder { return { name: '', parent: null, type: 'folder', children: [] } @@ -44,12 +45,16 @@ export function buildAssetTree(paths: string[]): AssetNodeFolder { return root } -export function getFullNodePath(item: AssetNode) { +export function getFullNodePath(item: AssetNode | TreeNode) { let path = '' let it = item - while (it.parent !== null && item.name !== '') { - path = item.name + '/' + path + while (it.parent) { + path = '/' + it.name + path it = it.parent } return path } + +export function isAssetNode(node: AssetNode | TreeNode): node is AssetNodeItem { + return node.type === 'asset' +} diff --git a/packages/@dcl/inspector/src/lib/logic/once.ts b/packages/@dcl/inspector/src/lib/logic/once.ts index c1848bab0..6ef824f2e 100644 --- a/packages/@dcl/inspector/src/lib/logic/once.ts +++ b/packages/@dcl/inspector/src/lib/logic/once.ts @@ -1,8 +1,8 @@ -export function memoize(cb: (a: K) => V): (a: K) => V { +export function memoize(cb: (a: K, ...params: P[]) => V): (a: K, ...params: P[]) => V { const memoized = new WeakMap() - return (a: K) => { + return (a: K, ...params: P[]) => { if (!memoized.has(a)) { - const ret = cb(a) + const ret = cb(a, ...params) memoized.set(a, ret) return ret }