Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

Commit

Permalink
feat: clustering layers (#143)
Browse files Browse the repository at this point in the history
Co-authored-by: basel.issmail <basel.issmail@gmail.com>
  • Loading branch information
issmail-basel and Basel-Issmail committed Dec 17, 2021
1 parent dae1376 commit 3439cc1
Show file tree
Hide file tree
Showing 24 changed files with 1,521 additions and 168 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -208,4 +208,4 @@
"use-debounce": "^7.0.1",
"use-file-input": "^1.0.0"
}
}
}
6 changes: 6 additions & 0 deletions src/components/atoms/Icon/Icons/cluster.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/components/atoms/Icon/icons.ts
Expand Up @@ -11,6 +11,9 @@ import PrimRect from "./Icons/primRect.svg";
import PrimModel from "./Icons/primModel.svg";
import PrimTileset from "./Icons/primTileset.svg";

// Cluster
import Cluster from "./Icons/cluster.svg";

// Built-in Widgets
import Widgets from "./Icons/widgetsIcon.svg";
import WidgetMenu from "./Icons/widgetMenu.svg";
Expand Down Expand Up @@ -136,6 +139,7 @@ export default {
rect: PrimRect,
model: PrimModel,
tileset: PrimTileset,
cluster: Cluster,
widget: Widgets,
widgets: Widgets,
menu: WidgetMenu,
Expand Down
102 changes: 98 additions & 4 deletions src/components/molecules/EarthEditor/OutlinePane/hooks.tsx
Expand Up @@ -35,6 +35,12 @@ export type Widget = {
enabled?: boolean;
};

export type Cluster = {
id: string;
name: string;
propertyId: string;
};

export type WidgetType = {
pluginId: string;
extensionId: string;
Expand All @@ -45,7 +51,7 @@ export type WidgetType = {

export type ItemType = ItemEx["type"];
export type ItemEx =
| { type: "root" | "scene" | "layer" | "scenes" | "widgets" }
| { type: "root" | "scene" | "layer" | "scenes" | "widgets" | "cluster" }
| {
type: "widget";
id?: string;
Expand All @@ -58,20 +64,26 @@ export default ({
rootLayerId,
layers,
widgets,
clusters,
widgetTypes,
sceneDescription,
selectedLayerId,
selectedWidgetId,
selectedClusterId,
selectedType,
onLayerSelect,
onSceneSelect,
onWidgetsSelect,
onWidgetSelect,
onWidgetAdd,
onWidgetActivation,
onClusterSelect,
onClusterAdd,
onClusterRemove,
onLayerMove,
onLayerRemove,
onLayerRename,
onClusterRename,
onLayerVisibilityChange,
onDrop,
onLayerGroupCreate,
Expand All @@ -81,11 +93,13 @@ export default ({
rootLayerId?: string;
layers?: Layer[];
widgets?: Widget[];
clusters?: Cluster[];
widgetTypes?: WidgetType[];
sceneDescription?: string;
selectedLayerId?: string;
selectedIndex?: number[];
selectedWidgetId?: string;
selectedClusterId?: string;
selectedType?: ItemType;
onLayerSelect?: (id: string, ...i: number[]) => void;
onLayerImport?: (file: File, format: Format) => void;
Expand All @@ -95,6 +109,9 @@ export default ({
onWidgetSelect?: (widgetId: string | undefined, pluginId: string, extensionId: string) => void;
onWidgetAdd?: (id?: string) => Promise<void>;
onWidgetActivation?: (widgetId: string, enabled: boolean) => Promise<void>;
onClusterSelect?: (clusterId: string) => void;
onClusterAdd?: (name?: string) => Promise<void>;
onClusterRemove?: (layerId: string) => Promise<void>;
onLayerMove?: (
src: string,
dest: string,
Expand All @@ -103,6 +120,7 @@ export default ({
parent: string,
) => void;
onLayerRename?: (id: string, name: string) => void;
onClusterRename?: (id: string, name: string) => void;
onLayerVisibilityChange?: (id: string, visibility: boolean) => void;
onDrop?: (layer: string, index: number, childrenCount: number) => any;
onLayerGroupCreate?: () => void;
Expand All @@ -118,14 +136,18 @@ export default ({

const item = items[0];
if (!item) return;

if (item.content.type === "scene") {
onSceneSelect?.();
} else if (item.id === "widgets") {
onWidgetsSelect?.();
} else if (item.content.type === "widget") {
const [pluginId, extensionId, widgetId] = item.content.id?.split("/") ?? [];
onWidgetSelect?.(widgetId, pluginId, extensionId);
} else if (item.content.type === "cluster") {
// console.log(item.content);
onClusterSelect?.(item.id);
// const [pluginId, extensionId, widgetId] = item.content.id?.split("/") ?? [];
// onWidgetSelect?.(widgetId, pluginId, extensionId);
} else if (item.content.type === "layer") {
onLayerSelect?.(
item.id,
Expand All @@ -134,7 +156,7 @@ export default ({
);
}
},
[onLayerSelect, onSceneSelect, onWidgetsSelect, onWidgetSelect],
[onClusterSelect, onLayerSelect, onSceneSelect, onWidgetsSelect, onWidgetSelect],
);

const drop = useCallback(
Expand Down Expand Up @@ -172,6 +194,7 @@ export default ({
const sceneTitle = intl.formatMessage({ defaultMessage: "Scene" });
const widgetTitle = intl.formatMessage({ defaultMessage: "Widgets" });
const layerTitle = intl.formatMessage({ defaultMessage: "Layers" });
const clusterTitle = intl.formatMessage({ defaultMessage: "Clusters" });

const sceneWidgetsItem = useMemo<TreeViewItemType<TreeViewItem> | undefined>(
() => ({
Expand Down Expand Up @@ -273,6 +296,59 @@ export default ({
[layerTitle, rootLayerId, layers],
);

const clustersItem = useMemo<TreeViewItemType<TreeViewItem> | undefined>(
() => ({
id: "root",
content: {
id: "root",
type: "root",
},
children: [
{
id: "cluster",
content: {
id: "cluster",
type: "cluster",
icon: "cluster",
title: clusterTitle,
childrenCount: clusters?.length,
showLayerActions: true,
actionItems: [
{
type: "cluster",
id: "",
title: "Cluster",
icon: "cluster",
},
],
underlined: true,
showChildrenCount: false,
group: true,
},
expandable: true,
children: clusters?.map(c => ({
id: c.id,
content: {
id: c.id,
name: c.name,
property: c.propertyId,
type: "cluster",
icon: "cluster",
title: c.name,
renamable: true,
},
draggable: false,
droppable: false,
droppableIntoChildren: false,
expandable: false,
selectable: true,
})),
},
],
}),
[clusterTitle, clusters],
);

const layerTreeViewItemOnRename = useCallback(
(item: TreeViewItemType<LayerTreeViewItemItem<ItemEx>>, name: string) =>
onLayerRename?.(item.id, name),
Expand Down Expand Up @@ -310,6 +386,20 @@ export default ({
rootLayerId,
});

const clusterTreeViewItemOnRename = useCallback(
(item: TreeViewItemType<LayerTreeViewItemItem<ItemEx>>, name: string) =>
onClusterRename?.(item.id, name),
[onClusterRename],
);

const ClusterTreeViewItem = useLayerTreeViewItem<ItemEx>({
onRename: clusterTreeViewItemOnRename,
onAdd: onClusterAdd,
onRemove: onClusterRemove,
visibilityShown: true,
selectedLayerId: selectedClusterId,
});

useEffect(() => {
const newState =
selectedType === "scene"
Expand All @@ -318,21 +408,25 @@ export default ({
? ["widgets"]
: selectedType === "layer" && selectedLayerId
? [selectedLayerId]
: selectedType === "cluster" && selectedClusterId
? [selectedClusterId]
: selectedType === "widget" && selectedWidgetId
? [selectedWidgetId]
: [];
setSelected(ids => (arrayEquals(ids, newState) ? ids : newState));
}, [selectedLayerId, selectedType, selectedWidgetId]);
}, [selectedLayerId, selectedType, selectedWidgetId, selectedClusterId]);

return {
sceneWidgetsItem,
layersItem,
clustersItem,
select,
drop,
dropExternals,
removeLayer,
SceneTreeViewItem,
LayerTreeViewItem,
ClusterTreeViewItem,
selected,
};
};
Expand Down
48 changes: 42 additions & 6 deletions src/components/molecules/EarthEditor/OutlinePane/index.tsx
Expand Up @@ -10,20 +10,22 @@ import TreeView from "@reearth/components/atoms/TreeView";
import { styled } from "@reearth/theme";
import { metricsSizes } from "@reearth/theme/metrics";

import useHooks, { Format, Layer, Widget, WidgetType, TreeViewItem } from "./hooks";
import useHooks, { Format, Layer, Widget, Cluster, WidgetType, TreeViewItem } from "./hooks";

export type { Format, Layer, Widget, WidgetType } from "./hooks";
export type { Format, Layer, Widget, Cluster, WidgetType } from "./hooks";

export type Props = {
className?: string;
rootLayerId?: string;
selectedLayerId?: string;
selectedWidgetId?: string;
selectedClusterId?: string;
layers?: Layer[];
widgets?: Widget[];
clusters?: Cluster[];
widgetTypes?: WidgetType[];
sceneDescription?: string;
selectedType?: "scene" | "layer" | "widgets" | "widget" | "dataset";
selectedType?: "scene" | "layer" | "widgets" | "widget" | "cluster" | "dataset";
loading?: boolean;
onLayerRename?: (id: string, name: string) => void;
onLayerVisibilityChange?: (id: string, visibility: boolean) => void;
Expand All @@ -35,6 +37,10 @@ export type Props = {
onWidgetAdd?: (id?: string) => Promise<void>;
onWidgetRemove?: (widgetId: string) => Promise<void>;
onWidgetActivation?: (widgetId: string, enabled: boolean) => Promise<void>;
onClusterSelect?: (clusterId: string) => void;
onClusterAdd?: () => Promise<void>;
onClusterRename?: (clusterId: string, name: string) => void;
onClusterRemove?: (layerId: string) => Promise<void>;
onLayerMove?: (
layer: string,
destLayer: string,
Expand All @@ -53,9 +59,11 @@ const OutlinePane: React.FC<Props> = ({
rootLayerId,
selectedLayerId,
selectedWidgetId,
selectedClusterId,
selectedType,
layers,
widgets,
clusters,
widgetTypes,
sceneDescription,
onLayerRename,
Expand All @@ -68,6 +76,10 @@ const OutlinePane: React.FC<Props> = ({
onWidgetAdd,
onWidgetRemove,
onWidgetActivation,
onClusterSelect,
onClusterAdd,
onClusterRename,
onClusterRemove,
onLayerMove,
onLayerImport,
onLayerGroupCreate,
Expand All @@ -82,20 +94,24 @@ const OutlinePane: React.FC<Props> = ({
const {
sceneWidgetsItem,
layersItem,
clustersItem,
select,
drop,
dropExternals,
SceneTreeViewItem,
LayerTreeViewItem,
ClusterTreeViewItem,
selected,
} = useHooks({
rootLayerId,
layers,
widgets,
clusters,
widgetTypes,
sceneDescription,
selectedLayerId,
selectedWidgetId,
selectedClusterId,
selectedType,
onLayerSelect,
onLayerImport,
Expand All @@ -105,8 +121,12 @@ const OutlinePane: React.FC<Props> = ({
onWidgetSelect,
onWidgetAdd,
onWidgetActivation,
onClusterSelect,
onClusterAdd,
onClusterRemove,
onLayerMove,
onLayerRename,
onClusterRename,
onLayerVisibilityChange,
onDrop,
onLayerGroupCreate,
Expand All @@ -131,7 +151,7 @@ const OutlinePane: React.FC<Props> = ({
/>
)}
</OutlineItemsWrapper>
<LayersItemWrapper>
<ItemsGroupWrapper>
{layersItem && (
<TreeView<TreeViewItem, HTMLDivElement>
item={layersItem}
Expand All @@ -150,7 +170,23 @@ const OutlinePane: React.FC<Props> = ({
/>
)}
{loading && <Loading />}
</LayersItemWrapper>
</ItemsGroupWrapper>

<ItemsGroupWrapper>
{clustersItem && (
<TreeView<TreeViewItem, HTMLDivElement>
item={clustersItem}
selected={selected}
renderItem={ClusterTreeViewItem}
selectable
expandable
expanded={["rootCluster"]}
onSelect={select}
/>
)}
{loading && <Loading />}
</ItemsGroupWrapper>

<ConfirmationModal
title={intl.formatMessage({ defaultMessage: "Delete widget" })}
body={
Expand Down Expand Up @@ -199,7 +235,7 @@ const OutlineItemsWrapper = styled.div`
background-color: ${props => props.theme.layers.bg};
`;

const LayersItemWrapper = styled(OutlineItemsWrapper)`
const ItemsGroupWrapper = styled(OutlineItemsWrapper)`
min-height: 0;
`;

Expand Down

0 comments on commit 3439cc1

Please sign in to comment.