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

fix: layer button component bugs #3540

Merged
merged 15 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
158 changes: 146 additions & 12 deletions react-components/src/components/RevealToolbar/LayersButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,163 @@
* Copyright 2023 Cognite AS
*/

import { type ReactElement, useState } from 'react';
import { type ReactElement, useState, useEffect, useMemo, useRef } from 'react';
import { Button, Dropdown } from '@cognite/cogs.js';
import { LayersContainer } from './LayersContainer/LayersContainer';
import { type Reveal3DResourcesLayerStates } from './LayersContainer/types';
import LayersContainer from './LayersContainer/LayersContainer';
import {
type CognitePointCloudModel,
type CogniteCadModel,
type CogniteModel,
type Image360Collection
} from '@cognite/reveal';
import { useReveal } from '../RevealContainer/RevealContext';
import { use3DModelName } from '../../hooks/use3DModelName';
import isEqual from 'lodash/isEqual';

export const LayersButton = (): ReactElement => {
pramodcog marked this conversation as resolved.
Show resolved Hide resolved
const [layersEnabled, setLayersEnabled] = useState(false);
const viewer = useReveal();
const [layersEnabled, setLayersEnabled] = useState<boolean>(false);

const [cadModelIds, setCadModelIds] = useState<number[]>([]);
const [pointCloudModelIds, setPointCloudModelIds] = useState<number[]>([]);
const prevModelsRef = useRef<CogniteModel[]>([]);

const [reveal3DResourcesLayerData, setReveal3DResourcesLayerData] =
useState<Reveal3DResourcesLayerStates>({
cadLayerData: [],
pointCloudLayerData: [],
image360LayerData: []
});

const cadModelName = use3DModelName(cadModelIds);
const pointCloudModelName = use3DModelName(pointCloudModelIds);

const showLayers = (): void => {
setLayersEnabled(!layersEnabled);
};

useEffect(() => {
const currentModels = viewer.models;
// Compare the previous and current models to avoid infinite loop
if (prevModelsRef.current !== currentModels) {
prevModelsRef.current = currentModels;
const cadIds = currentModels
.filter((model) => model.type === 'cad')
.map((model) => model.modelId);
const pointCloudIds = currentModels
.filter((model) => model.type === 'pointcloud')
.map((model) => model.modelId);

// Only update the state when the modelIds change
if (!isEqual(cadModelIds, cadIds)) {
setCadModelIds(cadIds);
}

if (!isEqual(pointCloudModelIds, pointCloudIds)) {
setPointCloudModelIds(pointCloudIds);
}
}
}, [viewer.models, cadModelIds, pointCloudModelIds]);

const updated3DResourcesLayerData: Reveal3DResourcesLayerStates = useMemo(() => {
if (cadModelName.data === null && pointCloudModelName.data === null) {
return {
cadLayerData: [],
pointCloudLayerData: [],
image360LayerData: []
};
}
const updatedCadLayerData = viewer.models
.filter((model): model is CogniteCadModel => model.type === 'cad')
.map((model, index) => fillCadLayerData(model, index));
const updatedPointCloudLayerData = viewer.models
.filter((model): model is CognitePointCloudModel => model.type === 'pointcloud')
.map((model, index) => fillPointCloudLayerData(model, index));
const updatedImage360LayerData = viewer
.get360ImageCollections()
.map((image36Collection) => fillImage360LayerData(image36Collection));

updatedImage360LayerData.forEach((image360LayerData) => {
subcribe360ImageEnterExitMode(image360LayerData);
});

function fillCadLayerData(
cadModel: CogniteCadModel,
index: number
): { model: CogniteCadModel; isToggled: boolean; name: string } {
return {
model: cadModel,
isToggled: cadModel.visible ?? true,
name: cadModelName?.data?.[index] ?? 'No model name'
};
}

function fillPointCloudLayerData(
pointCloudModel: CognitePointCloudModel,
index: number
): { model: CognitePointCloudModel; isToggled: boolean; name: string } {
return {
model: pointCloudModel,
isToggled: pointCloudModel.getDefaultPointCloudAppearance().visible ?? true,
name: pointCloudModelName?.data?.[index] ?? 'No model name'
};
}

function fillImage360LayerData(image360Collection: Image360Collection): {
image360: Image360Collection;
isToggled: boolean;
isActive: boolean;
} {
return {
image360: image360Collection,
isToggled: true,
isActive: false
};
}

function subcribe360ImageEnterExitMode(image360LayerData: {
image360: Image360Collection;
isToggled: boolean;
isActive: boolean;
}): void {
image360LayerData.image360.on('image360Entered', () => {
image360LayerData.isActive = true;
});
image360LayerData.image360.on('image360Exited', () => {
image360LayerData.isActive = false;
});
}

return {
cadLayerData: updatedCadLayerData,
pointCloudLayerData: updatedPointCloudLayerData,
image360LayerData: updatedImage360LayerData
};
}, [
viewer.models.length,
viewer.get360ImageCollections().length,
cadModelName.data,
pointCloudModelName.data
]);

useEffect(() => {
setReveal3DResourcesLayerData(updated3DResourcesLayerData);
}, [updated3DResourcesLayerData]);

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

import React, { type ReactElement, useState } from 'react';
import { type ReactElement } 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';
import { type Reveal3DResourcesLayersProps } from './types';

export const CadModelLayersContainer = (): ReactElement => {
export const CadModelLayersContainer = ({
layerProps
}: {
layerProps: Reveal3DResourcesLayersProps;
}): 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 { cadLayerData } = layerProps.reveal3DResourcesLayerData;

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 count = cadLayerData.length.toString();
const someModelVisible = !cadLayerData.every((data) => !data.isToggled);
const indeterminate = cadLayerData.some((data) => !data.isToggled);

const handleCadModelVisibility = (model: CogniteCadModel): void => {
selectedCadModels.map((data) => {
const updatedSelectedCadModels = cadLayerData.map((data) => {
if (data.model === model) {
return {
...data,
Expand All @@ -45,26 +36,31 @@ export const CadModelLayersContainer = (): ReactElement => {
});
model.visible = !model.visible;
viewer.requestRedraw();
setSelectedCadModels([...selectedCadModels]);
setIndeterminate(selectedCadModels.some((data) => !data.isToggled));
setAllCadModelVisible(!selectedCadModels.every((data) => !data.isToggled));
layerProps.setReveal3DResourcesLayerData((prevResourcesStates) => ({
...prevResourcesStates,
cadLayerData: updatedSelectedCadModels
}));
};

const handleAllCadModelsVisibility = (visible: boolean): void => {
selectedCadModels.forEach((data) => {
data.isToggled = visible;
const updatedSelectedCadModels = cadLayerData.map((data) => ({
...data,
isToggled: visible
}));
updatedSelectedCadModels.forEach((data) => {
data.model.visible = visible;
});
viewer.requestRedraw();
setAllCadModelVisible(visible);
setIndeterminate(false);
setSelectedCadModels([...selectedCadModels]);
layerProps.setReveal3DResourcesLayerData((prevResourcesStates) => ({
...prevResourcesStates,
cadLayerData: updatedSelectedCadModels
}));
};

const cadModelContent = (): React.JSX.Element => {
const cadModelContent = (): ReactElement => {
return (
<StyledSubMenu>
{selectedCadModels.map((data) => (
{cadLayerData.map((data) => (
<Menu.Item
key={uniqueId()}
hasCheckbox
Expand All @@ -83,19 +79,23 @@ export const CadModelLayersContainer = (): ReactElement => {
};

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>
<>
{cadLayerData.length > 0 && (
<Menu.Submenu content={cadModelContent()} title="CAD models">
<Flex direction="row" justifyContent="space-between" gap={4}>
<Checkbox
checked={someModelVisible}
indeterminate={indeterminate}
onChange={(e) => {
e.stopPropagation();
handleAllCadModelsVisibility(e.target.checked);
}}
/>
<StyledLabel> CAD models </StyledLabel>
<StyledChipCount label={count} hideTooltip type="neutral" />
</Flex>
</Menu.Submenu>
)}
</>
);
};
Loading
Loading