Skip to content

Commit

Permalink
feat(extension): Basic search widget support (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
keiya01 committed Sep 6, 2023
1 parent de97d2a commit d80207e
Show file tree
Hide file tree
Showing 77 changed files with 4,002 additions and 135 deletions.
5 changes: 5 additions & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"dependencies": {
"@ant-design/colors": "7.0.0",
"@ant-design/icons": "5.2.5",
"@dnd-kit/core": "6.0.8",
"@dnd-kit/modifiers": "6.0.1",
"@dnd-kit/sortable": "7.0.2",
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
"@mui/icons-material": "5.14.3",
Expand All @@ -32,6 +35,7 @@
"jotai-xstate": "0.3.0",
"lodash-es": "4.17.21",
"material-ui-popup-state": "5.0.9",
"moji": "0.5.1",
"overlayscrollbars": "2.2.1",
"overlayscrollbars-react": "0.5.1",
"re-resizable": "6.9.11",
Expand All @@ -55,6 +59,7 @@
"@types/chroma-js": "2.4.0",
"@types/d3": "7.4.0",
"@types/lodash-es": "4.17.8",
"@types/moji": "0.5.0",
"@types/node": "20.4.9",
"@types/overlayscrollbars": "1.12.1",
"@types/react": "18.2.15",
Expand Down
8 changes: 6 additions & 2 deletions extension/reearth-yml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ const yml = {
type: "widget",
name: "Search",
widgetLayout: {
extendable: {
vertically: true,
},
defaultLocation: {
zone: "outer",
zone: "inner",
section: "left",
area: "top",
area: "middle",
},
extended: true,
},
},
],
Expand Down
13 changes: 8 additions & 5 deletions extension/src/inspector/Widget.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { useAtom } from "jotai";
import { useState } from "react";

import { WidgetContext } from "../shared/context/WidgetContext";
import { countAtom } from "../shared/states/count";

export const Widget = () => {
const [count, setCount] = useState(0);
const [{ value: globalCount }, setGlobalCount] = useAtom(countAtom);

return (
<div style={{ background: "blue" }}>
<button onClick={() => setCount(n => n + 1)}>Local: {count}</button>
<br />
<button onClick={() => setGlobalCount(c => c + 1)}>Global: {globalCount}</button>
</div>
<WidgetContext>
<div style={{ background: "blue" }}>
<button onClick={() => setCount(n => n + 1)}>Local: {count}</button>
<br />
<button onClick={() => setGlobalCount(c => c + 1)}>Global: {globalCount}</button>
</div>
</WidgetContext>
);
};
3 changes: 3 additions & 0 deletions extension/src/prototypes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ These module's names are same with prototype's name.
- shared-states ... Shared Jotai atoms utils.
- react-helpers ... Helpers for React.
- type-helpers ... Helpers for typing.
- view ... Application logic.
- view-layers ... Application layers logic
- layers ... Some logics to handle layer
86 changes: 86 additions & 0 deletions extension/src/prototypes/datasets/TileFeatureIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// TODO(ReEarth): Need a search feature as ReEarth API.
export class TileFeatureIndex {}

// import moji from "moji";

// import { PlateauTilesetFeature } from "../../shared/plateau";

// function cleanseName(text: string): string {
// return moji(text)
// .convert("ZE", "HE")
// .convert("ZS", "HS")
// .convert("HK", "ZK")
// .toString()
// .replace(/ +/g, " ")
// .replace(/([^ ])\(/g, "$1 (")
// .replace(/\)([^ ])/g, ") $1")
// .trim();
// }

// interface IndexRecord {
// content: Cesium3DTileContent;
// batchId: number;
// }

// export interface SearchableFeatureRecord {
// feature: PlateauTilesetFeature;
// key: string;
// name: string;
// longitude: number;
// latitude: number;
// }

// export class TileFeatureIndex {
// private readonly records = new Map<string, IndexRecord[]>();
// readonly #searchableFeatures: SearchableFeatureRecord[] = [];

// get searchableFeatures(): ReadonlyArray<Readonly<SearchableFeatureRecord>> {
// return this.#searchableFeatures;
// }

// has(key: string): boolean {
// return this.records.has(key);
// }

// find(key: string): Cesium3DTileFeature[] | undefined {
// const record = this.records.get(key);
// if (record == null) {
// return;
// }
// return record.map(({ content, batchId }) => content.getFeature(batchId));
// }

// addTile(tile: Cesium3DTile, getKey: (feature: Cesium3DTileFeature) => string | undefined): void {
// forEachTileFeature(tile, (feature, batchId) => {
// this.addFeature(tile.content, batchId, getKey(feature));
// });
// }

// addFeature(content: Cesium3DTileContent, batchId: number, key?: string): void {
// if (key == null) {
// return;
// }
// const record = this.records.get(key);
// if (record == null) {
// this.records.set(key, [{ content, batchId }]);
// } else {
// record.push({ content, batchId });
// }

// const feature = content.getFeature(batchId);
// // Only PLATEAU 2020 datasets are supported.
// const name: string | undefined = feature.getProperty("名称");
// const longitude: number | undefined = feature.getProperty("_x");
// const latitude: number | undefined = feature.getProperty("_y");
// if (name == null || longitude == null || latitude == null) {
// return;
// }
// this.#searchableFeatures.push({
// feature,
// key,
// name: cleanseName(name),
// longitude,
// latitude,
// });
// }
// }
1 change: 1 addition & 0 deletions extension/src/prototypes/datasets/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./atomsWithQualitativeColorSet";
export * from "./atomsWithQuantitativeColorMap";
export * from "./colorSets";
export * from "./TileFeatureIndex";
99 changes: 99 additions & 0 deletions extension/src/prototypes/layers/LayerList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
closestCenter,
DndContext,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
type DragEndEvent,
} from "@dnd-kit/core";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
import {
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { styled } from "@mui/material";
import { useAtomValue, useSetAtom } from "jotai";
import {
forwardRef,
useCallback,
type ComponentPropsWithRef,
type ComponentType,
type ElementType,
} from "react";
import invariant from "tiny-invariant";

import { LayerListItem } from "./LayerListItem";
import { layerAtomsAtom, layerIdsAtom, moveLayerAtom } from "./states";
import { type LayerProps } from "./types";

const Root = styled("div")({});

export type LayerListProps<C extends ElementType = typeof Root> = ComponentPropsWithRef<C> & {
component?: C;
itemComponent: ComponentType<LayerProps>;
unmountWhenEmpty?: boolean;
minimumDragDistance?: number;
};

export const LayerList = forwardRef<HTMLDivElement, LayerListProps>(

Check warning on line 40 in extension/src/prototypes/layers/LayerList.tsx

View workflow job for this annotation

GitHub Actions / ci-extension / ci-extension

Component definition is missing display name
(
{ unmountWhenEmpty = false, minimumDragDistance = 5, component, itemComponent, ...props },
ref,
) => {
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: minimumDragDistance,
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);

const layerAtoms = useAtomValue(layerAtomsAtom);
const layerIds = useAtomValue(layerIdsAtom);
const move = useSetAtom(moveLayerAtom);

const handleDragEnd = useCallback(
(event: DragEndEvent) => {
if (event.over == null || event.active.id === event.over.id) {
return;
}
invariant(typeof event.active.id === "string");
invariant(typeof event.over.id === "string");
move(event.active.id, event.over.id);
},
[move],
);

if (unmountWhenEmpty && layerAtoms.length === 0) {
return null;
}
const Component = component ?? Root;
return (
<Component ref={ref} {...props}>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
modifiers={[restrictToVerticalAxis]}
onDragEnd={handleDragEnd}>
<SortableContext items={layerIds} strategy={verticalListSortingStrategy}>
{layerAtoms.map((layerAtom, index) => (
// Technically key can be coerced string of atom, but dnd-kit
// disagree with it.
<LayerListItem
key={layerIds[index]}
index={index}
layerAtom={layerAtom}
itemComponent={itemComponent}
/>
))}
</SortableContext>
</DndContext>
</Component>
);
},
) as <C extends ElementType>(props: LayerListProps<C>) => JSX.Element; // For generics
70 changes: 70 additions & 0 deletions extension/src/prototypes/layers/LayerListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { styled } from "@mui/material";
import { useAtom, useAtomValue, useSetAtom, type PrimitiveAtom } from "jotai";
import {
forwardRef,
useCallback,
type ComponentPropsWithRef,
type ComponentType,
type MouseEvent,
} from "react";
import { mergeRefs } from "react-merge-refs";

import { addLayerSelectionAtom, layerSelectionAtom } from "./states";
import { type LayerModel, type LayerProps } from "./types";

const Root = styled("div")({});

export interface LayerListItemProps extends ComponentPropsWithRef<typeof Root> {
index: number;
layerAtom: PrimitiveAtom<LayerModel>;
itemComponent: ComponentType<LayerProps>;
}

export const LayerListItem = forwardRef<HTMLDivElement, LayerListItemProps>(

Check warning on line 25 in extension/src/prototypes/layers/LayerListItem.tsx

View workflow job for this annotation

GitHub Actions / ci-extension / ci-extension

Component definition is missing display name
({ index, layerAtom, itemComponent, ...props }, forwardedRef) => {
const layer = useAtomValue(layerAtom);

const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
id: layer.id,
});

const [selection, setSelection] = useAtom(layerSelectionAtom);
const addSelection = useSetAtom(addLayerSelectionAtom);
const handleMouseDown = useCallback(
(event: MouseEvent) => {
event.stopPropagation();
if (event.shiftKey) {
// TODO: Toggle selection
addSelection([layer.id]);
} else {
setSelection([layer.id]);
}
},
[layer.id, setSelection, addSelection],
);

const ItemComponent = itemComponent;
return (
<Root
ref={mergeRefs([forwardedRef, setNodeRef])}
{...props}
style={{
transform: CSS.Translate.toString(transform),
transition,
}}
{...attributes}
{...listeners}>
<ItemComponent
{...layer}
index={index}
selected={selection.includes(layer.id)}
itemProps={{
onMouseDown: handleMouseDown,
}}
/>
</Root>
);
},
);
49 changes: 49 additions & 0 deletions extension/src/prototypes/layers/LayersRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useAtomValue, type PrimitiveAtom } from "jotai";
import { Suspense, type ComponentType, type FC, type ReactNode } from "react";

import { layerAtomsAtom, layerIdsAtom, layerSelectionAtom } from "./states";
import { type LayerComponents, type LayerModel, type LayerProps } from "./types";

interface LayerRendererProps {
components: LayerComponents;
index: number;
layerAtom: PrimitiveAtom<LayerModel>;
}

const LayerRenderer: FC<LayerRendererProps> = ({ components, index, layerAtom }) => {
const layer = useAtomValue(layerAtom);
const selection = useAtomValue(layerSelectionAtom);
const Component = components[layer.type] as ComponentType<LayerProps>;
if (Component == null) {
return null;
}
return (
<Suspense>
<Component {...layer} index={index} selected={selection.includes(layer.id)} />
</Suspense>
);
};

export interface LayersRendererProps<T extends LayerComponents> {
components: T;
children?: ReactNode;
}

export function LayersRenderer<T extends LayerComponents>({
components,
}: LayersRendererProps<T>): JSX.Element {
const layerAtoms = useAtomValue(layerAtomsAtom);
const layerIds = useAtomValue(layerIdsAtom);
return (
<>
{layerAtoms.map((layerAtom, index) => (
<LayerRenderer
key={layerIds[index]}
components={components}
index={index}
layerAtom={layerAtom}
/>
))}
</>
);
}
7 changes: 7 additions & 0 deletions extension/src/prototypes/layers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from "./LayerList";
export * from "./LayersRenderer";
export * from "./states";
export * from "./types";
export * from "./useAddLayer";
export * from "./useFilterLayers";
export * from "./useFindLayer";
Loading

0 comments on commit d80207e

Please sign in to comment.