Skip to content

Commit

Permalink
feat: 3D Resources layering react component (#3495)
Browse files Browse the repository at this point in the history
* Initial layering component for Cad model

* Added sliders and adjusted UI for submenu

* Fixed lint error

* fixed 360 image visibility issue

* Improved UI when one of the 3D resources is not available

* removed opacity from 3D resources layers option

* removed additional elements which was not needed

* added indeterminate option for checkbox and exposed LayerButton

* Update react-components/src/components/RevealToolbar/LayersContainer/elements.ts

Co-authored-by: Savelii Novikov <45129444+Savokr@users.noreply.github.com>

* Update react-components/src/components/RevealToolbar/LayersContainer/LayersContainer.tsx

Co-authored-by: Savelii Novikov <45129444+Savokr@users.noreply.github.com>

* Update react-components/src/components/RevealToolbar/LayersContainer/LayersContainer.tsx

Co-authored-by: Savelii Novikov <45129444+Savokr@users.noreply.github.com>

* Added model name & number of models in each 3D resources UI

* updated component key to lodash/uniqueId

* Update react-components/src/components/RevealToolbar/LayersContainer/CadModelLayersContainer.tsx

Co-authored-by: Deep <70804363+deep-cognite@users.noreply.github.com>

* Update react-components/src/components/RevealToolbar/LayersContainer/elements.ts

Co-authored-by: Savelii Novikov <45129444+Savokr@users.noreply.github.com>

* Update react-components/src/components/RevealToolbar/LayersContainer/CadModelLayersContainer.tsx

Co-authored-by: Savelii Novikov <45129444+Savokr@users.noreply.github.com>

* Update react-components/src/components/RevealToolbar/LayersContainer/Image360LayersContainer.tsx

Co-authored-by: Deep <70804363+deep-cognite@users.noreply.github.com>

* Update react-components/src/hooks/use3DModelName.tsx

Co-authored-by: Deep <70804363+deep-cognite@users.noreply.github.com>

* addresed review comments

* Updated pointCloud & 360 image count number styling

---------

Co-authored-by: cognite-bulldozer[bot] <51074376+cognite-bulldozer[bot]@users.noreply.github.com>
Co-authored-by: Savelii Novikov <45129444+Savokr@users.noreply.github.com>
Co-authored-by: Deep <70804363+deep-cognite@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 24, 2023
1 parent 209918d commit a4be2db
Show file tree
Hide file tree
Showing 10 changed files with 489 additions and 3 deletions.
30 changes: 30 additions & 0 deletions react-components/src/components/RevealToolbar/LayersButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*!
* Copyright 2023 Cognite AS
*/

import { type ReactElement, useState } from 'react';
import { Button, Dropdown } from '@cognite/cogs.js';
import { LayersContainer } from './LayersContainer/LayersContainer';

export const LayersButton = (): ReactElement => {
const [layersEnabled, setLayersEnabled] = useState(false);
const showLayers = (): void => {
setLayersEnabled(!layersEnabled);
};

return (
<Dropdown
appendTo={document.body}
content={<LayersContainer />}
visible={layersEnabled}
placement="auto">
<Button
type="ghost"
toggled={layersEnabled}
icon="Layers"
aria-label="3D Resource layers"
onClick={showLayers}
/>
</Dropdown>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*!
* Copyright 2023 Cognite AS
*/

import React, { type ReactElement, useState } from 'react';
import { useReveal } from '../../RevealContainer/RevealContext';
import { type CogniteCadModel } from '@cognite/reveal';
import { Checkbox, Flex, Menu } from '@cognite/cogs.js';
import { StyledChipCount, StyledLabel, StyledSubMenu } from './elements';
import { use3DModelName } from '../../../hooks/use3DModelName';
import uniqueId from 'lodash/uniqueId';

export const CadModelLayersContainer = (): ReactElement => {
const viewer = useReveal();
const cadModels = viewer.models.filter((model) => model.type === 'cad');
const cadModelIds = cadModels.map((model) => model.modelId);

const modelName = use3DModelName(cadModelIds);

const [selectedCadModels, setSelectedCadModels] = useState<
Array<{ model: CogniteCadModel; isToggled: boolean; name: string }>
>(
cadModels.map((model, index) => ({
model: model as CogniteCadModel,
isToggled: (model as CogniteCadModel).visible,
name: modelName?.data?.[index] ?? 'No model name'
}))
);

const [allCadModelVisible, setAllCadModelVisible] = useState(true);
const [indeterminate, setIndeterminate] = useState<boolean>(false);

const count = selectedCadModels.length.toString();

const handleCadModelVisibility = (model: CogniteCadModel): void => {
selectedCadModels.map((data) => {
if (data.model === model) {
return {
...data,
isToggled: !data.isToggled
};
} else {
return data;
}
});
model.visible = !model.visible;
viewer.requestRedraw();
setSelectedCadModels([...selectedCadModels]);
setIndeterminate(selectedCadModels.some((data) => !data.isToggled));
setAllCadModelVisible(!selectedCadModels.every((data) => !data.isToggled));
};

const handleAllCadModelsVisibility = (visible: boolean): void => {
selectedCadModels.forEach((data) => {
data.isToggled = visible;
data.model.visible = visible;
});
viewer.requestRedraw();
setAllCadModelVisible(visible);
setIndeterminate(false);
setSelectedCadModels([...selectedCadModels]);
};

const cadModelContent = (): React.JSX.Element => {
return (
<StyledSubMenu>
{selectedCadModels.map((data) => (
<Menu.Item
key={uniqueId()}
hasCheckbox
checkboxProps={{
checked: data.isToggled,
onChange: (e: { stopPropagation: () => void }) => {
e.stopPropagation();
handleCadModelVisibility(data.model);
}
}}>
{data.name}
</Menu.Item>
))}
</StyledSubMenu>
);
};

return (
<Menu.Submenu content={cadModelContent()} title="CAD models">
<Flex direction="row" justifyContent="space-between" gap={4}>
<Checkbox
checked={allCadModelVisible}
indeterminate={indeterminate}
onChange={(e, c) => {
e.stopPropagation();
handleAllCadModelsVisibility(c as boolean);
}}
/>
<StyledLabel> CAD models </StyledLabel>
<StyledChipCount label={count} hideTooltip type="neutral" />
</Flex>
</Menu.Submenu>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*!
* Copyright 2023 Cognite AS
*/

import React, { type ReactElement, useState } from 'react';
import { useReveal } from '../../RevealContainer/RevealContext';
import { Checkbox, Flex, Menu } from '@cognite/cogs.js';
import { StyledChipCount, StyledLabel, StyledSubMenu } from './elements';
import { type Image360Collection } from '@cognite/reveal';
import uniqueId from 'lodash/uniqueId';

export const Image360CollectionLayerContainer = (): ReactElement => {
const viewer = useReveal();
const image360Collection = viewer.get360ImageCollections();

const [selectedImage360Collection, setSelectedImage360Collection] = useState<
Array<{ image360: Image360Collection; isToggled: boolean }>
>(
image360Collection.map((image360Collection) => ({
image360: image360Collection,
isToggled: true
}))
);

const [all360ImagesVisible, setAll360ImagesVisible] = useState(true);
const [indeterminate, setIndeterminate] = useState<boolean>(false);

const count = image360Collection.length.toString();

const handle360ImagesVisibility = (image360: Image360Collection): void => {
selectedImage360Collection.map((data) => {
if (data.image360 === image360) {
data.isToggled = !data.isToggled;
image360.setIconsVisibility(data.isToggled);
}
return data;
});
viewer.requestRedraw();
setSelectedImage360Collection([...selectedImage360Collection]);
setIndeterminate(selectedImage360Collection.some((data) => !data.isToggled));
setAll360ImagesVisible(!selectedImage360Collection.every((data) => !data.isToggled));
};

const handleAll360ImagesVisibility = (visible: boolean): void => {
[...selectedImage360Collection].forEach((data) => {
data.isToggled = visible;
data.image360.setIconsVisibility(data.isToggled);
});
viewer.requestRedraw();
setAll360ImagesVisible(visible);
setIndeterminate(false);
setSelectedImage360Collection([...selectedImage360Collection]);
};

const image360Content = (): React.JSX.Element => {
return (
<StyledSubMenu>
{selectedImage360Collection.map((data) => (
<Menu.Item
key={uniqueId()}
hasCheckbox
checkboxProps={{
checked: data.isToggled,
onChange: (e: { stopPropagation: () => void }) => {
e.stopPropagation();
handle360ImagesVisibility(data.image360);
}
}}>
{data.image360.label}
</Menu.Item>
))}
</StyledSubMenu>
);
};

return (
<Menu.Submenu content={image360Content()} title="360 images">
<Flex direction="row" justifyContent="space-between">
<Checkbox
checked={all360ImagesVisible}
indeterminate={indeterminate}
onChange={(e, c) => {
e.stopPropagation();
handleAll360ImagesVisibility(c as boolean);
}}
/>
<StyledLabel> 360 images </StyledLabel>
<StyledChipCount label={count} hideTooltip type="neutral" />
</Flex>
</Menu.Submenu>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*!
* Copyright 2023 Cognite AS
*/

import { type ReactElement } from 'react';

import { Menu } from '@cognite/cogs.js';
import styled from 'styled-components';

import { CadModelLayersContainer } from './CadModelLayersContainer';
import { PointCloudLayersContainer } from './PointCloudLayersContainer';
import { Image360CollectionLayerContainer } from './Image360LayersContainer';

export const LayersContainer = (): ReactElement => {
return (
<Container>
<StyledMenu>
<CadModelLayersContainer />
<PointCloudLayersContainer />
<Image360CollectionLayerContainer />
</StyledMenu>
</Container>
);
};

const Container = styled.div`
position: relative;
top: 40px;
`;

const StyledMenu = styled(Menu)`
padding: 6px;
width: 214px;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*!
* Copyright 2023 Cognite AS
*/

import React, { type ReactElement, useState } from 'react';

import { useReveal } from '../../RevealContainer/RevealContext';
import { Checkbox, Flex, Menu } from '@cognite/cogs.js';
import { StyledChipCount, StyledLabel, StyledSubMenu } from './elements';
import { type CognitePointCloudModel } from '@cognite/reveal';
import { use3DModelName } from '../../../hooks/use3DModelName';
import uniqueId from 'lodash/uniqueId';

export const PointCloudLayersContainer = (): ReactElement => {
const viewer = useReveal();
const pointCloudModels = viewer.models.filter((model) => model.type === 'pointcloud');
const pointCloudModelIds = pointCloudModels.map((model) => model.modelId);

const modelName = use3DModelName(pointCloudModelIds);

const [selectedPointCloudModels, setSelectedPointCloudModels] = useState<
Array<{ model: CognitePointCloudModel; isToggled: boolean; name: string }>
>(
pointCloudModels.map((model, index) => ({
model: model as CognitePointCloudModel,
isToggled: (model as CognitePointCloudModel).getDefaultPointCloudAppearance().visible ?? true,
name: modelName?.data?.[index] ?? 'No model name'
}))
);

const [allPointCloudModelVisible, setAllPointCloudModelVisible] = useState(true);
const [indeterminate, setIndeterminate] = useState<boolean>(false);

const count = pointCloudModels.length.toString();

const handlePointCloudVisibility = (model: CognitePointCloudModel): void => {
selectedPointCloudModels.map((data) => {
if (data.model === model) {
data.isToggled = !data.isToggled;
model.setDefaultPointCloudAppearance({ visible: data.isToggled });
}
return data;
});
viewer.requestRedraw();
setSelectedPointCloudModels([...selectedPointCloudModels]);
setIndeterminate(selectedPointCloudModels.some((data) => !data.isToggled));
setAllPointCloudModelVisible(!selectedPointCloudModels.every((data) => !data.isToggled));
};

const handleAllPointCloudModelsVisibility = (visible: boolean): void => {
selectedPointCloudModels.forEach((data) => {
data.isToggled = visible;
data.model.setDefaultPointCloudAppearance({ visible });
});
viewer.requestRedraw();
setAllPointCloudModelVisible(visible);
setSelectedPointCloudModels([...selectedPointCloudModels]);
};

const pointCloudModelContent = (): React.JSX.Element => {
return (
<StyledSubMenu>
{selectedPointCloudModels.map((data) => (
<Menu.Item
key={uniqueId()}
hasCheckbox
checkboxProps={{
checked: data.isToggled,
onChange: (e: { stopPropagation: () => void }) => {
e.stopPropagation();
handlePointCloudVisibility(data.model);
}
}}>
{data.name}
</Menu.Item>
))}
</StyledSubMenu>
);
};

return (
<Menu.Submenu content={pointCloudModelContent()} title="Point clouds">
<Flex direction="row" justifyContent="space-between">
<Checkbox
checked={allPointCloudModelVisible}
indeterminate={indeterminate}
onChange={(e, c) => {
e.stopPropagation();
handleAllPointCloudModelsVisibility(c as boolean);
}}
/>
<StyledLabel> Point clouds </StyledLabel>
<StyledChipCount label={count} hideTooltip type="neutral" />
</Flex>
</Menu.Submenu>
);
};
Loading

0 comments on commit a4be2db

Please sign in to comment.