localisation.tsx 7.65 KB
Newer Older
Fabien Amarger's avatar
Fabien Amarger committed
1
2
3
import * as React from 'react';
import * as ReactDOM from 'react-dom';

4
5
import 'bootstrap/dist/css/bootstrap.min.css';
import Badge from 'react-bootstrap/Badge';
6
7
8
import { Map, Marker, Popup, TileLayer } from 'react-leaflet';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';
9

Fabien Amarger's avatar
Fabien Amarger committed
10
11
12
import { application, implementation, definition } from '@logilab/libview';
import * as $rdf from 'rdflib';

13
14
import './localisation.css';

15
16
const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#');
const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#');
17
const OWL = $rdf.Namespace('http://www.w3.org/2002/07/owl#');
18
const CULT = $rdf.Namespace('http://graphe-culture.fr/');
19

Fabien Amarger's avatar
Fabien Amarger committed
20
21
22
23
24
25
26
27
28
const DESCRIPTOR_LOCALISATION: definition.ViewDescriptor = {
    identifier: '::Culture::Localisation',
    name: 'Culture: Localisation de bien culturel',
    description:
        'Affiche une carte contenant la localisation des biens culturels dans les données ouvertes du ministère de la culture',
    entrypoint: 'VIEW_ENTRYPOINT',
    resourceCss: [],
    resourceJs: [],
    resourceMain: {
29
        uri: `${process.env.DOMAIN ?? 'http://localhost:8080'}/view_localisation.js`,
30
    },
Fabien Amarger's avatar
Fabien Amarger committed
31
32
};

33
34
35
interface BienCulturelProps {
    typeUris: string[];
    label: string;
36
    locationUri: string;
37
    dataFetcher?: implementation.DataFetcher;
38
    generateUrl?: (uri: string) => string;
39
40
}

41
export interface BienCulturelInfoProps {
42
    uri?: string;
43
    typeUris: string[];
44
    label: string;
45
    dataFetcher?: implementation.DataFetcher;
46
    generateUrl?: (uri: string) => string;
47
48
}

49
export interface Location {
50
51
52
53
    lat: number;
    lng: number;
}

54
55
export const BienCulturelInfo: React.FC<BienCulturelInfoProps> = (props) => {
    const {
56
        uri,
57
58
59
60
61
        typeUris,
        label,
        dataFetcher = { fetchDataFromURI: () => Promise.reject('Not implemented') },
        generateUrl = (uri) => uri,
    } = props;
62
63
64
    const [urisLabel, setUrisLabel] = React.useState<{ uri: string; label: string | null }[]>(
        typeUris.map((typeUri) => ({ uri: typeUri, label: null }))
    );
65

66
    React.useEffect(() => {
67
68
69
        Promise.all(
            typeUris.map((typeUri) =>
                dataFetcher.fetchDataFromURI(typeUri, { label: 'http://www.w3.org/2000/01/rdf-schema#label' })
70
            )
71
72
73
74
75
76
        )
            .then((fetchedLabels: { label?: string | string[] }[]) => {
                const newUrisLabel: { uri: string; label: string }[] = [];
                for (const [i, { label }] of fetchedLabels.entries()) {
                    if (label === undefined) {
                        continue;
77
                    }
78
79
80
81
82
83
84
85
86
87
                    if (i >= 0 && i < typeUris.length) {
                        newUrisLabel.push({
                            uri: typeUris[i],
                            label: label instanceof Array ? label[0] : label,
                        });
                    }
                }
                setUrisLabel(newUrisLabel);
            })
            .catch((e) => console.error(e));
88
    }, [dataFetcher, typeUris]);
89
90
91
    const typesLabel = urisLabel.map((uriLabel) => uriLabel.label ?? uriLabel.uri);
    return (
        <>
92
            <h1>{uri !== undefined ? <a href={generateUrl(uri)}>{label}</a> : label}</h1>
93
            <h5>
94
                {urisLabel.map(({ label, uri }) => (
95
                    <a key={uri} href={generateUrl(uri)}>
96
97
98
99
                        <Badge style={{ margin: '5px', color: 'white' }} key={uri} variant="primary">
                            {label ?? uri}
                        </Badge>
                    </a>
100
101
102
103
104
105
                ))}
            </h5>
        </>
    );
};

106
const BienCulturel: React.FC<BienCulturelProps> = ({ typeUris, label, locationUri, dataFetcher, generateUrl }) => {
107
    const [location, setLocation] = React.useState<Location | null>(null);
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
    React.useEffect(() => {
        if (dataFetcher !== undefined) {
            dataFetcher
                .fetchDataFromURI(locationUri, {
                    lat: 'http://www.w3.org/2003/01/geo/wgs84_pos#latitude',
                    lng: 'http://www.w3.org/2003/01/geo/wgs84_pos#longitude',
                })
                .then(({ lat, lng }) =>
                    setLocation({
                        lat: Number(lat),
                        lng: Number(lng),
                    })
                );
        }
    }, [locationUri, dataFetcher]);

    let locationComponent = <></>;
    if (location !== null) {
        L.Icon.Default.imagePath = `${process.env.DOMAIN}/images/`;
        const position = { lat: location.lat, lng: location.lng };
        locationComponent = (
            <Map zoom={13} center={position} style={{ height: '600px' }}>
                <TileLayer
                    attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
                    url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"
                />
                <Marker position={position}>
                    <Popup>
137
138
139
140
141
142
                        <BienCulturelInfo
                            typeUris={typeUris}
                            label={label}
                            dataFetcher={dataFetcher}
                            generateUrl={generateUrl}
                        />
143
144
145
146
147
148
                    </Popup>
                </Marker>
            </Map>
        );
    }

149
150
    return (
        <>
151
            <BienCulturelInfo typeUris={typeUris} label={label} dataFetcher={dataFetcher} generateUrl={generateUrl} />
152
            <div>{locationComponent}</div>
153
154
155
        </>
    );
};
156

Fabien Amarger's avatar
Fabien Amarger committed
157
158
class LocalisationRendering implements implementation.ViewImplementation {
    descriptor: definition.ViewDescriptor = DESCRIPTOR_LOCALISATION;
159
160
161
162
163
    priorityFor(context: $rdf.Formula, target: application.Resource): number {
        let score = 0;

        const targetSym = $rdf.sym(target.uri);

164
165
166
167
168
169
170
171
        if (
            context.any(null, RDF('type'), targetSym) !== null ||
            context.any(targetSym, RDF('type'), OWL('Class')) !== null ||
            context.any(targetSym, RDF('type'), RDFS('Class')) !== null
        ) {
            return -1; // this view is not suitable if it is a class
        }

172
173
174
175
176
177
178
179
180
181
182
183
        // has at least one type
        const firstType = context.any(targetSym, RDF('type'));
        if (firstType !== null) {
            score += 10;
        }

        // has at least one label
        const firstLabel = context.any(targetSym, RDFS('label'));
        if (firstLabel !== null) {
            score += 10;
        }
        return score;
Fabien Amarger's avatar
Fabien Amarger committed
184
185
186
187
188
189
190
191
    }

    render(
        renderer: application.ViewRenderer,
        context: application.RenderingContext,
        target: application.Resource
    ): implementation.ViewRendering {
        return {
192
            dom: this.simpleRender('TODO', context.store, target.uri),
Fabien Amarger's avatar
Fabien Amarger committed
193
194
195
196
            suggestedResources: [],
        };
    }

197
198
199
200
    simpleRender(
        nodeId: string,
        context: $rdf.Formula,
        target: string,
201
202
        dataFetcher?: implementation.DataFetcher,
        generateUrl?: (uri: string) => string
203
    ): Element {
Fabien Amarger's avatar
Fabien Amarger committed
204
205
        const targetSym = $rdf.sym(target);

206
        const bienCulturel: BienCulturelProps = {
207
            typeUris: context.each(targetSym, RDF('type')).map((node) => node.value) as string[],
208
            label: context.anyJS(targetSym, RDFS('label')),
209
            locationUri: context.anyJS(targetSym, CULT('aPourLocalisation')).value,
210
            dataFetcher,
211
            generateUrl,
Fabien Amarger's avatar
Fabien Amarger committed
212
213
214
        };

        const result = document.createElement('div');
215
        ReactDOM.render(<BienCulturel {...bienCulturel} />, document.getElementById(nodeId));
Fabien Amarger's avatar
Fabien Amarger committed
216
217
218
219
220
221
222
223
224
        return result;
    }
}

/**
 * Export the view
 */
export const VIEW_LOCALISATION_ENTRYPOINT: LocalisationRendering = new LocalisationRendering();
export const VIEW_ENTRYPOINT = VIEW_LOCALISATION_ENTRYPOINT;