forked from geosolutions-it/MapStore2
-
Notifications
You must be signed in to change notification settings - Fork 1
/
CompactCatalog.jsx
152 lines (148 loc) · 6.87 KB
/
CompactCatalog.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
* Copyright 2017, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
import { isNil, isObject, isEmpty } from 'lodash';
import React from 'react';
import { compose, mapPropsStream, withPropsOnChange } from 'recompose';
import Rx from 'rxjs';
import uuid from 'uuid';
import API from '../../api/catalog';
import { getCatalogRecords } from '../../utils/CatalogUtils';
import Message from '../I18N/Message';
import BorderLayout from '../layout/BorderLayout';
import SideGridComp from '../misc/cardgrids/SideGrid';
import emptyState from '../misc/enhancers/emptyState';
import withVirtualScroll from '../misc/enhancers/infiniteScroll/withInfiniteScroll';
import loadingState from '../misc/enhancers/loadingState';
import withControllableState from '../misc/enhancers/withControllableState';
import Icon from '../misc/FitIcon';
import LoadingSpinner from '../misc/LoadingSpinner';
import CatalogForm from './CatalogForm';
const defaultPreview = <Icon glyph="geoserver" padding={20}/>;
const SideGrid = compose(
loadingState(({loading, items = []} ) => items.length === 0 && loading),
emptyState(
({loading, error} ) => !loading && error,
{
title: <Message msgId="catalog.error" />,
style: { transform: "translateY(50%)"}
}),
emptyState(
({loading, items = []} ) => items.length === 0 && !loading,
{
title: <Message msgId="catalog.noRecordsMatched" />,
style: { transform: "translateY(50%)"}
})
)(SideGridComp);
/*
* assigns an identifier to a record
*/
/*
* assigns an identifier to a record. The ID is required for local selection.
* TODO: improve identifier generation.
*/
const getIdentifier = (r) =>
r.identifier ? r.identifier
: r.provider
? r.provider + (r.variant ?? "") // existing tileprovider
: (r.tileMapUrl // TMS 1.0.0
|| r.url + uuid()); // default
/*
* converts record item into a item for SideGrid
*/
const resToProps = ({records, result = {}, catalog = {}}) => ({
items: (records || []).map((record = {}) => ({
title: record.title && isObject(record.title) && record.title.default || record.title,
caption: getIdentifier(record),
description: record.description,
preview: !catalog.hideThumbnail ? record.thumbnail ? <img src={record.thumbnail} /> : defaultPreview : null,
record: {
...record, identifier: getIdentifier(record)
}
})),
total: result && result.numberOfRecordsMatched
});
const PAGE_SIZE = 10;
/*
* retrieves data from a catalog service and converts to props
*/
const loadPage = ({text, catalog = {}}, page = 0) => {
const type = catalog.type;
const _tempOption = {options: {service: catalog}};
let options = {};
if (type === 'csw') {
options = {..._tempOption, filter: catalog.filter};
} else if (type === 'tms') {
options = _tempOption;
}
return Rx.Observable
.fromPromise(API[type].textSearch(catalog.url, page * PAGE_SIZE + (type === "csw" ? 1 : 0), PAGE_SIZE, text, options))
.map((result) => ({ result, records: getCatalogRecords(type, result || [], { url: catalog && catalog.url, service: catalog })}))
.map(({records, result}) => resToProps({records, result, catalog}));
};
const scrollSpyOptions = {querySelector: ".ms2-border-layout-body .ms2-border-layout-content", pageSize: PAGE_SIZE};
/**
* Compat catalog : Reusable catalog component, with infinite scroll.
* You can simply pass the catalog to browse and the handler onRecordSelected.
* @example
* <CompactCatalog catalog={type: "csw", url: "..."} onSelected={selected => console.log(selected)} />
* @name CompactCatalog
* @memberof components.catalog
* @prop {object} catalog the definition of the selected catalog as `{type: "wms"|"wmts"|"csw", url: "..."}`
* @prop {object} selected the record selected. Passing this will show it as selected (highlighted) in the list. It will compare record's `identifier` property to guess the selected record in the list
* @prop {function} onRecordSelected
* @prop {boolean} showCatalogSelector if true shows the catalog selector - TODO
* @prop {array} services TODO allow selection of catalog from a list
* @prop {string} [searchText] the search text (if you want to control it)
* @prop {function} [setSearchText] handler to get search text changes (if not defined, the component will control the text by it's own)
*/
export default compose(
withControllableState('searchText', "setSearchText", ""),
withVirtualScroll({loadPage, scrollSpyOptions}),
mapPropsStream( props$ =>
props$.merge(props$.take(1).switchMap(({loadFirst = () => {}, services }) =>
props$
.debounceTime(500)
.startWith({searchText: ""})
.distinctUntilKeyChanged('searchText')
.do(({searchText, selectedService: nextSelectedService} = {}) => !isEmpty(services[nextSelectedService]) && loadFirst({text: searchText, catalog: services[nextSelectedService] }))
.ignoreElements() // don't want to emit props
))),
withPropsOnChange(['selectedService'], props => {
const service = props.services[props.selectedService];
if (!isEmpty(service)) {
props.loadFirst({text: props.searchText || "", catalog: service});
}
})
)(({ setSearchText = () => { }, selected, onRecordSelected, loading, searchText, items = [], total, catalog, services, title, showCatalogSelector = true, error,
onChangeSelectedService = () => {},
selectedService, onChangeCatalogMode = () => {}}) => {
return (<BorderLayout
className="compat-catalog"
header={<CatalogForm onChangeCatalogMode={onChangeCatalogMode} onChangeSelectedService={onChangeSelectedService}
services={Object.keys(services).map(key =>({ label: services[key]?.title, value: {...services[key], key}}))}
selectedService={services[selectedService]} showCatalogSelector={showCatalogSelector}
title={title}
searchText={searchText}
onSearchTextChange={setSearchText}/>}
footer={<div className="catalog-footer">
{loading ? <LoadingSpinner /> : null}
{!isNil(total) ? <span className="res-info"><Message msgId="catalog.pageInfoInfinite" msgParams={{loaded: items.length, total}}/></span> : null}
</div>}>
<SideGrid
items={items.map(i =>
i === selected
|| selected
&& i && i.record
&& selected.identifier === i.record.identifier
? {...i, selected: true}
: i)}
loading={loading}
error={error}
onItemClick={({record} = {}) => onRecordSelected(record, catalog)}/>
</BorderLayout>);
});