Skip to content

Commit

Permalink
fix: review fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
alvrs committed Jul 2, 2022
1 parent d68fc7c commit 845e77c
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 123 deletions.
88 changes: 50 additions & 38 deletions packages/ecs-browser/src/Browser.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,62 @@
import React, { useState } from "react";
import { Layers, Type, Component, Schema, World, EntityID } from "@latticexyz/recs";
import { BrowserContainer } from "./StyledComponents";
import { BrowserContainer, SmallHeadline } from "./StyledComponents";
import { SetContractComponentFunction } from "./types";
import { EntityEditor } from "./EntityEditor";
import { QueryBuilder } from "./QueryBuilder";
import { useClearDevHighlights } from "./hooks";
import { observer } from "mobx-react-lite";

/**
* An Entity Browser for viewing/editiing Component values.
*/
export const Browser = ({
entities,
layers,
setContractComponentValue,
devHighlightComponent,
hoverHighlightComponent,
world,
}: {
entities: EntityID[];
layers: Layers;
setContractComponentValue: SetContractComponentFunction<Schema>;
devHighlightComponent: Component<{ value: Type.OptionalNumber }>;
hoverHighlightComponent: Component<{ x: Type.OptionalNumber; y: Type.OptionalNumber; }>;
world: World;
}) => {
const [filteredEntities, setFilteredEntities] = useState<EntityID[]>([]);
export const Browser = observer(
({
entities,
layers,
setContractComponentValue,
devHighlightComponent,
hoverHighlightComponent,
world,
}: {
entities: EntityID[];
layers: Layers;
setContractComponentValue: SetContractComponentFunction<Schema>;
devHighlightComponent: Component<{ value: Type.OptionalNumber }>;
hoverHighlightComponent: Component<{ x: Type.OptionalNumber; y: Type.OptionalNumber }>;
world: World;
}) => {
const [filteredEntities, setFilteredEntities] = useState<EntityID[]>([]);
const [overflow, setOverflow] = useState(0);
const clearDevHighlights = useClearDevHighlights(devHighlightComponent);

return (
<BrowserContainer>
<QueryBuilder
devHighlightComponent={devHighlightComponent}
hoverHighlightComponent={hoverHighlightComponent}
allEntities={entities}
setFilteredEntities={setFilteredEntities}
layers={layers}
world={world}
/>
{filteredEntities.map((entity) => (
<EntityEditor
world={world}
key={`entity-editor-${entity}`}
entityId={entity}
layers={layers}
setContractComponentValue={setContractComponentValue}
return (
<BrowserContainer>
<QueryBuilder
devHighlightComponent={devHighlightComponent}
hoverHighlightComponent={hoverHighlightComponent}
allEntities={entities}
setFilteredEntities={setFilteredEntities}
layers={layers}
world={world}
clearDevHighlights={clearDevHighlights}
setOverflow={setOverflow}
/>
))}
</BrowserContainer>
);
};
<SmallHeadline>
Showing {filteredEntities.length} of {filteredEntities.length + overflow} entities
</SmallHeadline>
{filteredEntities.map((entity) => (
<EntityEditor
world={world}
key={`entity-editor-${entity}`}
entityId={entity}
layers={layers}
setContractComponentValue={setContractComponentValue}
devHighlightComponent={devHighlightComponent}
clearDevHighlights={clearDevHighlights}
/>
))}
</BrowserContainer>
);
}
);
126 changes: 59 additions & 67 deletions packages/ecs-browser/src/EntityEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import {
Layers,
removeComponent,
setComponent,
Type,
AnyComponent,
Expand All @@ -10,77 +9,70 @@ import {
World,
EntityID,
getEntityComponents,
Has,
defineQuery,
} from "@latticexyz/recs";
import { Collapse } from "react-collapse";
import { ComponentBrowserButton, EntityEditorContainer } from "./StyledComponents";
import { SetContractComponentFunction } from "./types";
import { ComponentEditor } from "./ComponentEditor";
import { observer } from "mobx-react-lite";

export const EntityEditor = observer(({
entityId,
layers,
setContractComponentValue,
devHighlightComponent,
world,
}: {
entityId: EntityID;
layers: Layers;
setContractComponentValue: SetContractComponentFunction<Schema>;
devHighlightComponent: Component<{ value: Type.OptionalNumber }>;
world: World;
}) => {
const [opened, setOpened] = useState(false);
const devHighlightedEntities = defineQuery([Has(devHighlightComponent)], { runOnInit: true }).matching;
const entity = world.entityToIndex.get(entityId);
if (!entity) return null;
export const EntityEditor = observer(
({
entityId,
layers,
setContractComponentValue,
devHighlightComponent,
world,
clearDevHighlights,
}: {
entityId: EntityID;
layers: Layers;
setContractComponentValue: SetContractComponentFunction<Schema>;
devHighlightComponent: Component<{ value: Type.OptionalNumber }>;
world: World;
clearDevHighlights: () => void;
}) => {
const [opened, setOpened] = useState(false);
const entity = world.entityToIndex.get(entityId);
if (!entity) return null;

const clearPreviousDevHighlights = useCallback(() => {
if(!devHighlightedEntities) return;
[...devHighlightedEntities].forEach((e) => removeComponent(devHighlightComponent, e));
}, [devHighlightedEntities]);
const [entityComponents, setEntityComponents] = useState<AnyComponent[]>([]);
useEffect(() => {
if (opened) {
const components = getEntityComponents(world, entity);
setEntityComponents(components);
}
}, [opened, world, entity, setEntityComponents]);

const [entityComponents, setEntityComponents] = useState<AnyComponent[]>([]);
useEffect(() => {
if (opened) {
const components = getEntityComponents(world, entity);
setEntityComponents(components);
}
}, [opened, world, entity, setEntityComponents]);

return (
<EntityEditorContainer
onMouseEnter={() => {
clearPreviousDevHighlights();
setComponent(devHighlightComponent, entity, {
value: null,
});
}}
onMouseLeave={() => {
removeComponent(devHighlightComponent, entity);
}}
>
<div onClick={() => setOpened(!opened)} style={{ cursor: "pointer" }}>
<h3 style={{ color: "white" }}>{world.entities[entity]}</h3>
<ComponentBrowserButton onClick={() => setOpened(!opened)}>
{opened ? <>&#9660;</> : <>&#9654;</>}
</ComponentBrowserButton>
</div>
<Collapse isOpened={opened}>
{[...entityComponents.values()]
.filter((c) => c.id !== devHighlightComponent.id)
.map((c) => (
<ComponentEditor
key={`component-editor-${entity}-${c.id}`}
entity={entity}
component={c}
layers={layers}
setContractComponentValue={setContractComponentValue}
/>
))}
</Collapse>
</EntityEditorContainer>
);
});
return (
<EntityEditorContainer
onMouseEnter={() => {
clearDevHighlights();
setComponent(devHighlightComponent, entity, {
value: null,
});
}}
>
<div onClick={() => setOpened(!opened)} style={{ cursor: "pointer" }}>
<h3 style={{ color: "white" }}>{world.entities[entity]}</h3>
<ComponentBrowserButton onClick={() => setOpened(!opened)}>
{opened ? <>&#9660;</> : <>&#9654;</>}
</ComponentBrowserButton>
</div>
<Collapse isOpened={opened}>
{[...entityComponents.values()]
.filter((c) => c.id !== devHighlightComponent.id)
.map((c) => (
<ComponentEditor
key={`component-editor-${entity}-${c.id}`}
entity={entity}
component={c}
layers={layers}
setContractComponentValue={setContractComponentValue}
/>
))}
</Collapse>
</EntityEditorContainer>
);
}
);
55 changes: 39 additions & 16 deletions packages/ecs-browser/src/QueryBuilder/QueryBuilder.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState, useRef, useCallback, useEffect } from "react";
import {
Layers,
removeComponent,
setComponent,
Type,
AnyComponent,
Expand All @@ -16,24 +15,30 @@ import { QueryBuilderForm, QueryShortcutContainer } from "./StyledComponents";
import * as recs from "@latticexyz/recs";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { dracula } from "react-syntax-highlighter/dist/esm/styles/prism";
import { flatten, orderBy } from "lodash";
import { flatten, orderBy, throttle } from "lodash";
import { PositionFilterButton } from "./PositionFilterButton";
import { MAX_ENTITIES } from "../constants";
import { observe } from "mobx";

export const QueryBuilder = function ({
export const QueryBuilder = ({
allEntities,
setFilteredEntities,
layers,
world,
devHighlightComponent,
hoverHighlightComponent,
clearDevHighlights,
setOverflow,
}: {
world: World;
layers: Layers;
allEntities: EntityID[];
setFilteredEntities: (es: EntityID[]) => void;
devHighlightComponent: Component<{ value: Type.OptionalNumber }>;
hoverHighlightComponent: Component<{ x: Type.OptionalNumber; y: Type.OptionalNumber }>;
}) {
clearDevHighlights: () => void;
setOverflow: (overflow: number) => void;
}) => {
const queryInputRef = useRef<HTMLInputElement>(null);
const [componentFilters, setComponentFilters] = useState<AnyComponent[]>([]);
const [isManuallyEditing, setIsManuallyEditing] = useState(true);
Expand Down Expand Up @@ -73,6 +78,14 @@ export const QueryBuilder = function ({
setComponentFilters([]);
}, []);

const cancelObserver = useRef<() => void>(() => void 0);
// Cancel outstanding observers on unmount
useEffect(() => {
return () => {
if (cancelObserver.current) cancelObserver.current();
};
}, []);

const executeFilter = useCallback(
(e: React.SyntheticEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -103,11 +116,27 @@ export const QueryBuilder = function ({
throw new Error("Invalid query");
}

const selectedEntities = defineQuery(queryArray, { runOnInit: true }).matching;
setFilteredEntities([...selectedEntities].map((idx) => world.entities[idx]));

selectedEntities.forEach((idx) => removeComponent(devHighlightComponent, idx));
selectedEntities.forEach((idx) => setComponent(devHighlightComponent, idx, { value: 0x0000ff }));
cancelObserver.current();
const queryResult = defineQuery(queryArray, { runOnInit: true });
const subscription = queryResult.update$.subscribe();
const selectEntities = throttle(
() => {
const selectedEntities = [...queryResult.matching].slice(0, MAX_ENTITIES);
setOverflow(queryResult.matching.size - selectedEntities.length);
setFilteredEntities(selectedEntities.map((idx) => world.entities[idx]));
clearDevHighlights();
selectedEntities.forEach((idx) => setComponent(devHighlightComponent, idx, { value: 0x0000ff }));
},
1000,
{ leading: true }
);
selectEntities();
const cancelObserve = observe(queryResult.matching, selectEntities);
cancelObserver.current = () => {
cancelObserve();
selectEntities.cancel();
subscription?.unsubscribe();
};
} catch (e: unknown) {
setErrorMessage((e as Error).message);
console.error(e);
Expand All @@ -118,12 +147,7 @@ export const QueryBuilder = function ({

return (
<>
<QueryBuilderForm
onSubmit={(e) => {
e.preventDefault();
queryInputRef.current?.blur();
}}
>
<QueryBuilderForm onSubmit={executeFilter}>
<SyntaxHighlighter wrapLongLines language="javascript" style={dracula}>
{entityQueryText}
</SyntaxHighlighter>
Expand All @@ -147,7 +171,6 @@ export const QueryBuilder = function ({
editQuery(e.target.value);
}}
onFocus={(e) => e.target.select()}
onBlur={(e) => executeFilter(e)}
/>
</QueryBuilderForm>

Expand Down
5 changes: 5 additions & 0 deletions packages/ecs-browser/src/StyledComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,8 @@ export const DraggableNumberLabelContainer = styled.label`
user-select: none;
color: #8c91a0;
`;

export const SmallHeadline = styled.p`
padding: 8px;
font-size: 14px;
`;
1 change: 1 addition & 0 deletions packages/ecs-browser/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MAX_ENTITIES = 1000;
21 changes: 21 additions & 0 deletions packages/ecs-browser/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Component, defineQuery, EntityIndex, Has, removeComponent } from "@latticexyz/recs";
import { ObservableSet } from "mobx";
import { useCallback, useEffect, useRef } from "react";

export function useClearDevHighlights(devHighlightComponent: Component) {
const highlightedEntities = useRef<ObservableSet<EntityIndex>>();

useEffect(() => {
const queryResult = defineQuery([Has(devHighlightComponent)], { runOnInit: true });
const subscription = queryResult.update$.subscribe();
highlightedEntities.current = queryResult.matching;
return () => subscription?.unsubscribe();
}, []);

return useCallback(() => {
if (!highlightedEntities.current) return;
for (const entity of highlightedEntities.current) {
removeComponent(devHighlightComponent, entity);
}
}, [highlightedEntities]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export async function createNetworkLayer(config?: NetworkLayerConfig) {
Stamina: defineComponent(
world,
{ current: Type.Number, max: Type.Number, regeneration: Type.Number },
{ id: "Stamina", metadata: { contractId: keccak256("ember.component.staminaComponent") } }
{ id: "Stamina", metadata: { contractId: keccak256("ember.component.personaComponent") } }
),
LastActionTurn: defineComponent(
world,
Expand Down

0 comments on commit 845e77c

Please sign in to comment.