Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/components/Map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,13 @@
</template>
</template>
</div>
<Slider v-show="sliderEnabled"
<Slider v-show="sliderEnabled && timeFilterMode !== 'calendar'"
:start="sliderStartTimestamp"
:end="sliderEndTimestamp"
:range-min="minDataTimestamp"
:range-max="maxDataTimestamp"
@range-change="$emit('slider-range-changed', $event)" />
<DateRangeFilter v-if="sliderEnabled && timeFilterMode === 'calendar'"
:start="sliderStartTimestamp"
:end="sliderEndTimestamp"
:range-min="minDataTimestamp"
Expand Down Expand Up @@ -170,6 +176,7 @@ import '@raruto/leaflet-elevation/dist/leaflet-elevation'
import '@raruto/leaflet-elevation/dist/leaflet-elevation.css'

import Slider from '../components/map/Slider.vue'
import DateRangeFilter from '../components/map/DateRangeFilter.vue'
import SearchControl from '../components/map/SearchControl.vue'
import HistoryControl from '../components/map/HistoryControl.vue'
import RoutingControl from '../components/map/routing/RoutingControl.vue'
Expand All @@ -187,7 +194,7 @@ import PhotoSuggestionsLayer from './map/PhotoSuggestionsLayer.vue'
export default {
name: 'Map',
components: {
Slider, HistoryControl, SearchControl, RoutingControl, FavoritesLayer,
Slider, DateRangeFilter, HistoryControl, SearchControl, RoutingControl, FavoritesLayer,
PhotosLayer, PhotoSuggestionsLayer, TracksLayer, DevicesLayer, ContactsLayer,
PlaceContactPopup, ClickSearchPopup, PoiMarker,
},
Expand Down Expand Up @@ -216,6 +223,7 @@ export default {
devices: { type: Array, required: true },
devicesEnabled: { type: Boolean, required: true },
sliderEnabled: { type: Boolean, required: true },
timeFilterMode: { type: String, default: 'slider' },
sliderStartTimestamp: { type: Number, required: true },
sliderEndTimestamp: { type: Number, required: true },
minDataTimestamp: { type: Number, required: true },
Expand Down
34 changes: 34 additions & 0 deletions src/components/MapsNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@
{{ window.t('maps', 'Display time filter slider') }}
</NcCheckboxRadioSwitch>

<div v-if="optionValues.displaySlider === 'true'"
class="maps-settings-time-filter-mode">
<NcCheckboxRadioSwitch
type="radio"
name="maps-time-filter-mode"
value="slider"
:model-value="timeFilterMode"
@update:model-value="onTimeFilterModeChange">
{{ window.t('maps', 'Slider') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
type="radio"
name="maps-time-filter-mode"
value="calendar"
:model-value="timeFilterMode"
@update:model-value="onTimeFilterModeChange">
{{ window.t('maps', 'Calendar') }}
</NcCheckboxRadioSwitch>
</div>

<p class="maps-settings-text">
{{ trueSizeText }}
</p>
Expand Down Expand Up @@ -86,6 +106,7 @@ export default {
'toggle-trackme',
'register-geo-link',
'toggle-slider',
'change-time-filter-mode',
],

data() {
Expand All @@ -97,6 +118,9 @@ export default {
},

computed: {
timeFilterMode() {
return this.optionValues.timeFilterMode === 'calendar' ? 'calendar' : 'slider'
},
trueSizeText() {
return window.t('maps', 'Keep in mind that map projections always distort sizes of countries. The standard Mercator projection is particularly biased. Read more at:')
}
Expand All @@ -113,11 +137,21 @@ export default {
optionsController.saveOptionValues({ displaySlider: checked ? 'true' : 'false' })
this.$emit('toggle-slider', checked)
},
onTimeFilterModeChange(mode) {
this.optionValues.timeFilterMode = mode
optionsController.saveOptionValues({ timeFilterMode: mode })
this.$emit('change-time-filter-mode', mode)
},
},
}
</script>

<style scoped lang="scss">
.maps-settings-time-filter-mode {
margin-top: -8px;
padding-left: 28px;
}

.loading-icon {
margin-top: 16px;
}
Expand Down
165 changes: 165 additions & 0 deletions src/components/map/DateRangeFilter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<template>
<div id="dateRangeFilter">
<NcDateTimePicker
class="date-range-filter-picker"
type="date"
:model-value="startValue"
:min="minDate"
:max="startMaxDate"
:clearable="false"
:append-to-body="true"
:placeholder="t('maps', 'From')"
:aria-label="t('maps', 'Time filter start date')"
@update:model-value="onStartChange" />
<span class="date-range-filter-separator">–</span>
<NcDateTimePicker
class="date-range-filter-picker"
type="date"
:model-value="endValue"
:min="endMinDate"
:max="maxDate"
:clearable="false"
:append-to-body="true"
:placeholder="t('maps', 'To')"
:aria-label="t('maps', 'Time filter end date')"
@update:model-value="onEndChange" />
<NcButton
:aria-label="t('maps', 'Show all dates')"
:title="t('maps', 'Show all dates')"
@click="onReset">
{{ t('maps', 'Reset') }}
</NcButton>
</div>
</template>

<script>
import { NcButton, NcDateTimePicker } from '@nextcloud/vue'
import optionsController from '../../optionsController.js'

export default {
name: 'DateRangeFilter',

components: {
NcButton,
NcDateTimePicker,
},

props: {
start: {
type: Number,
required: true,
},
end: {
type: Number,
required: true,
},
rangeMin: {
type: Number,
required: true,
},
rangeMax: {
type: Number,
required: true,
},
},

emits: ['range-change'],

computed: {
// the filter can be wider than the data (e.g. 0 when "everything" is selected), keep display inside the data range
displayStart() {
return Math.min(Math.max(this.start, this.rangeMin), this.rangeMax)
},
displayEnd() {
return Math.max(Math.min(this.end, this.rangeMax), this.displayStart)
},
startValue() {
return new Date(this.displayStart * 1000)
},
endValue() {
return new Date(this.displayEnd * 1000)
},
// first selectable day (dates before are grayed out)
minDate() {
return startOfDay(this.rangeMin)
},
// last selectable day (dates after are grayed out)
maxDate() {
return endOfDay(this.rangeMax)
},
// start can not be after the selected end day
startMaxDate() {
return endOfDay(this.displayEnd)
},
// end can not be before the selected start day
endMinDate() {
return startOfDay(this.displayStart)
},
},

methods: {
// whole days, in local time: start day 00:00:00, end day 23:59:59
onStartChange(date) {
if (!date) {
return
}
this.changeRange(Math.floor(startOfDay(date.getTime() / 1000).getTime() / 1000), this.end)
},
onEndChange(date) {
if (!date) {
return
}
this.changeRange(this.start, Math.floor(endOfDay(date.getTime() / 1000).getTime() / 1000))
},
onReset() {
this.changeRange(this.rangeMin, this.rangeMax)
},
changeRange(start, end) {
// remember the selection, a bound at the edge of the data is saved as open ('')
// so data added later (e.g. new device points) is still shown after a page reload
optionsController.saveOptionValues({
dateFilterStart: start <= this.rangeMin ? '' : String(start),
dateFilterEnd: end >= this.rangeMax ? '' : String(end),
})
this.$emit('range-change', { start, end })
},
},
}

function startOfDay(timestamp) {
const d = new Date(timestamp * 1000)
d.setHours(0, 0, 0, 0)
return d
}

function endOfDay(timestamp) {
const d = new Date(timestamp * 1000)
d.setHours(23, 59, 59, 999)
return d
}
</script>

<style lang="scss" scoped>
#dateRangeFilter {
position: absolute;
left: 50%;
bottom: 30px;
transform: translateX(-50%);
z-index: 10000;
display: flex;
align-items: center;
gap: 8px;
padding: 6px;
border-radius: var(--border-radius-large);
background-color: var(--color-main-background);
box-shadow: 0 0 10px var(--color-box-shadow);
}

.date-range-filter-picker {
width: 160px;
}

.date-range-filter-separator {
color: var(--color-text-maxcontrast);
}
</style>
16 changes: 10 additions & 6 deletions src/components/map/DeviceHoverMarker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,25 @@ export default {

watch: {
point: {
handler(newPoint) {
handler(newPoint, oldPoint) {
if (this.marker) {
this.marker.setLatLng([newPoint.lat, newPoint.lng])
this.marker.setIcon(this.icon)
this.marker.setTooltipContent(this.$el) // Force update DOM
// only replace the icon element when needed, replacing it under the mouse breaks hover events
if (!oldPoint || newPoint.color !== oldPoint.color) {
this.marker.setIcon(this.icon)
}
// show the info of the newly hovered point
this.marker.openTooltip()
}
},
deep: true
}
},

mounted() {
this.marker = L.marker([this.point.lat, this.point.lng], {
icon: this.icon,
interactive: false
// interactive (like in 1.6.0) so hovering the point opens its tooltip
this.marker = L.marker([this.point.lat, this.point.lng], {
icon: this.icon,
}).addTo(this.map)

this.marker.bindTooltip(this.$el, this.tooltipOptions)
Expand Down
23 changes: 20 additions & 3 deletions src/components/map/DevicesLayer.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<template>
<div style="display: none;">
<DeviceHoverMarker v-if="hoverPoint"
:point="hoverPoint"
:map="map" />
<LHeatMap v-if="points.length >= 2500"
ref="devicesHeatMap"
:map="map"
Expand All @@ -14,6 +17,7 @@ import { isComputer } from '../../utils.js'
import { binSearch, isPublic } from '../../utils/common.js'
import moment from '@nextcloud/moment'
import LHeatMap from './LHeatMap.vue'
import DeviceHoverMarker from './DeviceHoverMarker.vue'
import optionsController from '../../optionsController.js'

const DEVICE_MARKER_VIEW_SIZE = 40
Expand All @@ -22,6 +26,7 @@ export default {
name: 'DevicesLayer',
components: {
LHeatMap,
DeviceHoverMarker,
},

props: {
Expand All @@ -38,6 +43,7 @@ export default {
blur: 10,
gradient: { 0.4: 'blue', 0.65: 'lime', 1: 'red' },
},
hoverPoint: null,
}
},

Expand All @@ -50,6 +56,9 @@ export default {
displayedDevices() {
return this.devices.filter(d => d.enabled && d.points && d.points.length > 0);
},
enabledDevices() {
return this.devices.map(d => d.enabled);
},
points() {
return this.devices.reduce((pts, device) => {
if (device.enabled && device.historyEnabled) {
Expand All @@ -71,6 +80,9 @@ export default {

watch: {
displayedDevices: { handler: 'renderDevices', deep: true },
enabledDevices() {
this.hoverPoint = null;
},
start: 'renderDevices',
end: 'renderDevices',
},
Expand Down Expand Up @@ -131,7 +143,7 @@ export default {
</div>
`, { sticky: true, className: 'leaflet-marker-device-tooltip', direction: 'top', offset: L.point(0, 0) });

marker.on('mouseover', () => this.$emit('point-hover', { ...device.points[device.points.length - 1], color, user_agent: device.user_agent }));
marker.on('mouseover', () => this.onPointHover({ ...device.points[device.points.length - 1], color, user_agent: device.user_agent }));
marker.on('click', () => this.$emit('click', device));
marker.on('contextmenu', (e) => this.showDevicePopup(e, device));

Expand Down Expand Up @@ -191,9 +203,14 @@ export default {
}, { minDist: 40000000, hoverPoint: null });

if (closestPoint.hoverPoint) {
this.$emit('point-hover', { ...closestPoint.hoverPoint, color, user_agent: device.user_agent });
this.onPointHover({ ...closestPoint.hoverPoint, color, user_agent: device.user_agent });
}
}
},

onPointHover(point) {
this.hoverPoint = point;
this.$emit('point-hover', point);
},
},
}
</script>
Expand Down
Loading