Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiple reorders #1015

Merged
merged 5 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/ThreeEditor/Simulation/Materials/MaterialManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export class MaterialManager {
}

reset(): void {
this.selectedMaterials.clear();
this.customMaterials = {};
}
}
10 changes: 9 additions & 1 deletion src/ThreeEditor/Simulation/Materials/SimulationMaterial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DEFAULT_MATERIAL_ICRU,
DEFAULT_MATERIAL_NAME
} from './materials';
import { isCounterMapError } from '../../../util/CounterMap/CounterMap';

export type RenderProps = Omit<SimulationMaterialJSON, 'uuid' | 'name' | 'icru' | 'density'>;

Expand Down Expand Up @@ -130,7 +131,14 @@ export default class SimulationMaterial extends THREE.MeshPhongMaterial {
}

increment = () => this.editor.materialManager.selectedMaterials.increment(this.uuid);
decrement = () => this.editor.materialManager.selectedMaterials.decrement(this.uuid);
decrement = () => {
try {
this.editor.materialManager.selectedMaterials.decrement(this.uuid);
} catch (e: unknown) {
if (isCounterMapError(e)) console.error(e.message);
else throw e;
}
};
}

export const isSimulationMaterial = (m: unknown): m is SimulationMaterial =>
Expand Down
18 changes: 11 additions & 7 deletions src/ThreeEditor/components/Sidebar/EditorSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,17 @@ export function EditorSidebar(props: { editor: Editor }) {
}
],
tree: (
<SidebarTree
editor={editor}
sources={[
editor.zoneManager.worldZone,
editor.zoneManager.zoneContainer.children
]}
/>
<>
<SidebarTree
editor={editor}
sources={[editor.zoneManager.worldZone]}
/>
<Divider sx={{ marginBottom: t => t.spacing(1) }} />
<SidebarTree
editor={editor}
sources={[editor.zoneManager.zoneContainer.children]}
/>
</>
)
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
.editor-sidebar-tree {
}

.editor-sidebar-tree :is(ul, li),
.editor-sidebar-tree:is(ul, li) {
.editor-sidebar-tree :where(ul, li),
.editor-sidebar-tree:where(ul, li) {
list-style: none;
padding: 0;
margin: 0;
}

.editor-sidebar-tree {
padding-bottom: 10px;
}
49 changes: 35 additions & 14 deletions src/ThreeEditor/components/Sidebar/SidebarTree/SidebarTree.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Tree, TreeMethods } from '@minoru/react-dnd-treeview';
import { Object3D } from 'three';
import './SidebarTree.style.css';
Expand All @@ -7,30 +7,35 @@ import { SimulationElement } from '../../../Simulation/Base/SimulationElement';
import { SidebarTreeItem, TreeItem } from './SidebarTreeItem';
import { Divider } from '@mui/material';
import { hasVisibleChildren } from '../../../../util/hooks/useKeyboardEditorControls';
import { isOutput } from '../../../Simulation/Scoring/ScoringOutput';
import { isQuantity } from '../../../Simulation/Scoring/ScoringQuantity';
import { ChangeObjectOrderCommand } from '../../../js/commands/ChangeObjectOrderCommand';
import { isWorldZone } from '../../../Simulation/Zones/WorldZone/WorldZone';
import { generateUUID } from 'three/src/math/MathUtils';

type TreeSource = (Object3D[] | Object3D)[];

export function SidebarTree(props: { editor: Editor; sources: TreeSource }) {
const { editor, sources } = props;

const treeRef = useRef<TreeMethods>(null);
const treeId = useMemo(() => generateUUID(), []);

const buildOption = useCallback(
(
object: Object3D | SimulationElement | undefined,
items: Object3D[] | undefined,
parentId: number
parentId: number,
index?: number
): TreeItem[] => {
if (!object) return [];

if (!items) items = object.children;

let children: TreeItem[] = [];
if (hasVisibleChildren(object))
children = items.map(child => buildOption(child, child.children, object.id)).flat();
children = items
.map((child, idx) => buildOption(child, child.children, object.id, idx))
.flat();

return [
{
Expand All @@ -39,19 +44,23 @@ export function SidebarTree(props: { editor: Editor; sources: TreeSource }) {
droppable: true,
text: object.name,
data: {
object: object
object: object,
treeId: treeId,
index: index
}
},
...children
];
},
[]
[treeId]
);

const [treeData, setTreeData] = useState<TreeItem[]>([]);

const refreshTreeData = useCallback(() => {
const options = sources.flat().flatMap(source => buildOption(source, source.children, 0));
const options = sources
.flat()
.flatMap((source, idx) => buildOption(source, source.children, 0, idx));
setTreeData(options);
}, [buildOption, sources]);

Expand Down Expand Up @@ -85,16 +94,16 @@ export function SidebarTree(props: { editor: Editor; sources: TreeSource }) {
source: TreeItem | undefined,
target: TreeItem | undefined,
dropTargetId: string | number
) => {
): boolean => {
if (!source) return false;

if (source.data?.treeId !== treeId) return false;

const object3d = source.data?.object;

if (isQuantity(object3d)) return object3d.parent === target?.data?.object;

if (isOutput(object3d)) return source.parent === dropTargetId;

return false;
return source.parent === dropTargetId;
};

return (
Expand All @@ -111,15 +120,27 @@ export function SidebarTree(props: { editor: Editor; sources: TreeSource }) {
'relativeIndex is undefined. Probably you need disable sort option.'
);

if (dragSource?.data)
if (dragSource?.data) {
if (
relativeIndex === dragSource.data.index ||
relativeIndex - 1 === dragSource.data.index
)
return; // no change needed

let newPosition =
relativeIndex > (dragSource.data.index ?? 0)
? relativeIndex - 1
: relativeIndex;

editor.execute(
new ChangeObjectOrderCommand(editor, dragSource.data.object, relativeIndex)
new ChangeObjectOrderCommand(editor, dragSource.data.object, newPosition)
);
}
}}
canDrop={(_, { dropTarget, dragSource, dropTargetId }) => {
return canDrop(dragSource, dropTarget, dropTargetId);
}}
canDrag={node => isQuantity(node?.data?.object) || isOutput(node?.data?.object)}
Derstilon marked this conversation as resolved.
Show resolved Hide resolved
canDrag={node => !isWorldZone(node?.data?.object)}
sort={false}
insertDroppableFirst={false}
dropTargetOffset={5}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';

export type TreeItem = NodeModel<{
object: Object3D<THREE.Event> | SimulationElement;
treeId: string;
index?: number;
}>;

function isHidable(object: Object3D | SimulationPropertiesType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const Accordion = styled((props: AccordionProps) => (
square
{...props}
/>
))(({ theme }) => ({
))(() => ({
'&.Mui-expanded': {
margin: '0'
}
Expand Down
58 changes: 40 additions & 18 deletions src/ThreeEditor/js/commands/ChangeObjectOrderCommand.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Object3D, Event } from 'three';
import { Command } from '../Command.js';
import { Command } from '../Command';
import { Editor } from '../Editor.js';

interface ChangeObjectOrderCommandJSON {
oldIndex: number;
newIndex: number;
objectUuid: string;
selectedUuid: string;
}

/**
* @param editor Editor
* @param object THREE.Object3D
Expand All @@ -13,6 +19,7 @@ export class ChangeObjectOrderCommand extends Command {
parent: THREE.Object3D;
newIndex: number;
oldIndex: number;
oldSelect: THREE.Object3D | null;
constructor(editor: Editor, object: THREE.Object3D, newIndex: number) {
super(editor);

Expand All @@ -23,44 +30,59 @@ export class ChangeObjectOrderCommand extends Command {
this.newIndex = newIndex;
this.parent = object.parent;
this.oldIndex = object.parent.children.indexOf(object);
this.oldSelect = editor.selected;

if (this.parent.children[this.newIndex] === undefined)
throw new Error('The new index is not valid.');

if (this.oldIndex === -1) throw new Error('The object is not a child of the given parent.'); // should never happen.
}

execute() {
this.changeOrderOfObject(this.object, this.newIndex, this.oldIndex);
this.changeOrderOfObject(this.newIndex, this.oldIndex);
this.editor.select(this.object);
}

undo() {
this.changeOrderOfObject(this.object, this.oldIndex, this.newIndex);
this.changeOrderOfObject(this.oldIndex, this.newIndex);
this.editor.select(this.oldSelect);
}

private changeOrderOfObject(object: Object3D<Event>, newIndex: number, oldIndex: number) {
if (newIndex === oldIndex)
private changeOrderOfObject(newIndex: number, oldIndex: number) {
if (oldIndex === newIndex)
return console.warn('ChangeObjectOrderCommand: oldIndex and newIndex are the same.');

const offset = newIndex >= oldIndex ? -1 : 0;
this.parent.children.splice(oldIndex, 1);
this.parent.children.splice(newIndex + offset, 0, object);
const element = this.parent.children.splice(oldIndex, 1)[0];
if (element !== this.object) throw new Error('Object not in expected position.');

this.parent.children.splice(newIndex, 0, this.object);

this.editor.signals.objectChanged.dispatch(this.parent, 'children');
this.editor.signals.sceneGraphChanged.dispatch();
}

toJSON() {
const output = super.toJSON();

output.object = this.object.uuid;
output.oldIndex = this.oldIndex;
output.newIndex = this.newIndex;
const output: ChangeObjectOrderCommandJSON = {
objectUuid: this.object.uuid,
oldIndex: this.oldIndex,
newIndex: this.newIndex,
selectedUuid: this.editor.selected ? this.editor.selected.uuid : ''
};

return output;
return { ...super.toJSON(), output };
}

fromJSON(json: { object: { object: { uuid: string } }; oldIndex: number; newIndex: number }) {
fromJSON(json: ChangeObjectOrderCommandJSON) {
super.fromJSON(json);
const newObject = this.editor.objectByUuid(json.object.object.uuid);
if (newObject) this.object = newObject;

const found = this.editor.objectByUuid(json.objectUuid);

if (!found) throw new Error('The object was not found in the scene.');

this.object = found;
this.oldIndex = json.oldIndex;
this.newIndex = json.newIndex;

this.oldSelect = this.editor.objectByUuid(json.selectedUuid) ?? null;
}
}
20 changes: 19 additions & 1 deletion src/util/CounterMap/CounterMap.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
export class CounterMapError<K extends string> extends Error {
type = 'CounterMapError';

constructor(message: string, public readonly origin: { origin: string; key: K }) {
super(message);
}
}

export const isCounterMapError = <T extends string>(
error: unknown
): error is CounterMapError<T> => {
return error instanceof CounterMapError;
};

export class CounterMap<K extends string> {
private map: Map<K, number> = new Map();

Expand All @@ -15,7 +29,11 @@ export class CounterMap<K extends string> {
decrement(key: K): number {
const lastCount = this.map.get(key);

if (!lastCount) throw Error(`No item to decrement for key: ${key}`);
if (!lastCount)
throw new CounterMapError(`No item to decrement for key: ${key}`, {
origin: 'NoItemToDecrement',
key: key
});
const newValue = lastCount - 1;
this.map.set(key, newValue);
return newValue;
Expand Down