AppContent.jsx 18.4 KB
Newer Older
1
import React, { useEffect, useReducer, useState } from 'react';
2
3
4
import { useTranslation } from 'react-i18next';
import { latLngBounds } from 'leaflet';
import { useQuery } from "@apollo/react-hooks";
5
import {
6
    DashboardTile, DataSources, Histogram, ImageContents, Layout, OurMap, PageHeader, ResultsTable, ShowNext, Timeline
7
} from "..";
8
import { LinearProgress } from "@material-ui/core";
9
// Queries
10
11
12
13
import {
    byRegion as GET_SITES_BY_REGION, searchArchaeoSites as GET_ARCHAEOLOGICAL_SITES,
    searchObjectContext as GET_OBJECT_CONTEXT, searchObjects as GET_OBJECTS
} from "./queries.graphql";
14
import { timelineAdapter, timelineMapper, useDebounce } from "../../utils";
15
import { useStyles } from '../../styles';
16
import Container from "@material-ui/core/Container";
17

18
19
20
const initialInput = {
    mapBounds: latLngBounds([28.906303, -11.146792], [-3.355435, 47.564145]),
    zoomLevel: 5,
21
    clusterMarkers: true,
22
23
24
    objectId: 0,
    regionId: 0,
    regionTitle: null,
amarcic's avatar
amarcic committed
25
    searchStr: "brosche",
26
    catalogIdsList: [{"catalogLabel": "All SPP 2143 Arachne data", "catalogId": 123},
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
27
        {"catalogLabel": "AAArC - Fundplätze", "catalogId": 942},],
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
28
29
    checkedCatalogIds: [],
    checkedCatalogLabels: [],
30
    mode: "objects",
31
    sitesMode: "",
32
33
    showSearchResults: true,
    showArchaeoSites: false,
34
35
36
37
38
    showRelatedObjects: false,
    chronOntologyTerm: null,
    boundingBoxCorner1: [],
    boundingBoxCorner2: [],
    drawBBox: false,
39
    mapControlsExpanded: false,
40
    resultsListExpanded: true,
41
    selectedMarker: undefined,
42
    timelineSort: "period",
43
    highlightedTimelineObject: undefined,
44
    areaA: 1,
45
    areaB: 0,
46
47
    bigTileArea: "",
    arachneTypes: ["Einzelobjekte", "Topographien", "Bilder"]
48
49
50
51
52
53
};


export const AppContent = () => {
    const { t, i18n } = useTranslation();

54
55
    const classes = useStyles();

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
    // State
    function inputReducer(state, action) {
        const {type, payload} = action;
        switch (type) {
            case 'UPDATE_INPUT':
                return {
                    ...state,
                    [payload.field]: payload.value
                };
            case 'CHECK_ITEM':
                return {
                    ...state,
                    [payload.field]: [...state[payload.field], payload.toggledItem]
                };
            case 'UNCHECK_ITEM':
                return {
                    ...state,
                    [payload.field]: state[payload.field].filter(checked => checked !== payload.toggledItem)
                };
            case 'TOGGLE_STATE':
                return {
                    ...state,
                    [payload.toggledField]: !state[payload.toggledField]
                };
            case 'DRAW_BBOX':
                return {
                    ...state,
                    boundingBoxCorner1: state.boundingBoxCorner1.length === 0 ? [String(payload.lat), String(payload.lng)] : state.boundingBoxCorner1,
                    boundingBoxCorner2: state.boundingBoxCorner1.length === 0 ? state.boundingBoxCorner2 : [String(payload.lat), String(payload.lng)]
                };
            case 'MANUAL_BBOX':
                payload.value = payload.valueString.split(",").map(coordinateString => {
                    return parseFloat(coordinateString).toFixed(coordinateString.split(".")[1].length)
                });
                return {
                    ...state,
                    [payload.field]: payload.value
                };
            default:
            //return { ...state, [type]: payload };
        }
    }

    const [input, dispatch] = useReducer(inputReducer, initialInput);

Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
101
102
103
    // debounce input.searchStr
    const debouncedSearchStr = useDebounce(input.searchStr, 500);

104
105
106
107
108
109
    const [mapDataContext, setMapDataContext] = useState({});
    const [mapDataObjects, setMapDataObjects] = useState({});
    const [mapDataArchaeoSites, setMapDataArchaeoSites] = useState({});
    const [mapDataSitesByRegion, setMapDataSitesByRegion] = useState({});

    // Queries
110
    const {data: dataContext, loading: loadingContext, error: errorContext} = useQuery(GET_OBJECT_CONTEXT, input.mode === "objects"
111
112
113
114
115
116
117
        ? {variables: {arachneId: input.objectId}}
        : {variables: {arachneId: 0}});

    const {data: dataObjects, loading: loadingObjects, error: errorObjects} =
        useQuery(GET_OBJECTS, input.mode === "objects"
            ? {
                variables: {
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
118
                    searchTerm: debouncedSearchStr, catalogIds: input.checkedCatalogIds,
119
120
121
122
                    // only send coordinates if entered values have valid format (floats with at least one decimal place)
                    bbox: (/-?\d{1,2}\.\d+,-?\d{1,3}\.\d+/.test(input.boundingBoxCorner1)) && (/-?\d{1,2}\.\d+,-?\d{1,3}\.\d+/.test(input.boundingBoxCorner2))
                        ? input.boundingBoxCorner1.concat(input.boundingBoxCorner2)
                        : [],
123
124
                    periodTerm: input.chronOntologyTerm,
                    entityTypes: input.arachneTypes
125
126
                }
            }
127
            : {variables: {searchTerm: "", catalogIds: [], bbox: [], periodTerm: "", entityTypes: []}});
128
129
130
131

    const {data: dataArchaeoSites, loading: loadingArchaeoSites, error: errorArchaeoSites} = useQuery(GET_ARCHAEOLOGICAL_SITES, input.mode === "archaeoSites"
        ? {
            variables: {
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
132
                searchTerm: debouncedSearchStr,
133
134
135
136
137
138
                // only send coordinates if entered values have valid format (floats with at least one decimal place)
                bbox: (/-?\d{1,2}\.\d+,-?\d{1,3}\.\d+/.test(input.boundingBoxCorner1)) && (/-?\d{1,2}\.\d+,-?\d{1,3}\.\d+/.test(input.boundingBoxCorner2))
                    ? input.boundingBoxCorner1.concat(input.boundingBoxCorner2)
                    : []
            }
        }
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
139
140
        //: {variables: {searchTerm: "", bbox: []}});
        : {variables: {searchTerm: "''", bbox: []}});
141
142

    const {data: dataSitesByRegion, loading: loadingSitesByRegion, error: errorSitesByRegion} = useQuery(GET_SITES_BY_REGION, input.sitesMode === "region"
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
143
        ? {variables: {searchTerm: debouncedSearchStr, idOfRegion: input.regionId}}
144
145
146
147
148
149
        : {variables: {searchTerm: "", idOfRegion: 0}});


    const chronOntologyTerms = [
        'antoninisch', 'archaisch', 'augusteisch', 'FM III', 'frühkaiserzeitlich', 'geometrisch', 'hadrianisch',
        'hellenistisch', 'hochhellenistisch', 'kaiserzeitlich', 'klassisch', 'MM II', 'MM IIB', 'römisch', 'SB II',
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
150
151
        'severisch', 'SH IIIB', 'SM I', 'SM IB', 'trajanisch',
        'Altes Reich', 'Neues Reich', 'Erste Zwischenzeit', 'Holocene', 'Early Holocene', 'Middle Holocene', 'Late Holocene', 'Pleistocene'
152
153
154
155
    ];

    const regions = [
        {title: 'Africa', id: 2042601},
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
156
157
        {title: 'Benin', id: 2353200},
        {title: 'East Africa', id: 2359915},
158
        {title: 'Egypt', id: 2042786},
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
159
160
        {title: 'Horn of Africa', id: 2379066},
        {title: 'Maghreb', id: 2042694},
161
        {title: 'Meroe', id: 2293921},
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
162
        {title: 'Nubien', id: 2042608},
163
        {title: 'Republic of Namibia', id: 2293917},
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
164
        {title: 'Senegambia', id: 2348444},
165
166
167
        {title: 'Sudan', id: 2042707},
        {title: 'Tschad', id: 2128989},
        {title: 'Wadi Howar Region Sudan', id: 2042736},
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
168
        {title: 'West Africa', id: 2379014}
169
170
171
172
173
174
175
176
177
178
    ];


    const handleRelatedObjects = (id) => {
        dispatch({type: "UPDATE_INPUT", payload: {field: "objectId", value: id ? Number(id) : input.objectId}});
        dispatch({type: "TOGGLE_STATE", payload: {toggledField: "showSearchResults"}})
        dispatch({type: "TOGGLE_STATE", payload: {toggledField: "showRelatedObjects"}})
        console.log("handleRelatedObjects!");
    };

179
    const openPopup = (index) => {
180
181
182
183
184
        dispatch({type: "UPDATE_INPUT", payload: {field: "selectedMarker", value: index}});
    }


    useEffect( () => {
185
        if(dataContext && input.mode === "objects" && input.showRelatedObjects) {
186
187
188
189
190
            setMapDataContext(dataContext);
            console.log("rerender dataContext!");
            console.log("rerender dataContext --> dataContext: ", dataContext);
            console.log("rerender dataContext --> input:", input);
        }
191
    }, [dataContext, input.showRelatedObjects, input.mode]);
192
193

    useEffect( () => {
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
194
        if (dataObjects && input.mode === "objects" && input.showSearchResults && (debouncedSearchStr || input.checkedCatalogIds.length!==0 || input.chronOntologyTerm
195
196
197
198
199
200
            ||(input.boundingBoxCorner1.length!==0 && input.boundingBoxCorner2.length!==0))) {
            setMapDataObjects(dataObjects);
            console.log("rerender dataObjects!");
            console.log("rerender dataObjects --> dataObjects: ", dataObjects);
            console.log("rerender dataObjects --> input:", input);
        }
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
201
    }, [dataObjects, input.showSearchResults, debouncedSearchStr, input.checkedCatalogIds, input.chronOntologyTerm, input.boundingBoxCorner1, input.boundingBoxCorner2, input.mode]);
202
203

    useEffect( () => {
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
204
        if (dataArchaeoSites && input.showArchaeoSites && input.mode === "archaeoSites" && input.sitesMode!=="region" && (debouncedSearchStr || (input.boundingBoxCorner1.length!==0 && input.boundingBoxCorner2.length!==0))) {
205
206
207
208
209
            setMapDataArchaeoSites(dataArchaeoSites);
            console.log("rerender dataArchaeoSites!");
            console.log("rerender dataArchaeoSites --> dataArchaeoSites: ", dataArchaeoSites);
            console.log("rerender dataArchaeoSites --> input:", input);
        }
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
210
    }, [dataArchaeoSites, input.showArchaeoSites, debouncedSearchStr, input.boundingBoxCorner1, input.boundingBoxCorner2, input.sitesMode, input.mode]);
211
212

    useEffect( () => {
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
213
        if (dataSitesByRegion && input.showArchaeoSites && input.mode === "archaeoSites" && input.sitesMode==="region" && (debouncedSearchStr || input.regionId)) {
214
215
216
217
218
            setMapDataSitesByRegion(dataSitesByRegion);
            console.log("rerender dataSitesByRegion!");
            console.log("rerender dataSitesByRegion --> dataSitesByRegion: ", dataSitesByRegion);
            console.log("rerender dataSitesByRegion --> input:", input);
        }
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
219
    }, [dataSitesByRegion, input.showArchaeoSites, debouncedSearchStr, input.regionId, input.sitesMode, input.mode]);
220
221
222
223
224
225
226
227


    /* Conditions used to determine whether to render certain data (objects, related objects, sites, sites by region) */
    /* TODO: better names? use a function to check this instead? */
    const renderingConditionObjects =
        // this mode is selected
        input.showSearchResults
        // at least one relevant input not empty
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
228
        && (debouncedSearchStr || input.checkedCatalogIds.length!==0 || input.chronOntologyTerm
229
            || (input.boundingBoxCorner1.length!==0 && input.boundingBoxCorner2.length!==0))
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
        // query result not empty
        && mapDataObjects && mapDataObjects.entitiesMultiFilter;

    const renderingConditionRelatedObjects =
        // this mode is selected
        input.showRelatedObjects
        // relevant input not empty
        && input.objectId
        // query result not empty
        && mapDataContext && mapDataContext.entity;

    const renderingConditionSites =
        // this mode is selected
        input.showArchaeoSites && input.sitesMode!=="region"
        // at least one relevant input not empty
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
245
        && (debouncedSearchStr || (input.boundingBoxCorner1.length!==0 && input.boundingBoxCorner2.length!==0))
246
247
248
249
250
251
252
        // query result not empty
        && mapDataArchaeoSites && mapDataArchaeoSites.archaeologicalSites;

    const renderingConditionSitesByRegion =
        // this mode is selected
        input.showArchaeoSites && input.sitesMode==="region"
        // at least one relevant input not empty
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
253
        && (debouncedSearchStr || input.regionId)
254
255
256
        // query result not empty
        && mapDataSitesByRegion && mapDataSitesByRegion.sitesByRegion;

257
258
    const getMapData = () => {
        let mapData;
259

260
261
262
263
264
265
266
        if(renderingConditionObjects) mapData = mapDataObjects?.entitiesMultiFilter;
        else if(renderingConditionRelatedObjects) mapData = {original: mapDataContext?.entity?.spatial, related: mapDataContext?.entity?.related};
        else if(renderingConditionSites) mapData = mapDataArchaeoSites?.archaeologicalSites;
        else if(renderingConditionSitesByRegion) mapData = mapDataSitesByRegion?.sitesByRegion;

        return mapData;
    }
267

268
269
270
    const getMapDataType = () => {
        let type = null;
        let handler = false;
271

272
273
274
275
276
277
278
279
280
281
        if(renderingConditionObjects) handler = true;
        else if(renderingConditionRelatedObjects) {
            type = "related";
            handler = true;
        }

        return {type: type, handler: handler};
    }


282
283
    //const setWidth = () => window.innerWidth
    //const setOneTwelfthWidth = () => setWidth() / 12
284

285
    //window.addEventListener('resize', setOneTwelfthWidth)
286

287

288
    const renderAreaA = () => {
289
290
291
292
293
294
295
296
        const area = "areaA";

        return(
            <DashboardTile
                reducer={[input, dispatch]}
                area={area}
                content={
                    input[area]===0 && <ResultsTable
297
                        handleRelatedObjects={handleRelatedObjects}
298
299
300
301
302
303
304
305
306
307
                        mapDataObjects={mapDataObjects}
                        mapDataContext={mapDataContext}
                        mapDataArchaeoSites={mapDataArchaeoSites}
                        mapDataSitesByRegion={mapDataSitesByRegion}
                        reducer={[input, dispatch]}
                        renderingConditionObjects={renderingConditionObjects}
                        renderingConditionRelatedObjects={renderingConditionRelatedObjects}
                        renderingConditionSites={renderingConditionSites}
                        renderingConditionSitesByRegion={renderingConditionSitesByRegion}
                        openPopup={openPopup}
308
309
                    />
                    || input[area]===1 && <ImageContents
310
311
312
                        contents={dataObjects
                        && [dataObjects?.entitiesMultiFilter?.map(entity => entity?.categoryOfDepicted),
                            dataObjects?.entitiesMultiFilter?.map(entity => entity?.materialOfDepicted)]}
313
314
315
316
                    />
                    || input[area]===2 && <DataSources/>
                }
                showNext={
317
                    <ShowNext
318
                        area={area}
319
320
                        labels={["Results table", "Image contents", "Data sources"]}
                        reducer={[input, dispatch]}
321
                    />
322
323
                }
            />
324
325
326
327
        )
    }

    const renderAreaB = () => {
328
        const area = "areaB";
329

330
331
332
333
334
        return(
            <DashboardTile
                reducer={[input, dispatch]}
                area={area}
                content={
amarcic's avatar
amarcic committed
335
                    input[area]===0 && <Timeline
336
                        reducer={[input, dispatch]}
337
                        timelineObjectsData={dataObjects?.entitiesMultiFilter.flatMap(timelineAdapter)}
338
                    />
amarcic's avatar
amarcic committed
339
                    || input[area]===1 && <Histogram
340
                        timelineData={dataObjects?.entitiesMultiFilter.map(timelineMapper)}
amarcic's avatar
amarcic committed
341
                    />
342
343
                }
                showNext={
344
                    <ShowNext
345
                        area={area}
346
347
                        labels={["Timeline", "Histogram"]}
                        reducer={[input, dispatch]}
348
                    />
349
350
                }
            />
351
352
        )
    }
353

354
    const renderAreaC = () => {
355
356
357
358
359
360
361
362
363
        const area = "areaC";

        return(
            <DashboardTile
                reducer={[input, dispatch]}
                area={area}
                content={
                    <OurMap
                        handleRelatedObjects={handleRelatedObjects}
Elisabeth Reuhl's avatar
Elisabeth Reuhl committed
364
365
                        data={getMapData()}
                        dataType={getMapDataType()}
366
367
368
369
                        reducer={[input, dispatch]}
                    />
                }
            />
370
371
        )
    }
372

373
374
375
376
377
    return (
        /* Layout schema:
            F = filters, M = map, A = area A, B = area B; two rows = 100% height, four columns = 100 % width

            Size md/lg:
378
            default:      |    big M:        |    big A:        |    big B:        |    with expanded filters: (?)
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
            ------------------------------------------------------------------------------------------------------
            F             |    F             |    F             |    F             |    F  F  F  F
            M  M  A  A    |    M  M  M  M    |    A  A  A  A    |    B  B  B  B    |    ...
            M  M  B  B    |    M  M  M  M    |    A  A  A  A    |    B  B  B  B    |
                          |    A  A  B  B    |    M  M  B  B    |    M  M  A  A    |
                          |                  |    M  M          |    M  M          |
            Size xs:
            default: (?)  |   with expanded filters: (?)
            --------------------------------------------
            F  .  .  .    |    F  F  F  F
            M  M  M  M    |    F  F  F  F
            M  M  M  M    |    M  M  M  M
            A  A  A  A    |    M  M  M  M
            A  A  A  A    |    A  A  A  A
            B  B  B  B    |    A  A  A  A
            B  B  B  B    |    B  B  B  B
                          |    B  B  B  B
        */
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
        <>
            <PageHeader
                chronOntologyTerms={chronOntologyTerms}
                reducer={[input, dispatch]}
                regions={regions}
            />
            <Container maxWidth={"xl"}>
                <Layout
                    bigTile={
                        (input.bigTileArea === "areaA" && renderAreaA())
                        || (input.bigTileArea === "areaB" && renderAreaB())
                        || (input.bigTileArea === "areaC" && renderAreaC())
                    }
                    leftOrTopTile={
                        input.bigTileArea !== "areaC" && renderAreaC()
                    }
                    topRightOrMiddleTile={
                        input.bigTileArea !== "areaA"
                            ? renderAreaA()
                            : renderAreaB()
                    }
                    bottomRightOrBottomTile={
                        input.bigTileArea !== "areaA" && input.bigTileArea !== "areaB" && renderAreaB()
                    }
                    loadingIndicator={
                        (loadingContext || loadingObjects || loadingArchaeoSites || loadingSitesByRegion)
                        && <LinearProgress/>
                    }
                    rightTileIsMovedToBottomInstead={input.bigTileArea === "areaC" ? "true" : false}
                />
            </Container>
        </>
429
    )
430
};