@lad-sapienza/scms-core 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +73 -0
  3. package/components/BSNavbar.tsx +264 -0
  4. package/components/DataTb/DataTb.astro +33 -0
  5. package/components/DataTb/DataTb.tsx +340 -0
  6. package/components/DataTb/DataTbMdx.tsx +75 -0
  7. package/components/DataTb/README.md +275 -0
  8. package/components/DataTb/index.ts +36 -0
  9. package/components/DataTb/sources/ApiSource.tsx +89 -0
  10. package/components/DataTb/sources/CsvSource.tsx +69 -0
  11. package/components/DataTb/sources/DirectusSource.tsx +98 -0
  12. package/components/DataTb/sources/JsonSource.tsx +65 -0
  13. package/components/DataTb/sources/index.ts +17 -0
  14. package/components/DataTb/types.ts +215 -0
  15. package/components/DataTb/utils.ts +186 -0
  16. package/components/Gallery/Gallery.astro +65 -0
  17. package/components/Gallery/Gallery.tsx +136 -0
  18. package/components/Gallery/README.md +118 -0
  19. package/components/Gallery/__tests__/galleryUtils.test.ts +178 -0
  20. package/components/Gallery/galleryUtils.ts +186 -0
  21. package/components/Gallery/index.ts +7 -0
  22. package/components/Gallery/react-photoswipe-gallery.d.ts +26 -0
  23. package/components/Gallery/types.ts +46 -0
  24. package/components/Gallery/virtual-gallery.d.ts +15 -0
  25. package/components/Map/LayerControl.tsx +368 -0
  26. package/components/Map/Map.tsx +735 -0
  27. package/components/Map/MapMdx.tsx +37 -0
  28. package/components/Map/RasterLayerLibre.tsx +33 -0
  29. package/components/Map/Search/SearchOperators.ts +31 -0
  30. package/components/Map/Search/SearchUI.tsx +75 -0
  31. package/components/Map/Search/SearchUIAdvanced.tsx +289 -0
  32. package/components/Map/Search/SearchUISimple.tsx +115 -0
  33. package/components/Map/Search/index.ts +7 -0
  34. package/components/Map/defaultBasemaps.ts +88 -0
  35. package/components/Map/index.ts +6 -0
  36. package/components/Map/types.ts +179 -0
  37. package/components/Map/utils.ts +367 -0
  38. package/components/README.md +62 -0
  39. package/components/Record/Field.tsx +35 -0
  40. package/components/Record/Image.tsx +114 -0
  41. package/components/Record/RecordFetcher.tsx +127 -0
  42. package/components/Record/context.tsx +14 -0
  43. package/components/Record/index.ts +11 -0
  44. package/components/Record/useRecordFetcher.ts +104 -0
  45. package/components/Record/utils.ts +13 -0
  46. package/components/SEO/SEO.astro +132 -0
  47. package/components/TableOfContents/TableOfContents.astro +86 -0
  48. package/components/ZoteroGeoViewer/README.md +195 -0
  49. package/components/ZoteroGeoViewer/ZoteroGeoViewer.astro +26 -0
  50. package/components/ZoteroGeoViewer/ZoteroGeoViewer.tsx +483 -0
  51. package/components/ZoteroGeoViewer/ZoteroRecordsPreview.tsx +185 -0
  52. package/components/ZoteroGeoViewer/index.ts +16 -0
  53. package/components/ZoteroGeoViewer/types.ts +123 -0
  54. package/index.ts +26 -0
  55. package/integrations/__tests__/galleryIntegration.test.ts +70 -0
  56. package/integrations/contentAssetsIntegration.ts +132 -0
  57. package/integrations/directusLoader.ts +98 -0
  58. package/integrations/galleryIntegration.ts +78 -0
  59. package/package.json +56 -0
  60. package/scms.ts +95 -0
  61. package/utils/content.ts +88 -0
  62. package/utils/data-fetcher.ts +128 -0
  63. package/utils/directus-config.ts +160 -0
  64. package/utils/record-fetcher.ts +132 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * MDX-safe wrapper for Map component
3
+ */
4
+
5
+ import { useMemo } from 'react';
6
+ import { Map as MapCore } from './Map';
7
+ import type { MapProps } from './types';
8
+
9
+ export function Map(props: MapProps) {
10
+ // Add props validation
11
+ if (!props) {
12
+ console.warn('Map component received undefined props');
13
+ return null;
14
+ }
15
+
16
+ // Memoize center prop
17
+ const stableCenter = useMemo(() => props?.center, [props?.center]);
18
+
19
+ // Memoize array/object props to prevent unnecessary re-renders in MDX
20
+ const stableBaseLayers = useMemo(() => props?.baseLayers, [JSON.stringify(props?.baseLayers || [])]);
21
+ const stableVectorLayers = useMemo(() => props?.vectorLayers, [JSON.stringify(props?.vectorLayers || [])]);
22
+ const stableGeojson = useMemo(() => props?.geojson, [JSON.stringify(props?.geojson || null)]);
23
+ const stableCsv = useMemo(() => props?.csv, [JSON.stringify(props?.csv || null)]);
24
+ const stableJson = useMemo(() => props?.json, [JSON.stringify(props?.json || null)]);
25
+
26
+ return (
27
+ <MapCore
28
+ {...props}
29
+ center={stableCenter}
30
+ baseLayers={stableBaseLayers}
31
+ vectorLayers={stableVectorLayers}
32
+ geojson={stableGeojson}
33
+ csv={stableCsv}
34
+ json={stableJson}
35
+ />
36
+ );
37
+ }
@@ -0,0 +1,33 @@
1
+
2
+ import { Source, Layer } from '@vis.gl/react-maplibre';
3
+
4
+ interface RasterLayerProps {
5
+ url: string[];
6
+ attribution?: string;
7
+ tileSize?: number;
8
+ id?: string;
9
+ beforeId?: string;
10
+ visible?: boolean;
11
+ }
12
+
13
+ export function RasterLayerLibre({ url, attribution, tileSize = 256, id = 'base-layer', beforeId, visible = true }: RasterLayerProps) {
14
+ return (
15
+ <Source
16
+ id={`source-${id}`}
17
+ type="raster"
18
+ tiles={url}
19
+ tileSize={tileSize}
20
+ attribution={attribution}
21
+ >
22
+ <Layer
23
+ id={id}
24
+ type="raster"
25
+ paint={{}}
26
+ beforeId={beforeId}
27
+ layout={{
28
+ visibility: visible ? 'visible' : 'none'
29
+ }}
30
+ />
31
+ </Source>
32
+ );
33
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Search Operators Configuration
3
+ * Default search operators with their UI labels
4
+ */
5
+
6
+ import type { SearchOperators } from '../types';
7
+
8
+ export const DEFAULT_OPERATORS: SearchOperators = {
9
+ _eq: "Equals",
10
+ _neq: "Doesn't equal",
11
+ _lt: "Less than",
12
+ _lte: "Less than or equal to",
13
+ _gt: "Greater than",
14
+ _gte: "Greater than or equal to",
15
+ _null: "Is null",
16
+ _nnull: "Isn't null",
17
+ _contains: "Contains",
18
+ _icontains: "Contains (case-insensitive)",
19
+ _ncontains: "Doesn't contain",
20
+ _starts_with: "Starts with",
21
+ _istarts_with: "Starts with (case-insensitive)",
22
+ _nstarts_with: "Doesn't start with",
23
+ _ends_with: "Ends with",
24
+ _iends_with: "Ends with (case-insensitive)",
25
+ _nends_with: "Doesn't end with"
26
+ };
27
+
28
+ export const DEFAULT_CONNECTORS = {
29
+ _and: "AND",
30
+ _or: "OR"
31
+ };
@@ -0,0 +1,75 @@
1
+ import { useState } from 'react';
2
+ import type { SearchInFields, SearchQuery, SearchOperators } from '../types';
3
+ import { SearchUISimple } from './SearchUISimple';
4
+ import { SearchUIAdvanced } from './SearchUIAdvanced';
5
+
6
+ export interface SearchUIProps {
7
+ fieldList: SearchInFields;
8
+ onSearch: (query: SearchQuery) => void;
9
+ operators?: Partial<SearchOperators>;
10
+ isLoading?: boolean;
11
+ limitTo?: 'simple' | 'advanced';
12
+ currentQuery?: SearchQuery;
13
+ }
14
+
15
+ /**
16
+ * Main search UI component
17
+ * Handles switching between simple and advanced search modes
18
+ */
19
+ export function SearchUI({
20
+ fieldList,
21
+ onSearch,
22
+ operators,
23
+ isLoading = false,
24
+ limitTo,
25
+ currentQuery
26
+ }: SearchUIProps) {
27
+ const [isAdvanced, setIsAdvanced] = useState(limitTo === 'advanced');
28
+
29
+ const toggleMode = () => {
30
+ if (limitTo) return; // Don't allow toggling if limited to one mode
31
+ setIsAdvanced(!isAdvanced);
32
+ };
33
+
34
+ if (!fieldList || Object.keys(fieldList).length === 0) {
35
+ return (
36
+ <div className="text-danger small">
37
+ No searchable fields configured for this layer.
38
+ </div>
39
+ );
40
+ }
41
+
42
+ // Show advanced by default if limitTo is 'advanced', simple if limitTo is 'simple'
43
+ const showAdvanced = limitTo === 'advanced' || (limitTo !== 'simple' && isAdvanced);
44
+
45
+ return (
46
+ <div className="search-ui">
47
+ {isLoading && (
48
+ <div className="text-center mb-3">
49
+ <div className="spinner-border text-primary" role="status">
50
+ <span className="visually-hidden">Loading...</span>
51
+ </div>
52
+ </div>
53
+ )}
54
+
55
+ {showAdvanced ? (
56
+ <SearchUIAdvanced
57
+ fieldList={fieldList}
58
+ onSearch={onSearch}
59
+ operators={operators}
60
+ isLoading={isLoading}
61
+ onToggleSimple={limitTo ? undefined : toggleMode}
62
+ currentQuery={currentQuery}
63
+ />
64
+ ) : (
65
+ <SearchUISimple
66
+ fieldList={fieldList}
67
+ onSearch={onSearch}
68
+ isLoading={isLoading}
69
+ onToggleAdvanced={limitTo ? undefined : toggleMode}
70
+ currentQuery={currentQuery}
71
+ />
72
+ )}
73
+ </div>
74
+ );
75
+ }
@@ -0,0 +1,289 @@
1
+ import { useState, type SyntheticEvent } from 'react';
2
+ import { Search, X, Plus, Minus, ArrowLeft } from 'lucide-react';
3
+ import type { SearchInFields, SearchQuery, SearchFilter, SearchOperators } from '../types';
4
+ import { DEFAULT_OPERATORS, DEFAULT_CONNECTORS } from './SearchOperators';
5
+
6
+ export interface SearchUIAdvancedProps {
7
+ fieldList: SearchInFields;
8
+ onSearch: (query: SearchQuery) => void;
9
+ operators?: Partial<SearchOperators>;
10
+ isLoading?: boolean;
11
+ onToggleSimple?: () => void;
12
+ currentQuery?: SearchQuery;
13
+ }
14
+
15
+ /**
16
+ * Advanced search UI component
17
+ * Provides multiple field-operator-value triplets with AND/OR logic
18
+ */
19
+ export function SearchUIAdvanced({
20
+ fieldList,
21
+ onSearch,
22
+ operators = {},
23
+ isLoading = false,
24
+ onToggleSimple,
25
+ currentQuery
26
+ }: SearchUIAdvancedProps) {
27
+ const mergedOperators = { ...DEFAULT_OPERATORS, ...operators };
28
+
29
+ // Initialize state from currentQuery if provided
30
+ const getInitialConnector = (): '_and' | '_or' => {
31
+ return currentQuery?.connector || '_and';
32
+ };
33
+
34
+ const getInitialFilters = (): SearchFilter[] => {
35
+ if (currentQuery?.filters?.length) {
36
+ return currentQuery.filters;
37
+ }
38
+ return [{
39
+ field: Object.keys(fieldList)[0] || '',
40
+ operator: Object.keys(mergedOperators)[0] || '_eq',
41
+ value: ''
42
+ }];
43
+ };
44
+
45
+ const [connector, setConnector] = useState<'_and' | '_or'>(getInitialConnector());
46
+ const [filters, setFilters] = useState<SearchFilter[]>(getInitialFilters());
47
+
48
+ // Get the first field's default value if it has predefined values
49
+ const getDefaultValue = (fieldName: string) => {
50
+ const fieldConfig = fieldList[fieldName];
51
+ if (typeof fieldConfig === 'object' && fieldConfig.values && fieldConfig.values.length > 0) {
52
+ return fieldConfig.values[0];
53
+ }
54
+ return '';
55
+ };
56
+
57
+ const addFilter = () => {
58
+ const firstField = Object.keys(fieldList)[0] || '';
59
+ setFilters([...filters, {
60
+ field: firstField,
61
+ operator: Object.keys(mergedOperators)[0] || '_eq',
62
+ value: getDefaultValue(firstField)
63
+ }]);
64
+ };
65
+
66
+ const removeFilter = (index: number) => {
67
+ if (filters.length > 1) {
68
+ setFilters(filters.filter((_, i) => i !== index));
69
+ }
70
+ };
71
+
72
+ const updateFilter = (index: number, field: keyof SearchFilter, value: any) => {
73
+ const newFilters = [...filters];
74
+ newFilters[index] = { ...newFilters[index], [field]: value };
75
+
76
+ // If field changed, update default value
77
+ if (field === 'field') {
78
+ newFilters[index].value = getDefaultValue(value);
79
+ }
80
+
81
+ setFilters(newFilters);
82
+ };
83
+
84
+ const handleSubmit = (e: SyntheticEvent) => {
85
+ e.preventDefault();
86
+
87
+ // Filter out empty values
88
+ const validFilters = filters.filter(f => f.field && f.value !== '');
89
+
90
+ onSearch({
91
+ connector,
92
+ filters: validFilters
93
+ });
94
+ };
95
+
96
+ const handleClear = () => {
97
+ setFilters([{
98
+ field: Object.keys(fieldList)[0] || '',
99
+ operator: Object.keys(mergedOperators)[0] || '_eq',
100
+ value: ''
101
+ }]);
102
+ onSearch({ connector: '_or', filters: [] });
103
+ };
104
+
105
+ const hasFilters = () => {
106
+ return filters.some(f => f.field && f.value !== '');
107
+ };
108
+
109
+ const getFieldLabel = (fieldName: string): string => {
110
+ const fieldConfig = fieldList[fieldName];
111
+ return typeof fieldConfig === 'string' ? fieldConfig : fieldConfig?.label || fieldName;
112
+ };
113
+
114
+ const hasFieldValues = (fieldName: string): boolean => {
115
+ const fieldConfig = fieldList[fieldName];
116
+ return typeof fieldConfig === 'object' && Array.isArray(fieldConfig.values) && fieldConfig.values.length > 0;
117
+ };
118
+
119
+ const getFieldValues = (fieldName: string): string[] => {
120
+ const fieldConfig = fieldList[fieldName];
121
+ if (typeof fieldConfig === 'object' && fieldConfig.values) {
122
+ return fieldConfig.values;
123
+ }
124
+ return [];
125
+ };
126
+
127
+ return (
128
+ <div className="search-ui-advanced">
129
+ <form onSubmit={handleSubmit}>
130
+ {/* Connector selection (only show if more than one filter) */}
131
+ {filters.length > 1 && (
132
+ <div className="mb-3">
133
+ <div className="d-flex gap-3">
134
+ <label className="d-flex align-items-center gap-2">
135
+ <input
136
+ type="radio"
137
+ name="connector"
138
+ value="_and"
139
+ checked={connector === '_and'}
140
+ onChange={(e) => setConnector(e.target.value as '_and' | '_or')}
141
+ className="form-check-input"
142
+ />
143
+ <span className="small">{DEFAULT_CONNECTORS._and}</span>
144
+ </label>
145
+
146
+ <label className="d-flex align-items-center gap-2">
147
+ <input
148
+ type="radio"
149
+ name="connector"
150
+ value="_or"
151
+ checked={connector === '_or'}
152
+ onChange={(e) => setConnector(e.target.value as '_and' | '_or')}
153
+ className="form-check-input"
154
+ />
155
+ <span className="small">{DEFAULT_CONNECTORS._or}</span>
156
+ </label>
157
+ </div>
158
+ </div>
159
+ )}
160
+
161
+ {/* Filter rows */}
162
+ {filters.map((filter, index) => (
163
+ <div key={index} className="row g-2 mb-2 align-items-center">
164
+ {/* Field selection */}
165
+ <div className="col-3">
166
+ <select
167
+ className="form-select form-select-sm"
168
+ value={filter.field}
169
+ onChange={(e) => updateFilter(index, 'field', e.target.value)}
170
+ disabled={isLoading}
171
+ >
172
+ {Object.entries(fieldList).map(([fieldName, _config]) => (
173
+ <option key={fieldName} value={fieldName}>
174
+ {getFieldLabel(fieldName)}
175
+ </option>
176
+ ))}
177
+ </select>
178
+ </div>
179
+
180
+ {/* Operator selection */}
181
+ <div className="col-3">
182
+ <select
183
+ className="form-select form-select-sm"
184
+ value={filter.operator}
185
+ onChange={(e) => updateFilter(index, 'operator', e.target.value)}
186
+ disabled={isLoading}
187
+ >
188
+ {Object.entries(mergedOperators).map(([op, label]) => (
189
+ <option key={op} value={op}>
190
+ {label}
191
+ </option>
192
+ ))}
193
+ </select>
194
+ </div>
195
+
196
+ {/* Value input */}
197
+ <div className="col-4">
198
+ {hasFieldValues(filter.field) ? (
199
+ <select
200
+ className="form-select form-select-sm"
201
+ value={filter.value}
202
+ onChange={(e) => updateFilter(index, 'value', e.target.value)}
203
+ disabled={isLoading}
204
+ >
205
+ {getFieldValues(filter.field).map(value => (
206
+ <option key={value} value={value}>
207
+ {value}
208
+ </option>
209
+ ))}
210
+ </select>
211
+ ) : (
212
+ <input
213
+ type="text"
214
+ className="form-control form-control-sm"
215
+ value={filter.value}
216
+ onChange={(e) => updateFilter(index, 'value', e.target.value)}
217
+ disabled={isLoading}
218
+ />
219
+ )}
220
+ </div>
221
+
222
+ {/* Actions */}
223
+ <div className="col-2 d-flex gap-1">
224
+ <button
225
+ type="button"
226
+ className="btn btn-danger btn-sm"
227
+ onClick={() => removeFilter(index)}
228
+ disabled={isLoading || filters.length === 1}
229
+ title={filters.length === 1 ? "Cannot remove the last filter" : "Remove filter"}
230
+ >
231
+ <Minus size={16} />
232
+ </button>
233
+ </div>
234
+ </div>
235
+ ))}
236
+
237
+ {/* Add Filter Button */}
238
+ <div className="mb-3">
239
+ <button
240
+ type="button"
241
+ className="btn btn-secondary btn-sm"
242
+ onClick={addFilter}
243
+ disabled={isLoading}
244
+ >
245
+ <Plus size={16} />
246
+ Add Filter
247
+ </button>
248
+ </div>
249
+
250
+ {/* Actions */}
251
+ <div className="d-flex gap-2 mt-3">
252
+ <button
253
+ type="submit"
254
+ className="btn btn-primary btn-sm"
255
+ disabled={isLoading}
256
+ >
257
+ {isLoading ? (
258
+ <span className="spinner-border spinner-border-sm" role="status">
259
+ <span className="visually-hidden">Loading...</span>
260
+ </span>
261
+ ) : (
262
+ <Search size={16} />
263
+ )}
264
+ Search
265
+ </button>
266
+ <button
267
+ type="button"
268
+ className="btn btn-secondary btn-sm"
269
+ onClick={handleClear}
270
+ disabled={isLoading || !hasFilters()}
271
+ >
272
+ <X size={16} />
273
+ Clear
274
+ </button>
275
+ {onToggleSimple && (
276
+ <button
277
+ type="button"
278
+ className="btn btn-sm btn-link"
279
+ onClick={onToggleSimple}
280
+ >
281
+ <ArrowLeft size={16} />
282
+ Simple Search
283
+ </button>
284
+ )}
285
+ </div>
286
+ </form>
287
+ </div>
288
+ );
289
+ }
@@ -0,0 +1,115 @@
1
+ import { useState, type SyntheticEvent } from 'react';
2
+ import { Search, X, TextSearch } from 'lucide-react';
3
+ import type { SearchInFields, SearchQuery, SearchFilter } from '../types';
4
+
5
+ export interface SearchUISimpleProps {
6
+ fieldList: SearchInFields;
7
+ onSearch: (query: SearchQuery) => void;
8
+ isLoading?: boolean;
9
+ onToggleAdvanced?: () => void;
10
+ currentQuery?: SearchQuery;
11
+ }
12
+
13
+ /**
14
+ * Simple search UI component
15
+ * Provides a single text input that searches across all configured fields using OR logic
16
+ */
17
+ export function SearchUISimple({
18
+ fieldList,
19
+ onSearch,
20
+ isLoading = false,
21
+ onToggleAdvanced,
22
+ currentQuery
23
+ }: SearchUISimpleProps) {
24
+ // Extract search text from currentQuery if it exists and is a simple search (all filters have same value)
25
+ const getInitialSearchText = () => {
26
+ if (!currentQuery?.filters?.length) return '';
27
+ const firstFilter = currentQuery.filters[0];
28
+ if (currentQuery.filters.every(f => f.value === firstFilter.value && f.operator === '_icontains')) {
29
+ return firstFilter.value;
30
+ }
31
+ return '';
32
+ };
33
+
34
+ const [searchText, setSearchText] = useState(getInitialSearchText());
35
+
36
+ const handleSubmit = (e: SyntheticEvent) => {
37
+ e.preventDefault();
38
+
39
+ if (!searchText.trim()) {
40
+ // Clear search
41
+ onSearch({ connector: '_or', filters: [] });
42
+ return;
43
+ }
44
+
45
+ // Create filters for all searchable fields using case-insensitive contains
46
+ const filters: SearchFilter[] = Object.keys(fieldList).map(field => ({
47
+ field,
48
+ operator: '_icontains',
49
+ value: searchText.trim()
50
+ }));
51
+
52
+ onSearch({
53
+ connector: '_or',
54
+ filters
55
+ });
56
+ };
57
+
58
+ const handleClear = () => {
59
+ setSearchText('');
60
+ onSearch({ connector: '_or', filters: [] });
61
+ };
62
+
63
+ return (
64
+ <div className="search-ui-simple">
65
+ <form onSubmit={handleSubmit} className="mb-3">
66
+ <div className="d-flex gap-2">
67
+ <input
68
+ type="text"
69
+ className="form-control form-control-sm"
70
+ placeholder="Search..."
71
+ value={searchText}
72
+ onChange={(e) => setSearchText(e.target.value)}
73
+ disabled={isLoading}
74
+ />
75
+ <button
76
+ type="submit"
77
+ className="btn btn-primary btn-sm"
78
+ disabled={isLoading}
79
+ >
80
+ {isLoading ? (
81
+ <span className="spinner-border spinner-border-sm" role="status">
82
+ <span className="visually-hidden">Loading...</span>
83
+ </span>
84
+ ) : (
85
+ <Search size={16} />
86
+ )}
87
+ Search
88
+ </button>
89
+ <button
90
+ type="button"
91
+ className="btn btn-secondary btn-sm"
92
+ onClick={handleClear}
93
+ disabled={isLoading || !searchText}
94
+ >
95
+ <X size={16} />
96
+ Clear
97
+ </button>
98
+ </div>
99
+ </form>
100
+
101
+ {onToggleAdvanced && (
102
+ <div className="text-center">
103
+ <button
104
+ type="button"
105
+ className="btn btn-sm btn-link"
106
+ onClick={onToggleAdvanced}
107
+ >
108
+ <TextSearch size={16} />
109
+ Advanced Search
110
+ </button>
111
+ </div>
112
+ )}
113
+ </div>
114
+ );
115
+ }
@@ -0,0 +1,7 @@
1
+ export { SearchUI } from './SearchUI';
2
+ export { SearchUISimple } from './SearchUISimple';
3
+ export { SearchUIAdvanced } from './SearchUIAdvanced';
4
+ export { DEFAULT_OPERATORS, DEFAULT_CONNECTORS } from './SearchOperators';
5
+ export type { SearchUIProps } from './SearchUI';
6
+ export type { SearchUISimpleProps } from './SearchUISimple';
7
+ export type { SearchUIAdvancedProps } from './SearchUIAdvanced';
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Default Basemap configurations
3
+ * Based on https://github.com/lad-sapienza/sCMS/blob/main/src/modules/maps/defaultBaseLayers.js
4
+ */
5
+
6
+ import type { BaseLayerConfig } from './types';
7
+
8
+ export const defaultBasemaps: Record<string, BaseLayerConfig> = {
9
+ CAWM: {
10
+ name: "Ancient World Map",
11
+ url: "https://cawm.lib.uiowa.edu/tiles/{z}/{x}/{y}.png",
12
+ attribution: '&copy; <a href="http://cawm.lib.uiowa.edu/index.html">Consortium of Ancient World Mappers</a>',
13
+ },
14
+ OSM: {
15
+ name: "Open Street Map",
16
+ url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
17
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
18
+ },
19
+ EsriSatellite: {
20
+ name: "Esri Imagery/Satellite",
21
+ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
22
+ },
23
+ EsriStreets: {
24
+ name: "Esri Streets",
25
+ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}",
26
+ },
27
+ EsriTopo: {
28
+ name: "Esri Topo",
29
+ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}",
30
+ },
31
+ GoogleSatellite: {
32
+ name: "Google Satellite",
33
+ url: "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}",
34
+ },
35
+ GoogleRoadmap: {
36
+ name: "Google Roadmap",
37
+ url: "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}",
38
+ },
39
+ GoogleTerrain: {
40
+ name: "Google Terrain",
41
+ url: "https://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}",
42
+ },
43
+ GoogleAlteredRoadmap: {
44
+ name: "Google Altered Roadmap",
45
+ url: "https://mt1.google.com/vt/lyrs=r&x={x}&y={y}&z={z}",
46
+ },
47
+ CartoDB: {
48
+ name: "CartoDB",
49
+ url: "https://cartodb-basemaps-a.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png",
50
+ },
51
+ StamenTerrain: {
52
+ name: "Stamen Terrain",
53
+ url: "https://a.tile.stamen.com/terrain/{z}/{x}/{y}.png",
54
+ },
55
+ OSMMapnick: {
56
+ name: "OpenStreetMap Mapnick",
57
+ url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
58
+ },
59
+ OSMCycle: {
60
+ name: "OSM Cycle Map",
61
+ url: "https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png",
62
+ },
63
+ Imperium: {
64
+ name: "Imperium (DARE)",
65
+ url: "https://dh.gu.se/tiles/imperium/{z}/{x}/{y}.png",
66
+ }
67
+ };
68
+
69
+ /**
70
+ * Valid basemap keys
71
+ */
72
+ export type BasemapKey = keyof typeof defaultBasemaps;
73
+
74
+ /**
75
+ * Get basemap configuration by key
76
+ */
77
+ export function getBasemap(key: string): BaseLayerConfig | undefined {
78
+ return defaultBasemaps[key];
79
+ }
80
+
81
+ /**
82
+ * Get multiple basemaps by keys
83
+ */
84
+ export function getBasemaps(keys: string[]): BaseLayerConfig[] {
85
+ return keys
86
+ .map(key => defaultBasemaps[key])
87
+ .filter((config): config is BaseLayerConfig => config !== undefined);
88
+ }
@@ -0,0 +1,6 @@
1
+ export { Map } from './MapMdx';
2
+ export { Map as MapCore } from './Map';
3
+ export { defaultBasemaps, getBasemap, getBasemaps } from './defaultBasemaps';
4
+ export type { BasemapKey } from './defaultBasemaps';
5
+ export type { VectorLayerConfig } from './types';
6
+ export * from './types';