Skip to content

Commit

Permalink
feat(composer): render tags from layer
Browse files Browse the repository at this point in the history
  • Loading branch information
sheilaXu authored and mumanity committed Sep 14, 2023
1 parent 391eddd commit d9c5191
Show file tree
Hide file tree
Showing 39 changed files with 1,493 additions and 200 deletions.
4 changes: 4 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/scene-composer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"convert-svg": "npx @svgr/cli --out-dir src/assets/auto-gen/icons/ --typescript --index-template tools/index-template.js -- src/assets/icons/",
"release": "run-s compile copy-assets",
"copy-assets": "copyfiles -e \"**/*.tsx\" -e \"**/*.ts\" -e \"**/*.snap\" -e \"**/*.js\" -e \"**/*.jsx\" -e \"**/*.json\" \"src/**/*\" dist/",
"lint": "eslint . --max-warnings=636",
"lint": "eslint . --max-warnings=625",
"fix": "eslint --fix .",
"test": "jest --config jest.config.ts --coverage --silent",
"test:dev": "jest --config jest.config.ts --coverage",
Expand All @@ -73,6 +73,7 @@
},
"devDependencies": {
"@aws-sdk/credential-providers": "3.395.0",
"@aws-sdk/types": "^3.310.0",
"@awsui/jest-preset": "^2.0.20",
"@babel/preset-env": "^7.22.5",
"@babel/preset-react": "^7.22.5",
Expand Down Expand Up @@ -148,6 +149,7 @@
"@react-three/fiber": "^8.13.3",
"@react-three/postprocessing": "2.6.2",
"@react-three/test-renderer": "^8.1.3",
"@tanstack/react-query": "^4.29.15",
"@tweenjs/tween.js": "^20.0.3",
"3d-tiles-renderer": "^0.3.16",
"arraybuffer-to-string": "1.0.2",
Expand Down
1 change: 1 addition & 0 deletions packages/scene-composer/src/common/entityModelConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const componentTypeToId: Record<KnownComponentType, string> = {
EntityBinding: NODE_COMPONENT_TYPE_ID, // EntityBinding is saved at node component
};
export const DEFAULT_ENTITY_BINDING_RELATIONSHIP_NAME = 'isVisualOf';
export const DEFAULT_PARENT_RELATIONSHIP_NAME = 'isChildOf';
export const DEFAULT_NODE_COMPONENT_NAME = 'Node';
export const SCENE_ROOT_ENTITY_ID = 'SCENES_EntityId';

Expand Down
138 changes: 138 additions & 0 deletions packages/scene-composer/src/components/SceneLayers.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { useQuery } from '@tanstack/react-query';

import { useStore } from '../store';
import { processQueries } from '../utils/entityModelUtils/processQueries';

import { SceneLayers } from './SceneLayers';

jest.mock('../utils/entityModelUtils/processQueries', () => ({
processQueries: jest.fn(),
}));

jest.mock('@tanstack/react-query', () => ({
useQuery: jest.fn(),
}));

describe('SceneLayers', () => {
const renderSceneNodesFromLayersMock = jest.fn();
const isViewingMock = jest.fn();
const getScenePropertyMock = jest.fn();
const baseState = {
getSceneProperty: getScenePropertyMock,
renderSceneNodesFromLayers: renderSceneNodesFromLayersMock,
isViewing: isViewingMock,
};

beforeEach(() => {
jest.clearAllMocks();

isViewingMock.mockReturnValue(true);
getScenePropertyMock.mockReturnValue(['layer1', 'layer2']);
});

it('should enable query when have layerId', async () => {
useStore('default').setState(baseState);
let enabledValue = false;
(useQuery as jest.Mock).mockImplementation(({ enabled, ..._ }) => {
enabledValue = enabled;
return {};
});

render(<SceneLayers />);

expect(enabledValue).toBe(true);
});

it('should not enable query when no layerId', async () => {
useStore('default').setState(baseState);
getScenePropertyMock.mockReturnValue([]);

let enabledValue = true;
(useQuery as jest.Mock).mockImplementation(({ enabled, ..._ }) => {
enabledValue = enabled;
return {};
});

render(<SceneLayers />);

expect(enabledValue).toBe(false);
});

it('should not refetch when not in viewing mode', async () => {
useStore('default').setState(baseState);
isViewingMock.mockReturnValue(false);
let interval;
(useQuery as jest.Mock).mockImplementation(({ refetchInterval, ..._ }) => {
interval = refetchInterval(undefined, { state: {} });
return {};
});

render(<SceneLayers />);

expect(interval).toBe(0);
});

it('should not refetch when previous query failed', async () => {
useStore('default').setState(baseState);
isViewingMock.mockReturnValue(false);
let interval;
(useQuery as jest.Mock).mockImplementation(({ refetchInterval, ..._ }) => {
interval = refetchInterval(undefined, { state: { error: 'error' } });
return {};
});

render(<SceneLayers />);

expect(interval).toBe(0);
});

it('should refetch with expected interval', async () => {
useStore('default').setState(baseState);
let interval;
(useQuery as jest.Mock).mockImplementation(({ refetchInterval, ..._ }) => {
interval = refetchInterval(undefined, { state: {} });
return {};
});

render(<SceneLayers />);

expect(interval).toBe(10 * 1000);
});

it('should call processQueries with expected data', async () => {
useStore('default').setState(baseState);
let queryFunction;
(useQuery as jest.Mock).mockImplementation(({ queryFn, ..._ }) => {
queryFunction = queryFn;
return {};
});

render(<SceneLayers />);
await queryFunction();

expect(processQueries as jest.Mock).toBeCalledTimes(1);
expect(processQueries as jest.Mock).toBeCalledWith(
[expect.stringContaining("AND e.entityId = 'layer1'")],
expect.anything(),
);
expect(renderSceneNodesFromLayersMock).not.toBeCalled();
});

it('should call processQueries with expected data', async () => {
useStore('default').setState(baseState);
let queryFunction;
(useQuery as jest.Mock).mockImplementation(({ queryFn, ..._ }) => {
queryFunction = queryFn;
return { data: ['random'] };
});

render(<SceneLayers />);
await queryFunction();

expect(processQueries as jest.Mock).toBeCalledTimes(1);
expect(renderSceneNodesFromLayersMock).toBeCalledTimes(1);
expect(renderSceneNodesFromLayersMock).toBeCalledWith(['random'], 'layer1');
});
});
47 changes: 47 additions & 0 deletions packages/scene-composer/src/components/SceneLayers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { useContext, useEffect } from 'react';
import { isEmpty } from 'lodash';
import { useQuery } from '@tanstack/react-query';

import { sceneComposerIdContext } from '../common/sceneComposerIdContext';
import { useStore } from '../store';
import { processQueries } from '../utils/entityModelUtils/processQueries';
import { KnownSceneProperty } from '../interfaces';
import { DEFAULT_LAYER_RELATIONSHIP_NAME } from '../common/entityModelConstants';

export const SceneLayers: React.FC = () => {
const sceneComposerId = useContext(sceneComposerIdContext);
const isViewing = useStore(sceneComposerId)((state) => state.isViewing());

const renderSceneNodesFromLayers = useStore(sceneComposerId)((state) => state.renderSceneNodesFromLayers);
const layerIds = useStore(sceneComposerId)((state) => state.getSceneProperty<string[]>(KnownSceneProperty.LayerIds));
const layerId = layerIds?.[0];

const nodes = useQuery({
enabled: !isEmpty(layerIds),
queryKey: ['scene-layers', layerIds, sceneComposerId],
queryFn: async () => {
const nodes = await processQueries(
[
`SELECT entity, r, e
FROM EntityGraph
MATCH (entity)-[r]->(e)
WHERE r.relationshipName = '${DEFAULT_LAYER_RELATIONSHIP_NAME}'
AND e.entityId = '${layerId}'`,
],
(node) => (node.properties.layerIds = [...(node.properties.layerIds ?? []), layerId!]),
);
return nodes;
},
refetchInterval: (_, query) => {
return !query.state.error && isViewing ? 10 * 1000 : 0;
},
});

useEffect(() => {
if (nodes.data) {
renderSceneNodesFromLayers(nodes.data, layerId!);
}
}, [nodes.data, renderSceneNodesFromLayers]);

return <></>;
};
11 changes: 11 additions & 0 deletions packages/scene-composer/src/components/WebGLCanvasManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useContext, useEffect, useRef } from 'react';
import { GizmoHelper, GizmoViewport } from '@react-three/drei';
import { ThreeEvent, useThree } from '@react-three/fiber';
import { MatterportModel } from '@matterport/r3f/dist';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import { KnownSceneProperty } from '../interfaces';
import useLifecycleLogging from '../logger/react-logger/hooks/useLifecycleLogging';
Expand All @@ -17,6 +18,7 @@ import { getIntersectionTransform } from '../utils/raycastUtils';
import { createNodeWithPositionAndNormal } from '../utils/nodeUtils';
import { EnvironmentLoadingManager } from '../common/loadingManagers';
import useMatterportViewer from '../hooks/useMatterportViewer';
import useDynamicScene from '../hooks/useDynamicScene';

import Environment, { presets } from './three-fiber/Environment';
import { StatsWindow } from './three-fiber/StatsWindow';
Expand All @@ -25,6 +27,9 @@ import { EditorMainCamera } from './three-fiber/EditorCamera';
import { EditorTransformControls } from './three-fiber/EditorTransformControls';
import { SceneInfoView } from './three-fiber/SceneInfoView';
import IntlProvider from './IntlProvider';
import { SceneLayers } from './SceneLayers';

const queryClient = new QueryClient();

const GIZMO_MARGIN: [number, number] = [72, 72];

Expand All @@ -44,6 +49,7 @@ export const WebGLCanvasManager: React.FC = () => {
const domRef = useRef<HTMLElement>(gl.domElement.parentElement);
const environmentPreset = getSceneProperty<string>(KnownSceneProperty.EnvironmentPreset);
const rootNodeRefs = document.rootNodeRefs;
const dynamicSceneEnabled = useDynamicScene();

const editingTargetPlaneRef = useRef(null);
const gridHelperRef = useRef<THREE.GridHelper>(null);
Expand Down Expand Up @@ -84,6 +90,11 @@ export const WebGLCanvasManager: React.FC = () => {
{environmentPreset && environmentPreset in presets && (
<Environment preset={environmentPreset} extensions={envLoaderExtension} />
)}
{dynamicSceneEnabled && (
<QueryClientProvider client={queryClient}>
<SceneLayers />
</QueryClientProvider>
)}
<group name={ROOT_OBJECT_3D_NAME} dispose={null}>
{rootNodeRefs &&
rootNodeRefs.map((rootNodeRef) => {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const MatterportTagSync: React.FC = () => {
try {
const root = await createSceneRootEntity();
rootId = root?.entityId;
setSceneProperty(KnownSceneProperty.SceneRootEntityId, root?.entityId);
setSceneProperty(KnownSceneProperty.SceneRootEntityId, rootId);
} catch (e) {
logger?.error('Create scene root entity error', e);
// TODO: error handling
Expand Down
2 changes: 1 addition & 1 deletion packages/scene-composer/src/hooks/useDynamicScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { COMPOSER_FEATURES } from '../interfaces';

import useFeature from './useFeature';

const useDynamicScene = () => {
const useDynamicScene = (): boolean => {
const enabled = useFeature(COMPOSER_FEATURES.DynamicScene).at(0)?.variation === 'T1';

return !!enabled;
Expand Down

0 comments on commit d9c5191

Please sign in to comment.