localisation.tsx 6.24 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
6
import { Map, Marker, Popup, TileLayer } from 'react-leaflet';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';
7

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

11
12
import './localisation.css';

13
14
import { URIBadge } from '../utils/URIBadge';

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

    return (
        <>
65
            <h1>{uri !== undefined ? <a href={generateUrl(uri)}>{label}</a> : label}</h1>
66
            <h5>
67
68
                {typeUris.map((uri) => (
                    <URIBadge uri={uri} dataFetcher={dataFetcher} generateUrl={generateUrl} />
69
70
71
72
73
74
                ))}
            </h5>
        </>
    );
};

75
const BienCulturel: React.FC<BienCulturelProps> = ({ typeUris, label, locationUri, dataFetcher, generateUrl }) => {
76
    const [location, setLocation] = React.useState<Location | null>(null);
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
    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>
106
107
108
109
110
111
                        <BienCulturelInfo
                            typeUris={typeUris}
                            label={label}
                            dataFetcher={dataFetcher}
                            generateUrl={generateUrl}
                        />
112
113
114
115
116
117
                    </Popup>
                </Marker>
            </Map>
        );
    }

118
119
    return (
        <>
120
            <BienCulturelInfo typeUris={typeUris} label={label} dataFetcher={dataFetcher} generateUrl={generateUrl} />
121
            <div>{locationComponent}</div>
122
123
124
        </>
    );
};
125

Fabien Amarger's avatar
Fabien Amarger committed
126
127
class LocalisationRendering implements implementation.ViewImplementation {
    descriptor: definition.ViewDescriptor = DESCRIPTOR_LOCALISATION;
128
129
130
131
132
    priorityFor(context: $rdf.Formula, target: application.Resource): number {
        let score = 0;

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

133
134
135
136
137
138
139
140
        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
        }

141
142
143
144
145
146
147
148
149
150
151
152
        // 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
153
154
155
156
157
158
159
160
    }

    render(
        renderer: application.ViewRenderer,
        context: application.RenderingContext,
        target: application.Resource
    ): implementation.ViewRendering {
        return {
161
            dom: this.simpleRender('TODO', context.store, target.uri),
Fabien Amarger's avatar
Fabien Amarger committed
162
163
164
165
            suggestedResources: [],
        };
    }

166
167
168
169
    simpleRender(
        nodeId: string,
        context: $rdf.Formula,
        target: string,
170
171
        dataFetcher?: implementation.DataFetcher,
        generateUrl?: (uri: string) => string
172
    ): Element {
Fabien Amarger's avatar
Fabien Amarger committed
173
174
        const targetSym = $rdf.sym(target);

175
        const bienCulturel: BienCulturelProps = {
176
            typeUris: context.each(targetSym, RDF('type')).map((node) => node.value) as string[],
177
            label: context.anyJS(targetSym, RDFS('label')),
178
            locationUri: context.anyJS(targetSym, CULT('aPourLocalisation')).value,
179
            dataFetcher,
180
            generateUrl,
Fabien Amarger's avatar
Fabien Amarger committed
181
182
183
        };

        const result = document.createElement('div');
184
        ReactDOM.render(<BienCulturel {...bienCulturel} />, document.getElementById(nodeId));
Fabien Amarger's avatar
Fabien Amarger committed
185
186
187
188
189
190
191
192
193
        return result;
    }
}

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