Skip to content

Commit cf50108

Browse files
committed
Performance Improvements PhotoSuggestions
Signed-off-by: Arne Hamann <git@arne.email>
1 parent 7f11a12 commit cf50108

4 files changed

Lines changed: 237 additions & 44 deletions

File tree

lib/Service/GeophotoService.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,6 @@ public function getNonLocalizedFromDB (string $userId, $folder=null, bool $respe
240240
}
241241
$this->nonLocalizedPhotosCache->set($key, $filesById, 60 * 60 * 24);
242242
}
243-
shuffle($filesById);
244243
return $filesById;
245244
}
246245

src/components/Map.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@
100100
:map="map"
101101
:photo-suggestions="photoSuggestions"
102102
:photo-suggestions-selected-indices="photoSuggestionsSelectedIndices"
103+
:date-filter-enabled="sliderEnabled"
104+
:date-filter-start="sliderStartTimestamp"
105+
:date-filter-end="sliderEndTimestamp"
103106
:draggable="photosDraggable"
104107
@photo-suggestion-moved="onPhotoSuggestionMoved"
105108
@photo-suggestion-selected="$emit('photo-suggestion-selected', $event)" />

src/components/map/PhotoSuggestionsLayer.vue

Lines changed: 234 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
<template>
2-
<Vue2LeafletMarkerCluster :options="clusterOptions"
2+
<Vue2LeafletMarkerCluster
3+
ref="markerCluster"
4+
:options="clusterOptions"
35
@clusterclick="onClusterClick"
46
@clustercontextmenu="onClusterRightClick"
57
@spiderfied="onSpiderfied">
6-
<LMarker v-for="(p, i) in photoSuggestions"
7-
:key="i"
8+
<!-- <LMarker v-for="(p, i) in photoSuggestions"
89
v-if="p"
10+
:key="i"
911
:options="{ data: p }"
1012
:icon="getPhotoMarkerIcon(p, i)"
1113
:draggable="draggable"
@@ -35,6 +37,34 @@
3537
{{ t('maps', 'Display picture') }}
3638
</NcActionButton>
3739
</LPopup>
40+
</LMarker>-->
41+
<LMarker
42+
:lat-lng="[0, 0]"
43+
:visible="false">
44+
<LTooltip
45+
ref="markerTooltip"
46+
:class="{
47+
'tooltip-photo-suggestion-wrapper': true,
48+
'photo-suggestion-marker-selected': photoSuggestionsSelectedIndices.includes(currentSuggestion?.i)
49+
}"
50+
:options="{ ...tooltipOptions, opacity: draggable ? 0 : 1 }">
51+
<img class="photo-suggestion-tooltip"
52+
:src="getPreviewUrl(currentSuggestion)">
53+
<p class="tooltip-photo-suggestion-date">
54+
{{ getPhotoFormattedDate(currentSuggestion) }}
55+
</p>
56+
<p class="tooltip-photo-suggestion-name">
57+
{{ currentSuggestion ? basename(currentSuggestion.path) : ''}}
58+
</p>
59+
</LTooltip>
60+
<LPopup
61+
ref="markerPopup"
62+
class="popup-photo-wrapper"
63+
:options="popupOptions">
64+
<NcActionButton icon="icon-toggle" @click="viewPhoto(currentSuggestion)">
65+
{{ t('maps', 'Display picture') }}
66+
</NcActionButton>
67+
</LPopup>
3868
</LMarker>
3969
<LMarker
4070
:lat-lng="[0, 0]"
@@ -68,6 +98,7 @@ import { LMarker, LTooltip, LPopup } from 'vue2-leaflet'
6898
import Vue2LeafletMarkerCluster from 'vue2-leaflet-markercluster'
6999
70100
import optionsController from '../../optionsController'
101+
import {binSearch} from "../../utils/common";
71102
72103
const PHOTO_MARKER_VIEW_SIZE = 40
73104
@@ -94,6 +125,18 @@ export default {
94125
type: Array,
95126
required: true,
96127
},
128+
dateFilterEnabled: {
129+
type: Boolean,
130+
required: true,
131+
},
132+
dateFilterStart: {
133+
type: Number,
134+
required: true,
135+
},
136+
dateFilterEnd: {
137+
type: Number,
138+
required: true,
139+
},
97140
draggable: {
98141
type: Boolean,
99142
required: true,
@@ -106,12 +149,17 @@ export default {
106149
clusterOptions: {
107150
iconCreateFunction: this.getClusterMarkerIcon,
108151
spiderfyOnMaxZoom: false,
152+
singleMarkerMode: true,
109153
showCoverageOnHover: false,
110154
zoomToBoundsOnClick: false,
111155
maxClusterRadius: PHOTO_MARKER_VIEW_SIZE + 10,
112156
icon: {
113157
iconSize: [PHOTO_MARKER_VIEW_SIZE, PHOTO_MARKER_VIEW_SIZE],
114158
},
159+
chunkedLoading: true,
160+
chunkDelay: 50,
161+
chunkInterval: 250,
162+
chunkProgress: this.updateClusterLoadingProgress,
115163
},
116164
tooltipOptions: {
117165
className: 'leaflet-marker-photo-suggestion-tooltip',
@@ -130,15 +178,101 @@ export default {
130178
},
131179
contextCluster: null,
132180
spiderfied: false,
181+
currentSuggestion: null,
182+
suggestionMarkers: [],
183+
clustersLoading: false,
133184
}
134185
},
135186
136187
computed: {
188+
suggestionsLastNullIndex() {
189+
return this.dateFilterEnabled ? binSearch(this.photoSuggestions, (p) => !p.dateTaken) : -1
190+
},
191+
suggestionsFirstShownIndex() {
192+
return this.dateFilterEnabled ? binSearch(this.photoSuggestions, (p) => (p.dateTaken || 0) < this.dateFilterStart) + 1 : 0
193+
},
194+
suggestionsLastShownIndex() {
195+
return this.dateFilterEnabled ? binSearch(this.photoSuggestions, (p) => (p.dateTaken || 0) < this.dateFilterEnd) : this.photoSuggestions.length - 1
196+
},
197+
},
198+
199+
watch: {
200+
photoSuggestions() {
201+
this.updateSuggestionMarkers()
202+
},
203+
draggable() {
204+
this.updateSuggestionMarkersDraggable()
205+
},
206+
dateFilterEnabled(newValue) {
207+
if (newValue) {
208+
this.$refs.markerCluster.mapObject.removeLayers(
209+
this.suggestionMarkers.slice(
210+
this.suggestionsLastNullIndex + 1,
211+
this.suggestionsFirstShownIndex
212+
)
213+
)
214+
this.$refs.markerCluster.mapObject.removeLayers(
215+
this.suggestionMarkers.slice(
216+
this.suggestionsLastShownIndex + 1,
217+
)
218+
)
219+
} else {
220+
this.$refs.markerCluster.mapObject.addLayers(
221+
this.suggestionMarkers.slice(
222+
this.suggestionsLastNullIndex + 1,
223+
this.suggestionsFirstShownIndex
224+
)
225+
)
226+
this.$refs.markerCluster.mapObject.addLayers(
227+
this.suggestionMarkers.slice(
228+
this.suggestionsLastShownIndex + 1,
229+
)
230+
)
231+
}
232+
},
233+
suggestionsFirstShownIndex(newIndex, oldIndex) {
234+
if (newIndex < oldIndex) {
235+
this.$refs.markerCluster.mapObject.addLayers(
236+
this.suggestionMarkers.slice(
237+
newIndex,
238+
oldIndex
239+
)
240+
)
241+
} else if (newIndex > oldIndex) {
242+
this.$refs.markerCluster.mapObject.removeLayers(
243+
this.suggestionMarkers.slice(
244+
oldIndex,
245+
newIndex
246+
)
247+
)
248+
}
249+
},
250+
suggestionsLastShownIndex(newIndex, oldIndex) {
251+
if (newIndex < oldIndex) {
252+
this.$refs.markerCluster.mapObject.removeLayers(
253+
this.suggestionMarkers.slice(
254+
newIndex + 1,
255+
oldIndex + 1
256+
)
257+
)
258+
} else if (newIndex > oldIndex) {
259+
this.$refs.markerCluster.mapObject.addLayers(
260+
this.suggestionMarkers.slice(
261+
oldIndex + 1,
262+
newIndex + 1
263+
)
264+
)
265+
}
266+
},
137267
},
138268
139269
beforeMount() {
140270
},
141271
272+
mounted() {
273+
this.updateSuggestionMarkers()
274+
},
275+
142276
methods: {
143277
basename(path) {
144278
return basename(path)
@@ -184,7 +318,7 @@ export default {
184318
},
185319
displayCluster(cluster) {
186320
const photoList = cluster.getAllChildMarkers().map((m) => {
187-
return m.options.data
321+
return m.data
188322
})
189323
photoList.sort((a, b) => {
190324
return a.dateTaken - b.dateTaken
@@ -194,20 +328,23 @@ export default {
194328
this.map.closePopup()
195329
},
196330
getClusterMarkerIcon(cluster) {
197-
const photo = cluster.getAllChildMarkers()[0].options.data
331+
const count = cluster.getChildCount()
332+
const photo = cluster.getAllChildMarkers()[0].data
333+
if (count === 1) {
334+
return this.getPhotoMarkerIcon(photo)
335+
}
198336
const iconUrl = this.getPreviewUrl(photo)
199-
const label = cluster.getChildCount()
200337
return new L.DivIcon(L.extend({
201338
className: 'leaflet-marker-photo-suggestion cluster-suggestion-marker',
202-
html: '<div class="thumbnail" style="background-image: url(' + iconUrl + ');"></div>​<span class="label">' + label + '</span>',
339+
html: '<div class="thumbnail" style="background-image: url(' + iconUrl + ');"></div>​<span class="label">' + count + '</span>',
203340
}, cluster, {
204341
iconSize: [PHOTO_MARKER_VIEW_SIZE, PHOTO_MARKER_VIEW_SIZE],
205342
iconAnchor: [PHOTO_MARKER_VIEW_SIZE / 2, PHOTO_MARKER_VIEW_SIZE],
206343
}))
207344
},
208-
getPhotoMarkerIcon(photo, i) {
345+
getPhotoMarkerIcon(photo) {
209346
const iconUrl = this.getPreviewUrl(photo)
210-
const selectedClass = this.photoSuggestionsSelectedIndices.includes(i)
347+
const selectedClass = this.photoSuggestionsSelectedIndices.includes(photo.i)
211348
? '-selected'
212349
: ''
213350
return L.divIcon(L.extend({
@@ -219,14 +356,15 @@ export default {
219356
}))
220357
},
221358
getPreviewUrl(photo) {
222-
return photo.hasPreview
359+
return photo && photo.hasPreview
223360
? generateUrl('core') + '/preview?fileId=' + photo.fileId + '&x=341&y=256&a=1'
224361
: generateUrl('/apps/theming/img/core/filetypes') + '/image.svg?v=2'
225362
},
226363
getPhotoFormattedDate(photo) {
227-
return moment.unix(photo.dateTaken).format('LLL')
364+
return photo ? moment.unix(photo.dateTaken).format('LLL') : ''
228365
},
229-
onPhotoClick(e, index) {
366+
onPhotoClick(e) {
367+
const index = e.target.i
230368
// we want popup to open on right click only
231369
this.$nextTick(() => {
232370
e.target.closePopup()
@@ -239,35 +377,94 @@ export default {
239377
this.map.closePopup()
240378
}
241379
},
242-
onPhotoRightClick(e, photo) {
380+
onPhotoRightClick(e) {
381+
const photo = e.target.data
382+
this.currentSuggestion = photo
383+
const popup = this.$refs.markerPopup.mapObject
384+
popup.setLatLng([photo.lat, photo.lng])
385+
this.$nextTick(() => {
386+
popup.openOn(this.map)
387+
})
388+
},
389+
onPhotoMouseOver(e) {
390+
const photo = e.target.data
391+
this.currentSuggestion = photo
392+
const tooltip = this.$refs.markerTooltip.mapObject
393+
tooltip.setLatLng([photo.lat, photo.lng])
243394
this.$nextTick(() => {
244-
e.target.openPopup()
395+
tooltip.openOn(this.map)
245396
})
246397
},
247-
resetClusterPhotoCoords() {
248-
const clusterSize = this.contextCluster.getChildCount()
249-
OC.dialogs.confirmDestructive(
250-
'',
251-
t('maps', 'Are you sure you want to remove geo data of {nb} photos?', { nb: clusterSize }),
252-
{
253-
type: OC.dialogs.YES_NO_BUTTONS,
254-
confirm: t('maps', 'Yes'),
255-
confirmClasses: '',
256-
cancel: t('maps', 'Cancel'),
257-
},
258-
(result) => {
259-
if (result) {
260-
const photos = this.contextCluster.getAllChildMarkers().map((m) => {
261-
return m.options.data
262-
})
263-
this.resetPhotosCoords(photos)
264-
}
265-
},
266-
true
267-
)
398+
onPhotoMouseOut(e) {
399+
const tooltip = this.$refs.markerTooltip.mapObject
400+
tooltip.close()
401+
},
402+
onPhotoMoved(e) {
403+
this.$emit('photo-suggestion-moved', e.target.i, e.target.getLatLng())
268404
},
269-
onPhotoMoved(e, index) {
270-
this.$emit('photo-suggestion-moved', index, e.target.getLatLng())
405+
updateClusterLoadingProgress(processed, total, elapsed, layersArray) {
406+
if (elapsed > 100 && !this.clustersLoading) {
407+
this.clustersLoading = true
408+
}
409+
this.$emit('cluster-loading', processed, total)
410+
411+
if (processed === total) {
412+
this.clustersLoading = false
413+
// all markers processed - hide the progress bar:
414+
this.$emit('cluster-loaded')
415+
}
416+
},
417+
418+
async updateSuggestionMarkers() {
419+
this.$refs.markerCluster.mapObject.removeLayers(this.suggestionMarkers)
420+
this.suggestionMarkers = this.photoSuggestions.map((p, i) => {
421+
const m = new L.Marker([p.lat, p.lng],
422+
{
423+
draggable: this.draggable,
424+
},
425+
)
426+
m.on(
427+
'click', this.onPhotoClick
428+
)
429+
m.on(
430+
'contextmenu', this.onPhotoRightClick
431+
)
432+
m.on(
433+
'mouseover', this.onPhotoMouseOver
434+
)
435+
m.on(
436+
'mouseout', this.onPhotoMouseOut
437+
)
438+
m.on(
439+
'moveend', this.onPhotoMoved
440+
)
441+
m.data = p
442+
m.i = i
443+
return m
444+
})
445+
this.$refs.markerCluster.mapObject.addLayers(this.suggestionMarkers)
446+
if (this.dateFilterEnabled) {
447+
this.$refs.markerCluster.mapObject.removeLayers(
448+
this.suggestionMarkers.slice(
449+
this.suggestionsLastNullIndex + 1,
450+
this.suggestionsFirstShownIndex
451+
)
452+
)
453+
this.$refs.markerCluster.mapObject.removeLayers(
454+
this.suggestionMarkers.slice(
455+
this.suggestionsLastShownIndex + 1,
456+
)
457+
)
458+
}
459+
},
460+
async updateSuggestionMarkersDraggable() {
461+
this.suggestionMarkers.forEach((m) => {
462+
if (m.dragging) {
463+
this.draggable ? m.dragging.enable() : m.dragging.disable()
464+
}
465+
m.options.draggable = this.draggable
466+
m.update()
467+
})
271468
},
272469
},
273470
}

0 commit comments

Comments
 (0)