Skip to content

Commit

Permalink
feat(web): add VisualizerContext in NLS for beta (#591)
Browse files Browse the repository at this point in the history
  • Loading branch information
keiya01 committed Jul 26, 2023
1 parent ad4ae6d commit 39811d4
Show file tree
Hide file tree
Showing 10 changed files with 416 additions and 265 deletions.
9 changes: 8 additions & 1 deletion web/src/beta/features/Editor/Visualizer/CanvasArea/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo, useEffect, useCallback } from "react";
import { useMemo, useEffect, useCallback, useState } from "react";

import type { Alignment, Location } from "@reearth/beta/lib/core/Crust";
import type { LatLng, Tag, ValueTypes, ComputedLayer } from "@reearth/beta/lib/core/mantle";
Expand Down Expand Up @@ -32,6 +32,11 @@ export default ({ sceneId, isBuilt }: { sceneId?: string; isBuilt?: boolean }) =
const [widgetAlignEditorActivated] = useWidgetAlignEditorActivated();
const [zoomedLayerId, zoomToLayer] = useZoomedLayerId();

const [isVisualizerReady, setIsVisualizerReady] = useState(false);
const handleMount = useCallback(() => {
setIsVisualizerReady(true);
}, []);

const onBlockMove = useCallback(
async (_id: string, _fromIndex: number, _toIndex: number) => {
if (selected?.type !== "layer") return;
Expand Down Expand Up @@ -217,6 +222,7 @@ export default ({ sceneId, isBuilt }: { sceneId?: string; isBuilt?: boolean }) =
engineMeta,
layerSelectionReason,
useExperimentalSandbox,
isVisualizerReady,
selectLayer,
selectBlock,
onBlockChange,
Expand All @@ -231,5 +237,6 @@ export default ({ sceneId, isBuilt }: { sceneId?: string; isBuilt?: boolean }) =
onFovChange,
handleDropLayer,
zoomToLayer,
handleMount,
};
};
3 changes: 3 additions & 0 deletions web/src/beta/features/Editor/Visualizer/CanvasArea/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const CanvasArea: React.FC<Props> = ({ sceneId, isBuilt, inEditor }) => {
engineMeta,
layerSelectionReason,
useExperimentalSandbox,
isVisualizerReady: _isVisualizerReady,
selectLayer,
selectBlock,
onBlockChange,
Expand All @@ -49,6 +50,7 @@ const CanvasArea: React.FC<Props> = ({ sceneId, isBuilt, inEditor }) => {
onFovChange,
handleDropLayer,
zoomToLayer,
handleMount,
} = useHooks({ sceneId, isBuilt });
const renderInfoboxInsertionPopUp = useCallback<
NonNullable<VisualizerProps["renderInfoboxInsertionPopup"]>
Expand Down Expand Up @@ -99,6 +101,7 @@ const CanvasArea: React.FC<Props> = ({ sceneId, isBuilt, inEditor }) => {
onBlockInsert={onBlockInsert}
onLayerDrop={handleDropLayer}
onZoomToLayer={zoomToLayer}
onMount={handleMount}
renderInfoboxInsertionPopup={renderInfoboxInsertionPopUp}
/>
<FovSlider
Expand Down
1 change: 1 addition & 0 deletions web/src/beta/lib/core/Map/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export type EngineProps = {
position: LatLng | undefined,
) => void;
onLayerEdit?: (e: LayerEditEvent) => void;
onMount?: () => void;
};

export type LayerEditEvent = {
Expand Down
12 changes: 12 additions & 0 deletions web/src/beta/lib/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@
- **Map**: Engine + mantle

![Architecture](docs/architecture.svg)

## Context

We have some type of the context to expose the interface for the map API. The map API is the abstracted map engine API. For example, we are using Cesium as the map engine, but we have a plan to support another map engine in the future. To do this, we define the interface as the map API. The map API is abstracted as MapRef internally. The MapRef has two type of API for now. First one is EngineRef that is API to access to the map engine's API. And second one is LayersRef that is API to access to the abstracted layer system. This manages the data to display for the map engine.

We have the context as the follows.

- FeatureContext ... It works as the interface for exposing the map API to the Feature components and the Layers component.
- WidgetContext ... It works as the interface for exposing the map API to the Widget components.
- VisualizerContext ... It works as the interface for exposing the map API to Visualizer.

By defining these context as the interface, we can understand which API for the map API is used in each layer. And if there are some features of the map API depends on the layer, we can absorb the feature in the context.
27 changes: 27 additions & 0 deletions web/src/beta/lib/core/Visualizer/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { FC, PropsWithChildren, RefObject, createContext, useContext, useMemo } from "react";

import { Ref as MapRef } from "../Map";

const context = createContext<RefObject<MapRef> | undefined>(undefined);

export type Context = RefObject<MapRef>;

export const useVisualizer = (): RefObject<MapRef> => {
const value = useContext(context);
if (!value) {
throw new Error("Visualizer is not declared. You have to use this hook inside of Visualizer");
}
return value;
};

const filterMapRefToContext = (mapRef: RefObject<MapRef>): Context => {
return mapRef as Context;
};

export const VisualizerProvider: FC<PropsWithChildren<{ mapRef: RefObject<MapRef> }>> = ({
mapRef,
children,
}) => {
const value = useMemo(() => filterMapRefToContext(mapRef), [mapRef]);
return <context.Provider value={value}>{children}</context.Provider>;
};
79 changes: 42 additions & 37 deletions web/src/beta/lib/core/Visualizer/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Ref, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import { useWindowSize } from "react-use";

// TODO: Move these utils
Expand All @@ -24,44 +24,49 @@ import useViewport from "./useViewport";

const viewportMobileMaxWidth = 768;

export default function useHooks({
selectedBlockId: initialSelectedBlockId,
camera: initialCamera,
interactionMode: initialInteractionMode,
sceneProperty,
isEditable,
rootLayerId,
zoomedLayerId,
ownBuiltinWidgets,
onLayerSelect,
onBlockSelect,
onCameraChange,
onInteractionModeChange,
onZoomToLayer,
onLayerDrop,
}: {
selectedBlockId?: string;
camera?: Camera;
interactionMode?: InteractionModeType;
isEditable?: boolean;
rootLayerId?: string;
sceneProperty?: SceneProperty;
zoomedLayerId?: string;
ownBuiltinWidgets?: (keyof BuiltinWidgets)[];
onLayerSelect?: (
layerId: string | undefined,
featureId: string | undefined,
layer: (() => Promise<ComputedLayer | undefined>) | undefined,
reason: LayerSelectionReason | undefined,
) => void;
onBlockSelect?: (blockId?: string) => void;
onCameraChange?: (camera: Camera) => void;
onInteractionModeChange?: (mode: InteractionModeType) => void;
onZoomToLayer?: (layerId: string | undefined) => void;
onLayerDrop?: (layerId: string, propertyKey: string, position: LatLng | undefined) => void;
}) {
export default function useHooks(
{
selectedBlockId: initialSelectedBlockId,
camera: initialCamera,
interactionMode: initialInteractionMode,
sceneProperty,
isEditable,
rootLayerId,
zoomedLayerId,
ownBuiltinWidgets,
onLayerSelect,
onBlockSelect,
onCameraChange,
onInteractionModeChange,
onZoomToLayer,
onLayerDrop,
}: {
selectedBlockId?: string;
camera?: Camera;
interactionMode?: InteractionModeType;
isEditable?: boolean;
rootLayerId?: string;
sceneProperty?: SceneProperty;
zoomedLayerId?: string;
ownBuiltinWidgets?: (keyof BuiltinWidgets)[];
onLayerSelect?: (
layerId: string | undefined,
featureId: string | undefined,
layer: (() => Promise<ComputedLayer | undefined>) | undefined,
reason: LayerSelectionReason | undefined,
) => void;
onBlockSelect?: (blockId?: string) => void;
onCameraChange?: (camera: Camera) => void;
onInteractionModeChange?: (mode: InteractionModeType) => void;
onZoomToLayer?: (layerId: string | undefined) => void;
onLayerDrop?: (layerId: string, propertyKey: string, position: LatLng | undefined) => void;
},
ref: Ref<MapRef | null>,
) {
const mapRef = useRef<MapRef>(null);

useImperativeHandle(ref, () => mapRef.current, []);

const wrapperRef = useRef<HTMLDivElement>(null);

const { ref: dropRef, isDroppable } = useDrop(
Expand Down
161 changes: 110 additions & 51 deletions web/src/beta/lib/core/Visualizer/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,126 @@
import { Meta, Story } from "@storybook/react";
import { Meta, StoryObj } from "@storybook/react";
import { ComponentProps, FC, useEffect, useState } from "react";

import Component, { Props } from ".";
import { useVisualizer } from "./context";

import Component from ".";

export default {
component: Component,
parameters: { actions: { argTypesRegex: "^on.*" } },
} as Meta;

const Template: Story<Props> = args => <Component {...args} />;
type Story = StoryObj<typeof Component>;

export const Cesium = Template.bind({});
export const Cesium: Story = {
args: {
ready: true,
engine: "cesium",
sceneProperty: {
tiles: [
{
id: "default",
tile_type: "default",
},
],
},
},
};

Cesium.args = {
ready: true,
engine: "cesium",
sceneProperty: {
tiles: [
{
id: "default",
tile_type: "default",
const Content: FC<{ ready?: boolean }> = ({ ready }) => {
const visualizer = useVisualizer();
useEffect(() => {
if (!ready) return;
visualizer.current?.engine.flyTo({
lat: 35.683252649879606,
lng: 139.75262379931652,
height: 5000,
});
visualizer.current?.layers.add({
type: "simple",
data: {
type: "geojson",
value: {
// GeoJSON
type: "Feature",
geometry: {
coordinates: [139.75262379931652, 35.683252649879606, 1000],
type: "Point",
},
},
},
],
},
marker: {
imageColor: "blue",
},
});
}, [visualizer, ready]);
return null;
};

export const Plugin = Template.bind({});
const VisualizerWrapper: FC<ComponentProps<typeof Component>> = props => {
const [ready, setReady] = useState(false);
return (
<Component {...props} onMount={() => setReady(true)}>
<Content ready={ready} />
</Component>
);
};

Plugin.args = {
ready: true,
engine: "cesium",
sceneProperty: {
tiles: [
{
id: "default",
tile_type: "default",
},
],
export const API: Story = {
render: args => {
return <VisualizerWrapper {...args} />;
},
args: {
ready: true,
engine: "cesium",
sceneProperty: {
tiles: [
{
id: "default",
tile_type: "default",
},
],
},
},
widgetAlignSystem: {
outer: {
left: {
top: {
align: "start",
widgets: [
{
id: "plugin",
pluginId: "plugin",
extensionId: "test",
...({
__REEARTH_SOURCECODE: `reearth.layers.add(${JSON.stringify({
id: "l",
type: "simple",
data: {
type: "geojson",
value: { type: "Feature", geometry: { type: "Point", coordinates: [0, 0] } },
},
marker: {
imageColor: "#fff",
},
})});`,
} as any),
},
],
};

export const Plugin = {
args: {
ready: true,
engine: "cesium",
sceneProperty: {
tiles: [
{
id: "default",
tile_type: "default",
},
],
},
widgetAlignSystem: {
outer: {
left: {
top: {
align: "start",
widgets: [
{
id: "plugin",
pluginId: "plugin",
extensionId: "test",
...({
__REEARTH_SOURCECODE: `reearth.layers.add(${JSON.stringify({
id: "l",
type: "simple",
data: {
type: "geojson",
value: { type: "Feature", geometry: { type: "Point", coordinates: [0, 0] } },
},
marker: {
imageColor: "#fff",
},
})});`,
} as any),
},
],
},
},
},
},
Expand Down
Loading

0 comments on commit 39811d4

Please sign in to comment.