import {IDockviewPanelProps} from "dockview"; import {MenuBar, MenuItem} from "../Components/Menu.tsx"; import "./EntityHierarchyWindow.css"; import React, {MouseEvent, MouseEventHandler, ReactElement, useEffect, useState} from "react"; import IconVisible from "../assets/Icons/AntDesign/eye-visible.svg"; import IconInvisible from "../assets/Icons/AntDesign/eye-invisible.svg"; import {Updater, useImmer} from "use-immer"; import {EngineGlue} from "../EngineGlue"; import {enableMapSet} from "immer" import {Entity, EntityId} from "../Entity.ts"; import {IconCheckButton} from "../Components/IconCheckButton.tsx"; enableMapSet() type EntityMap = { selectedIds: Set; entities: Map; //[id: EntityId]: Entity; }; const entityHierarchy: EntityMap = { selectedIds: new Set(), entities: new Map([ ["0", new Entity("Root", "0", ["1", "2"])], ["1", new Entity("Entity 1", "1", [])], ["2", new Entity("Entity 2", "2", ["3"])], ["3", new Entity("Entity 3 in 2", "3", [])] ]) // // 0 is hardcoded to be the root. The editor must provide this "pseudo"-entity. // 0: new Entity("Root", 0, [1, 2]), // 1: new Entity("Entity 1", 1, []), // 2: new Entity("Entity 2", 2, [3]), // 3: new Entity("Entity 3 in 2", 3, []) }; export default function EntityHierarchyWindow(props: IDockviewPanelProps) { const [entities, updateEntities] = useImmer(entityHierarchy); const [filterText, setFilterText] = useState(""); useEffect(() => { EngineGlue.onUpdateEntities = (entities: Entity[]) => { updateEntities(draft => { try { for (const entity of entities) { // console.log(`${entity.id}: "${entity.name ?? "deleted"}", // ${entity.visible ? "Visible" : "Invisible"}, ${entity.children.length} Children: [${entity.children}]`) if (entity.name === "undefined") { draft.entities.delete(entity.id); //delete draft[entity.id]; draft.selectedIds.delete(entity.id); } else { draft.entities.set(entity.id, entity); //draft[entity.id] = entity; } } } catch (e) { console.log(`Failed to update entities: ${e}`); } }); }; EngineGlue.requestEntityHierarchyUpdate(); return () => { delete EngineGlue.onUpdateEntities; }; }, [updateEntities]); return ( <> ); } function EntityTreeItem({item, allEntities, updateEntities, showAsTree}: { item: Entity, allEntities: EntityMap, updateEntities: Updater, showAsTree: boolean}) : ReactElement { //const [isOpen, setOpen] = useState(false); const isSelected = allEntities.selectedIds.has(item.id); //const isVisible = allEntities[item.id].visible; function handleClick(event: MouseEvent) { console.log("Clicked" + item.name); updateEntities(draft => { if (isSelected) { draft.selectedIds.delete(item.id); } else { draft.selectedIds.add(item.id); } }); event.stopPropagation(); } function toggleVisibility(event: MouseEvent) { event.stopPropagation(); // updateEntities(draft => { // draft[item.id].visible = !draft[item.id].visible; // }); //event.stopPropagation(); } return (
  • {item.name}
    { showAsTree && item.children.length > 0 &&
      { item.children.map((childId) => { const entity = allEntities.entities.get(childId); if (entity === undefined) return; return ; }) }
    }
  • ); } function VisibilityToggle({item, onClick, updateEntities} : {item: Entity, onClick: MouseEventHandler, updateEntities: Updater}) { function clickHandler(event: MouseEvent) { updateEntities(draft => { const entity = draft.entities.get(item.id) if (entity !== undefined) { entity.visible = !entity.visible; EngineGlue.setEntityVisibility(entity.id, entity.visible); } }); onClick(event); event.stopPropagation(); } return } function EntityTree({items, updateItems, entityFilter}: { items: EntityMap, updateItems: Updater, entityFilter: string }) { const rootEntity = items.entities.get("0"); if (entityFilter.length == 0) { return (
      { rootEntity && rootEntity.children.map((childId) => { const entity = items.entities.get(childId); if (entity === undefined) return; return ; }) }
    ); } else { return (
      { Array.from(items.entities).filter(([, entity]) => { return entity.name.indexOf(entityFilter) != -1 }).map(([, entity]) => { return ; }) }
    ); } } function EntityMenuBar({searchString, onFilterTextChanged}: { searchString: string, onFilterTextChanged: (s: string) => void }) { return onFilterTextChanged(e.target.value)}/> ; }