-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(extension): Basic search widget support (#17)
- Loading branch information
Showing
77 changed files
with
4,002 additions
and
135 deletions.
There are no files selected for viewing
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
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
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 |
---|---|---|
@@ -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> | ||
); | ||
}; |
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
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,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, | ||
// }); | ||
// } | ||
// } |
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 |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from "./atomsWithQualitativeColorSet"; | ||
export * from "./atomsWithQuantitativeColorMap"; | ||
export * from "./colorSets"; | ||
export * from "./TileFeatureIndex"; |
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,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>( | ||
( | ||
{ 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 |
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,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>( | ||
({ 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> | ||
); | ||
}, | ||
); |
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,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} | ||
/> | ||
))} | ||
</> | ||
); | ||
} |
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,7 @@ | ||
export * from "./LayerList"; | ||
export * from "./LayersRenderer"; | ||
export * from "./states"; | ||
export * from "./types"; | ||
export * from "./useAddLayer"; | ||
export * from "./useFilterLayers"; | ||
export * from "./useFindLayer"; |
Oops, something went wrong.