-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 3D Resources layering react component (#3495)
* 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
1 parent
209918d
commit a4be2db
Showing
10 changed files
with
489 additions
and
3 deletions.
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
react-components/src/components/RevealToolbar/LayersButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
101 changes: 101 additions & 0 deletions
101
react-components/src/components/RevealToolbar/LayersContainer/CadModelLayersContainer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
92 changes: 92 additions & 0 deletions
92
react-components/src/components/RevealToolbar/LayersContainer/Image360LayersContainer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
34 changes: 34 additions & 0 deletions
34
react-components/src/components/RevealToolbar/LayersContainer/LayersContainer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
`; |
97 changes: 97 additions & 0 deletions
97
react-components/src/components/RevealToolbar/LayersContainer/PointCloudLayersContainer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
Oops, something went wrong.