localisation.tsx 6.34 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
    graph?: string;
44
    typeUris: string[];
45
    label: string;
46
    dataFetcher?: implementation.DataFetcher;
47
    generateUrl?: (uri: string) => string;
48
49
}

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

55
56
export const BienCulturelInfo: React.FC<BienCulturelInfoProps> = (props) => {
    const {
57
        uri,
58
        graph,
59
60
61
62
63
        typeUris,
        label,
        dataFetcher = { fetchDataFromURI: () => Promise.reject('Not implemented') },
        generateUrl = (uri) => uri,
    } = props;
64
65
66

    return (
        <>
67
            <h1>{uri !== undefined ? <a href={generateUrl(uri)}>{label}</a> : label}</h1>
68
            <h5>
69
                {typeUris.map((uri) => (
70
                    <URIBadge key={uri} uri={uri} dataFetcher={dataFetcher} generateUrl={generateUrl} />
71
72
                ))}
            </h5>
73
            {graph && <span>Source : {graph} </span>}
74
75
76
77
        </>
    );
};

78
const BienCulturel: React.FC<BienCulturelProps> = ({ typeUris, label, locationUri, dataFetcher, generateUrl }) => {
79
    const [location, setLocation] = React.useState<Location | null>(null);
80

81
82
83
84
    React.useEffect(() => {
        if (dataFetcher !== undefined) {
            dataFetcher
                .fetchDataFromURI(locationUri, {
85
86
                    lat: 'http://www.w3.org/2003/01/geo/wgs84_pos#lat',
                    lng: 'http://www.w3.org/2003/01/geo/wgs84_pos#long',
87
88
89
90
91
92
93
94
95
96
97
98
                })
                .then(({ lat, lng }) =>
                    setLocation({
                        lat: Number(lat),
                        lng: Number(lng),
                    })
                );
        }
    }, [locationUri, dataFetcher]);

    let locationComponent = <></>;
    if (location !== null) {
99
        L.Icon.Default.imagePath = `https://unpkg.com/leaflet@1.7.1/dist/images/`;
100
101
102
103
104
105
106
107
108
        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>
109
110
111
112
113
114
                        <BienCulturelInfo
                            typeUris={typeUris}
                            label={label}
                            dataFetcher={dataFetcher}
                            generateUrl={generateUrl}
                        />
115
116
117
118
119
120
                    </Popup>
                </Marker>
            </Map>
        );
    }

121
122
    return (
        <>
123
            <BienCulturelInfo typeUris={typeUris} label={label} dataFetcher={dataFetcher} generateUrl={generateUrl} />
124
            <div>{locationComponent}</div>
125
126
127
        </>
    );
};
128

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

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

136
137
138
139
140
141
142
143
        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
        }

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

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

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

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

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

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