@@ -128,6 +140,7 @@ export function AppUI() { + ("ol-app.AppModel") as AppModel; + const resultListState = useSnapshot(appModel.state).resultListState; + const resultListOpen = resultListState.open; const toggleToolState = (name: keyof ToolState) => { onToolStateChange(name, !toolState[name]); @@ -52,6 +58,14 @@ export function MapTools(props: MapToolsProps) { gap={1} padding={1} > + {resultListState.input && ( + } + isActive={resultListState.open} + onClick={() => appModel.setResultListVisibility(!resultListOpen)} + /> + )} } diff --git a/src/samples/map-sample/ol-app/ui/ResultList.tsx b/src/samples/map-sample/ol-app/ui/ResultList.tsx new file mode 100644 index 00000000..8ebb0232 --- /dev/null +++ b/src/samples/map-sample/ol-app/ui/ResultList.tsx @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) +// SPDX-License-Identifier: Apache-2.0 +import { Box } from "@open-pioneer/chakra-integration"; +import { ResultList } from "@open-pioneer/result-list"; +import { useService } from "open-pioneer:react-hooks"; +import { useSnapshot } from "valtio"; +import { AppModel } from "../AppModel"; +import { MAP_ID } from "../MapConfigProviderImpl"; + +export function ResultListComponent() { + const appModel = useService("ol-app.AppModel") as AppModel; + const state = useSnapshot(appModel.state).resultListState; + return ( + state.input && ( + + + + ) + ); +} diff --git a/src/samples/map-sample/ol-app/ui/Selection.tsx b/src/samples/map-sample/ol-app/ui/Selection.tsx index 8b8ff6f6..fab8ecd8 100644 --- a/src/samples/map-sample/ol-app/ui/Selection.tsx +++ b/src/samples/map-sample/ol-app/ui/Selection.tsx @@ -23,9 +23,10 @@ export function SelectionComponent() { const { map } = useMapModel(MAP_ID); const appModel = useService("ol-app.AppModel"); const sources = useSnapshot(appModel.state).selectionSources; + const sourceMetadata = useSnapshot(appModel.state).sourceMetadata; function onSelectionComplete(event: SelectionCompleteEvent) { - const results = event.results; + const { source, results } = event; if (!map) { console.debug("Map not ready"); @@ -38,6 +39,14 @@ export function SelectionComponent() { highlightAndZoom(map, geometries); } + const currentMetadata = sourceMetadata.get(source); + if (!currentMetadata) { + console.warn("Can not show results because no metadata could be found"); + return; + } + + appModel.setResultListInput({ columns: currentMetadata, data: results }); + notifier.notify({ level: "info", message: intl.formatMessage( diff --git a/src/samples/test-result-list/index.html b/src/samples/test-result-list/index.html new file mode 100644 index 00000000..f6684ff7 --- /dev/null +++ b/src/samples/test-result-list/index.html @@ -0,0 +1,25 @@ + + + + + + Result List Test + + + + + + + + + diff --git a/src/samples/test-result-list/result-list-app/AppUI.tsx b/src/samples/test-result-list/result-list-app/AppUI.tsx new file mode 100644 index 00000000..da7335fb --- /dev/null +++ b/src/samples/test-result-list/result-list-app/AppUI.tsx @@ -0,0 +1,339 @@ +// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) +// SPDX-License-Identifier: Apache-2.0 +import { + Box, + Button, + Flex, + ListItem, + Menu, + MenuButton, + MenuItem, + MenuList, + Portal, + Stack, + Text, + UnorderedList, + VStack +} from "@open-pioneer/chakra-integration"; +import { BaseFeature, MapAnchor, MapContainer } from "@open-pioneer/map"; +import { SectionHeading, TitledSection } from "@open-pioneer/react-utils"; +import { ResultList, ResultListInput } from "@open-pioneer/result-list"; +import { useMemo, useState } from "react"; +import { MAP_ID } from "./MapConfigProviderImpl"; +import { ChevronDownIcon } from "@chakra-ui/icons"; + +const RESULT_LIST_HEIGHT_PIXELS = 400; + +export function AppUI() { + const [displayVersion, setDisplayVersion] = useState(0); + const [currentInput, setCurrentInput] = useState(); + const [resultListOpen, setResultListOpen] = useState(false); + const [hideColumns, setHideColumns] = useState(false); + const showResultList = !!currentInput && resultListOpen; + const fillResultList = (input: ResultListInput) => { + setCurrentInput(input); + setResultListOpen(true); + + // Incrementing the displayVersion (used as key) drops internal result list state + setDisplayVersion(displayVersion + 1); + }; + + // Hide certain columns to test UI state. + const shownInput = useMemo(() => { + if (!currentInput || !hideColumns) { + return currentInput; // unchanged + } + + const filteredColumns = currentInput.columns.filter((_, index) => index % 2 == 1); + return { + ...currentInput, + columns: filteredColumns + }; + }, [hideColumns, currentInput]); + + return ( + + + + OpenLayers Base Packages - Result List + + + } + > + + + + + + Test Controls: + + }> + Fill result list + + + + fillResultList(PERSONS)}> + Persons + + fillResultList(GENERATED)}> + Generated + + fillResultList(LONG_STRINGS)} + > + Long Strings + + fillResultList(MANY_COLUMS)} + > + Many Columns + + + + + + + + + + + + + + Description + + This application can be used to test the result list component. + Internally, this application keeps track of the current result + list input and displays it when the component shall be shown. + + + + If the result list has been filled, it can be hidden and + shown again while preserving the state (selection, sort, + scroll, ...). + + + The result list is embedded with a fixed height (with + internal scrolling) above the map (using view padding). + Showing or hiding the component will animate the view. + + + Toggling columns will preserve the state of the result list. + + + Filling the result list again resets the state (even when + using equal data). + + + Fully closing the result list drops all state. + + + + + + {shownInput && ( + + + + )} + + + + ); +} + +let nextPersonId = 1; +function createPerson(name: string, age: number, city: string) { + return { + id: String(nextPersonId++), + properties: { + name, + age, + city + } + } satisfies BaseFeature; +} + +const PERSONS: ResultListInput = { + data: [ + createPerson("Test User A", 21, "Cologne"), + createPerson("Test User B", 33, "Berlin"), + createPerson("Test User C", 44, "New York"), + createPerson("Test User D", 55, "London"), + createPerson("Test User E", 8, "Rome"), + createPerson("Test User F", 14, "Vienna"), + createPerson("Test User G", 17, "Paris"), + createPerson("Test User H", 27, "Brussels"), + createPerson("Test User I", 19, "Warsaw") + ], + columns: [ + { + // Simple computed column value + displayName: "id", + getPropertyValue(feature) { + return feature.id; + } + }, + { + propertyName: "name" + }, + { + propertyName: "age" + }, + { + propertyName: "city" + } + ] +}; + +const GENERATED: ResultListInput = { + data: Array.from(Array(100).keys()).map((index): BaseFeature => { + return { + id: index, + properties: { + "boolean": index % 2 == 0, + "empty": index % 2 == 0 ? null : undefined, + "number": index, + "string": `Item ${index}`, + "date": new Date() + } + }; + }), + columns: [ + { + propertyName: "boolean" + }, + { + propertyName: "number" + }, + { + propertyName: "string" + }, + { + propertyName: "date" + }, + { + propertyName: "empty" + } + ] +}; + +const LONG_STRINGS: ResultListInput = { + data: [ + { + id: 1, + properties: { + short: "Short 1", + long: `LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG LONG ` + } + }, + { + id: 2, + properties: { + short: "Short 2", + long: `LONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONGLONG` + } + } + ], + columns: [ + { + propertyName: "short" + }, + { + propertyName: "long", + width: 300 + } + ] +}; + +const MANY_COLUMS: ResultListInput = { + columns: [ + { + propertyName: "a" + }, + { + propertyName: "b" + }, + { + propertyName: "c" + }, + { + propertyName: "d" + }, + { + propertyName: "e" + }, + { + propertyName: "f" + }, + { + propertyName: "g" + }, + { + propertyName: "h" + }, + { + propertyName: "i" + } + ], + data: [ + { + id: 0, + properties: { + a: 1, + b: 1, + c: 1, + d: 1, + e: 1, + f: 1, + g: 1, + h: 1, + i: 1 + } + } + ] +}; diff --git a/src/samples/test-result-list/result-list-app/CHANGELOG.md b/src/samples/test-result-list/result-list-app/CHANGELOG.md new file mode 100644 index 00000000..56919099 --- /dev/null +++ b/src/samples/test-result-list/result-list-app/CHANGELOG.md @@ -0,0 +1 @@ +# result-list-app diff --git a/src/samples/test-result-list/result-list-app/MapConfigProviderImpl.ts b/src/samples/test-result-list/result-list-app/MapConfigProviderImpl.ts new file mode 100644 index 00000000..d348c549 --- /dev/null +++ b/src/samples/test-result-list/result-list-app/MapConfigProviderImpl.ts @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) +// SPDX-License-Identifier: Apache-2.0 +import { MapConfig, MapConfigProvider, SimpleLayer } from "@open-pioneer/map"; +import TileLayer from "ol/layer/Tile"; +import OSM from "ol/source/OSM"; + +export const MAP_ID = "main"; + +export class MapConfigProviderImpl implements MapConfigProvider { + mapId = MAP_ID; + + async getMapConfig(): Promise { + return { + initialView: { + kind: "position", + center: { x: 404747, y: 5757920 }, + zoom: 14 + }, + layers: [ + new SimpleLayer({ + title: "OSM", + isBaseLayer: true, + olLayer: new TileLayer({ + source: new OSM() + }) + }) + ] + }; + } +} diff --git a/src/samples/test-result-list/result-list-app/app.ts b/src/samples/test-result-list/result-list-app/app.ts new file mode 100644 index 00000000..40d84c8a --- /dev/null +++ b/src/samples/test-result-list/result-list-app/app.ts @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) +// SPDX-License-Identifier: Apache-2.0 +import { createCustomElement } from "@open-pioneer/runtime"; +import { theme } from "@open-pioneer/theme"; +import * as appMetadata from "open-pioneer:app"; +import { AppUI } from "./AppUI"; + +const element = createCustomElement({ + component: AppUI, + theme, + appMetadata +}); + +customElements.define("result-list-app", element); diff --git a/src/samples/test-result-list/result-list-app/build.config.mjs b/src/samples/test-result-list/result-list-app/build.config.mjs new file mode 100644 index 00000000..28f8b7ec --- /dev/null +++ b/src/samples/test-result-list/result-list-app/build.config.mjs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) +// SPDX-License-Identifier: Apache-2.0 +import { defineBuildConfig } from "@open-pioneer/build-support"; + +export default defineBuildConfig({ + i18n: ["en"], + services: { + MapConfigProviderImpl: { + provides: ["map.MapConfigProvider"] + } + } +}); diff --git a/src/samples/test-result-list/result-list-app/i18n/en.yaml b/src/samples/test-result-list/result-list-app/i18n/en.yaml new file mode 100644 index 00000000..2acec4da --- /dev/null +++ b/src/samples/test-result-list/result-list-app/i18n/en.yaml @@ -0,0 +1 @@ +messages: diff --git a/src/samples/test-result-list/result-list-app/package.json b/src/samples/test-result-list/result-list-app/package.json new file mode 100644 index 00000000..faabcf9e --- /dev/null +++ b/src/samples/test-result-list/result-list-app/package.json @@ -0,0 +1,14 @@ +{ + "name": "result-list-app", + "private": true, + "dependencies": { + "@open-pioneer/chakra-integration": "^1.1.1", + "@open-pioneer/map": "workspace:^", + "@open-pioneer/react-utils": "workspace:^", + "@open-pioneer/runtime": "^2.1.0", + "@open-pioneer/result-list": "workspace:^", + "@open-pioneer/theme": "workspace:^", + "react": "^18.2.0" + }, + "version": "0.0.1" +} diff --git a/src/samples/test-result-list/result-list-app/services.ts b/src/samples/test-result-list/result-list-app/services.ts new file mode 100644 index 00000000..60f6dc66 --- /dev/null +++ b/src/samples/test-result-list/result-list-app/services.ts @@ -0,0 +1,3 @@ +// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) +// SPDX-License-Identifier: Apache-2.0 +export { MapConfigProviderImpl } from "./MapConfigProviderImpl"; diff --git a/vite.config.ts b/vite.config.ts index b65c2645..37500a16 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -25,6 +25,7 @@ const sampleSites = [ "samples/test-toc", "samples/test-highlight-and-zoom", "samples/test-menu-fix", + "samples/test-result-list", "samples/experimental-sidebar", ];