13 Commits

Author SHA1 Message Date
Linus Groh
2827d85865 Release 2.0.0-beta.6 2019-12-14 19:39:56 +00:00
Linus Groh
693947c064 Fix heatmap 2019-12-14 19:35:24 +00:00
Linus Groh
a31c048060 Release 2.0.0-beta.5 2019-12-14 17:13:13 +00:00
Linus Groh
1f2be0aeb9 Upgrade dependencies 2019-12-14 17:10:22 +00:00
Linus Groh
92401eb6b1 Add $config Vue instance property 2019-12-14 16:59:12 +00:00
Linus Groh
8d2f22d3de Use configured locale for timestamp formatting 2019-12-14 16:48:18 +00:00
Linus Groh
1d106e45da Add Leaflet popup close button bg color transition 2019-12-14 16:33:17 +00:00
Linus Groh
1f07ae9266 Improve accessibility
* More title attributes
* Usage of aria-hidden and role attributes
* Focus style improvements
* Text contrast improvements
2019-12-14 16:32:10 +00:00
Linus Groh
8399476195 Release 2.0.0-beta.4 2019-12-14 09:43:07 -05:00
Linus Groh
7767a06875 Add support for time selection 2019-12-14 09:35:46 -05:00
Linus Groh
5bcb7a63bc Add missing translation of "to" 2019-12-14 13:37:56 +00:00
Linus Groh
f0b3ed2632 Add example commit for new locale 2019-12-13 20:33:54 +00:00
Linus Groh
185d6fd842 Clarify i18n development notes 2019-12-13 20:29:10 +00:00
31 changed files with 430 additions and 270 deletions

View File

@@ -1,3 +1,24 @@
# 2.0.0-beta.6 (2019-12-14)
- Fix heatmap - the upgrade of `vue2-leaflet` from 2.2.1 to 2.3.0 added an `activated` attribute to layers causing the heatmap to not show
# 2.0.0-beta.5 (2019-12-14)
- Add Leaflet popup close button background color transition
- Add `$config` Vue instance property
- Improve accessibility
- Use configured locale for timestamp formatting
- Upgrade dependencies
# 2.0.0-beta.4 (2019-12-14)
- Add support for time selection
- New date/time picker component is properly translated/localised and keyboard accessible
- Config options are now `startDateTime`/`endDateTime` and format of URL parameters changed
- Changed default start/end date and time to use local timezone
- Fix missing translation of "[date] to [date]"
- Update i18n development notes in `README.md`
# 2.0.0-beta.3 (2019-12-13)
- Add i18 support (currently English and German, `locale` config option)
@@ -9,7 +30,6 @@
- `README.md` enhancements
- Upgrade dependencies
# 2.0.0-beta.2 (2019-11-02)
- Add `onLocationChange.reloadHistory` config option

View File

@@ -135,7 +135,10 @@ $ yarn i18n:report
```
To add a new locale, copy `en.json` to `<locale>.json` in [`src/locales`](src/locales)
and start translating the individual strings. Make sure to add the new locale to the docs!
and start translating the individual strings. Make sure to [mention the new locale to the docs](docs/config.md#locale)!
For a specific example see commit [`b2edda4`](https://github.com/owntracks/frontend/commit/b2edda410f16633aa6fd9cd4e5250f2031536c7d)
where German translations were added.
## Screenshots

View File

@@ -6,7 +6,7 @@ COPY . ./
RUN yarn build
FROM nginx:1.17-alpine
LABEL version="2.0.0-beta.3"
LABEL version="2.0.0-beta.6"
LABEL description="OwnTracks UI"
LABEL maintainer="Linus Groh <mail@linusgroh.de>"
ENV LISTEN_PORT=80 \

View File

@@ -24,7 +24,7 @@ window.owntracks.config = {};
- `api`
- [`baseUrl`](#apibaseurl)
- [`endDate`](#enddate)
- [`endDateTime`](#enddatetime)
- [`ignorePingLocation`](#ignorepinglocation)
- [`locale`](#locale)
- `map`
@@ -65,7 +65,7 @@ window.owntracks.config = {};
- [`primaryColor`](#primarycolor)
- [`selectedDevice`](#selecteddevice)
- [`selectedUser`](#selecteduser)
- [`startDate`](#startdate)
- [`startDateTime`](#startdatetime)
- [`verbose`](#verbose)
### `api.baseUrl`
@@ -92,17 +92,17 @@ Base URL for the recorder's HTTP and WebSocket API. Keep CORS in mind.
};
```
### `endDate`
### `endDateTime`
Initial end date for fetched data.
Initial end date and time (browser timezone) for fetched data.
- Type: [`Date`]
- Default: today
- Default: today, 23:59:59
- Example:
```js
// Data will be fetched up to 1970-01-01
window.owntracks.config = {
endDate: new Date(1970, 1, 1)
endDateTime: new Date(1970, 1, 1)
};
```
@@ -432,20 +432,20 @@ amount of data fetched after page load.
};
```
### `startDate`
### `startDateTime`
Initial start date for fetched data.
Initial start date and time (browser timezone) for fetched data.
- Type: [`Date`]
- Default: one month ago
- Default: one month ago, 00:00:00
- Example:
```js
// Data will be fetched from the first day of the current month
const startDate = new Date();
startDate.setUTCHours(0, 0, 0, 0);
startDate.setUTCDate(1);
const startDateTime = new Date();
startDateTime.setHours(0, 0, 0, 0);
startDateTime.setDate(1);
window.owntracks.config = {
startDate
startDateTime
};
```

View File

@@ -1,6 +1,6 @@
{
"name": "owntracks-ui",
"version": "2.0.0-beta.3",
"version": "2.0.0-beta.6",
"author": {
"name": "Linus Groh",
"email": "mail@linusgroh.de"
@@ -15,18 +15,19 @@
},
"dependencies": {
"clipboard-copy": "^3.1.0",
"core-js": "^3.4.8",
"core-js": "^3.5.0",
"deepmerge": "^4.2.2",
"leaflet": "^1.6.0",
"leaflet.heat": "^0.2.0",
"vue": "^2.6.6",
"moment": "^2.24.0",
"vue": "^2.6.11",
"vue-ctk-date-time-picker": "^2.4.0",
"vue-feather-icons": "^5.0.0",
"vue-i18n": "^8.0.0",
"vue-js-modal": "^1.3.31",
"vue-outside-events": "^1.1.3",
"vue-router": "^3.1.3",
"vue2-leaflet": "^2.2.1",
"vuejs-datepicker": "^1.6.2",
"vue2-leaflet": "^2.3.0",
"vuex": "^3.1.2"
},
"devDependencies": {
@@ -45,10 +46,11 @@
"eslint-plugin-vue": "^6.0.1",
"jest-fetch-mock": "^2.1.2",
"lint-staged": "^9.5.0",
"moment-locales-webpack-plugin": "^1.1.2",
"node-sass": "^4.13.0",
"sass-loader": "^8.0.0",
"vue-cli-plugin-i18n": "^0.6.0",
"vue-template-compiler": "^2.5.21"
"vue-template-compiler": "^2.6.11"
},
"license": "MIT",
"repository": {

View File

@@ -17,7 +17,6 @@
<script>
import { mapActions } from "vuex";
import config from "@/config";
import * as types from "@/store/mutation-types";
import { log } from "@/logging";
import AppHeader from "@/components/AppHeader";
@@ -30,7 +29,7 @@ export default {
created() {
document.documentElement.style.setProperty(
"--color-primary",
config.primaryColor
this.$config.primaryColor
);
this.populateStateFromQuery(this.$route.query);
this.loadData();
@@ -40,8 +39,8 @@ export default {
[
types.SET_SELECTED_USER,
types.SET_SELECTED_DEVICE,
types.SET_START_DATE,
types.SET_END_DATE,
types.SET_START_DATE_TIME,
types.SET_END_DATE_TIME,
types.SET_MAP_CENTER,
types.SET_MAP_ZOOM,
types.SET_MAP_LAYER_VISIBILITY,
@@ -68,8 +67,8 @@ export default {
updateUrlQuery() {
const {
map,
startDate: start,
endDate: end,
startDateTime: start,
endDateTime: end,
selectedUser: user,
selectedDevice: device,
} = this.$store.state;
@@ -80,8 +79,8 @@ export default {
lat: map.center.lat,
lng: map.center.lng,
zoom: map.zoom,
start: start.toISOString().split("T")[0],
end: end.toISOString().split("T")[0],
start,
end,
...(user !== null && { user }),
...(user !== null && device !== null && { device }),
...(activeLayers.length > 0 && { layers: activeLayers.join(",") }),

View File

@@ -95,8 +95,8 @@ export const getLastLocations = async (user, device) => {
*
* @param {User} user Username
* @param {Device} device Device name
* @param {Date} start Start date
* @param {Date} end End date
* @param {String} start Start date and time in UTC
* @param {String} end End date and time in UTC
* @return {LocationHistory} Array of location history objects
*/
export const getUserDeviceLocationHistory = async (
@@ -105,11 +105,9 @@ export const getUserDeviceLocationHistory = async (
start,
end
) => {
const startDate = start.toISOString().split("T")[0];
const endDate = end.toISOString().split("T")[0];
const response = await fetchApi("/api/0/locations", {
from: `${startDate}T00:00:00`,
to: `${endDate}T23:59:59`,
from: start,
to: end,
user,
device,
format: "json",
@@ -122,8 +120,8 @@ export const getUserDeviceLocationHistory = async (
* Get the location history of multiple devices.
*
* @param {Object.<User, Array.<Device>>} devices Devices of which the history should be fetched
* @param {Date} start Start date
* @param {Date} end End date
* @param {String} start Start date and time in UTC
* @param {String} end End date and time in UTC
* @return {Object.<User, Object.<Device, LocationHistory>>} Array of location history objects
*/
export const getLocationHistory = async (devices, start, end) => {

View File

@@ -13,7 +13,7 @@
</button>
</div>
<div class="nav-item">
<LayersIcon size="1x" />
<LayersIcon size="1x" aria-hidden="true" role="img" />
<Dropdown :label="$t('Layer settings')" :title="$t('Show/hide layers')">
<label v-for="option in layerSettingsOptions" :key="option.layer">
<input
@@ -31,23 +31,39 @@
</Dropdown>
</div>
<div class="nav-item">
<CalendarIcon size="1x" />
<Datepicker
v-model="startDate"
:use-utc="true"
:disabled-dates="startDateDisabledDates"
:title="$t('Select start date')"
/>
to
<Datepicker
v-model="endDate"
:use-utc="true"
:disabled-dates="endDateDisabledDates"
:title="$t('Select end date')"
/>
<CalendarIcon size="1x" aria-hidden="true" role="img" />
<VueCtkDateTimePicker
v-model="startDateTime"
:format="DATE_TIME_FORMAT"
:color="$config.primaryColor"
:locale="$config.locale"
:max-date="endDateTime"
:button-now-translation="$t('Now')"
>
<button
type="button"
class="dropdown-button button"
:title="$t('Select start date')"
/>
</VueCtkDateTimePicker>
{{ $t("to") }}
<VueCtkDateTimePicker
v-model="endDateTime"
:format="DATE_TIME_FORMAT"
:color="$config.primaryColor"
:locale="$config.locale"
:min-date="startDateTime"
:button-now-translation="$t('Now')"
>
<button
type="button"
class="dropdown-button button"
:title="$t('Select end date')"
/>
</VueCtkDateTimePicker>
</div>
<div class="nav-item">
<UserIcon size="1x" />
<UserIcon size="1x" aria-hidden="true" role="img" />
<select
v-model="selectedUser"
class="dropdown-button button"
@@ -62,7 +78,7 @@
</select>
</div>
<div v-if="selectedUser" class="nav-item">
<SmartphoneIcon size="1x" />
<SmartphoneIcon size="1x" aria-hidden="true" role="img" />
<select
v-model="selectedDevice"
class="dropdown-button button"
@@ -88,7 +104,11 @@
:title="$t('Download raw data')"
@click="$modal.show('download')"
>
<DownloadIcon size="1x" />
<DownloadIcon
size="1x"
:aria-label="$t('Download raw data')"
role="img"
/>
</button>
</div>
<div class="nav-item">
@@ -97,7 +117,7 @@
:title="$t('Information')"
@click="$modal.show('information')"
>
<InfoIcon size="1x" />
<InfoIcon size="1x" :aria-label="$t('Information')" role="img" />
</button>
</div>
</nav>
@@ -105,6 +125,7 @@
</template>
<script>
import moment from "moment";
import { mapActions, mapGetters, mapMutations, mapState } from "vuex";
import {
CalendarIcon,
@@ -114,9 +135,11 @@ import {
SmartphoneIcon,
UserIcon,
} from "vue-feather-icons";
import Datepicker from "vuejs-datepicker";
import VueCtkDateTimePicker from "vue-ctk-date-time-picker";
import "vue-ctk-date-time-picker/dist/vue-ctk-date-time-picker.css";
import Dropdown from "@/components/Dropdown";
import { DATE_TIME_FORMAT } from "@/constants";
import * as types from "@/store/mutation-types";
export default {
@@ -127,11 +150,12 @@ export default {
LayersIcon,
SmartphoneIcon,
UserIcon,
Datepicker,
VueCtkDateTimePicker,
Dropdown,
},
data() {
return {
DATE_TIME_FORMAT,
layerSettingsOptions: [
{ layer: "last", label: this.$t("Show last known locations") },
{ layer: "line", label: this.$t("Show location history (line)") },
@@ -142,7 +166,6 @@ export default {
},
computed: {
...mapState(["users", "devices", "map"]),
...mapGetters(["startDateDisabledDates", "endDateDisabledDates"]),
selectedUser: {
get() {
return this.$store.state.selectedUser;
@@ -159,20 +182,35 @@ export default {
this.setSelectedDevice(value);
},
},
startDate: {
startDateTime: {
get() {
return this.$store.state.startDate;
return moment
.utc(this.$store.state.startDateTime, DATE_TIME_FORMAT)
.local()
.format(DATE_TIME_FORMAT);
},
set(value) {
this.setStartDate(value);
this.setStartDateTime(
moment(value, DATE_TIME_FORMAT)
.utc()
.format(DATE_TIME_FORMAT)
);
},
},
endDate: {
endDateTime: {
get() {
return this.$store.state.endDate;
return moment
.utc(this.$store.state.endDateTime, DATE_TIME_FORMAT)
.local()
.format(DATE_TIME_FORMAT);
},
set(value) {
this.setEndDate(value);
this.setEndDateTime(
moment(value, DATE_TIME_FORMAT)
.set("seconds", 59)
.utc()
.format(DATE_TIME_FORMAT)
);
},
},
},
@@ -183,8 +221,8 @@ export default {
...mapActions([
"setSelectedUser",
"setSelectedDevice",
"setStartDate",
"setEndDate",
"setStartDateTime",
"setEndDateTime",
]),
},
};

View File

@@ -3,30 +3,30 @@
<div v-if="name" class="device">{{ name }}</div>
<div v-else class="device">{{ user }}/{{ device }}</div>
<div class="wrapper">
<img v-if="face" :src="faceImageDataURI" />
<img v-if="face" :src="faceImageDataURI" alt="" />
<ul class="info-list">
<li>
<ClockIcon size="1x" />
{{ new Date(timestamp * 1000).toLocaleString() }}
<li :title="$t('Timestamp')">
<ClockIcon size="1x" aria-hidden="true" role="img" />
{{ new Date(timestamp * 1000).toLocaleString($config.locale) }}
</li>
<li>
<MapPinIcon size="1x" />
<li :title="$t('Location')">
<MapPinIcon size="1x" aria-hidden="true" role="img" />
{{ lat }}
<br />
{{ lon }}
<br />
{{ alt }}m
</li>
<li v-if="address">
<HomeIcon size="1x" />
<li v-if="address" :title="$t('Address')">
<HomeIcon size="1x" aria-hidden="true" role="img" />
{{ address }}
</li>
<li v-if="typeof battery === 'number'">
<BatteryIcon size="1x" />
<li v-if="typeof battery === 'number'" :title="$t('Battery')">
<BatteryIcon size="1x" aria-hidden="true" role="img" />
{{ battery }} %
</li>
<li v-if="typeof speed === 'number'">
<ZapIcon size="1x" />
<li v-if="typeof speed === 'number'" :title="$t('Speed')">
<ZapIcon size="1x" aria-hidden="true" role="img" />
{{ speed }} km/h
</li>
</ul>

View File

@@ -58,6 +58,11 @@ const props = {
custom: true,
default: true,
},
activated: {
type: Boolean,
custom: true,
default: true,
},
};
export default {

View File

@@ -12,10 +12,18 @@
</label>
</div>
<div class="buttons">
<button class="button button-outline button-primary" @click="copy">
<button
class="button button-outline button-primary"
:title="$t('Copy to clipboard')"
@click="copy"
>
{{ $t("Copy to clipboard") }}
</button>
<button class="button button-primary" @click="download">
<button
class="button button-primary"
:title="$t('Download')"
@click="download"
>
{{ $t("Download") }}
</button>
</div>
@@ -65,8 +73,8 @@ export default {
},
computed: {
...mapState([
"startDate",
"endDate",
"startDateTime",
"endDateTime",
"selectedUser",
"selectedDevice",
"locationHistory",
@@ -90,8 +98,8 @@ export default {
null,
this.options.minifyJson ? 0 : 2
);
const start = this.startDate.toISOString().split("T")[0];
const end = this.endDate.toISOString().split("T")[0];
const start = this.startDateTime;
const end = this.endDateTime;
const user = this.selectedUser ? `_${this.selectedUser}` : "";
const device = this.selectedDevice ? `_${this.selectedDevice}` : "";
const filename = `data_${start}_${end}${user}${device}.json`;

View File

@@ -2,33 +2,33 @@
<modal name="information" adaptive>
<ul class="info-list">
<li>
<GithubIcon size="1x" />
<GithubIcon size="1x" aria-hidden="true" role="img" />
<a href="https://github.com/owntracks/frontend">
owntracks/frontend
</a>
({{ frontendVersion }})
</li>
<li>
<GithubIcon size="1x" />
<GithubIcon size="1x" aria-hidden="true" role="img" />
<a href="https://github.com/owntracks/recorder">
owntracks/recorder
</a>
({{ recorderVersion || "Loading version..." }})
</li>
<li>
<GlobeIcon size="1x" />
<GlobeIcon size="1x" aria-hidden="true" role="img" />
<a href="https://owntracks.org">
{{ $t("OwnTracks website") }}
</a>
</li>
<li>
<BookIcon size="1x" />
<BookIcon size="1x" aria-hidden="true" role="img" />
<a href="https://owntracks.org/booklet/">
{{ $t("OwnTracks documentation") }}
</a>
</li>
<li>
<TwitterIcon size="1x" />
<TwitterIcon size="1x" aria-hidden="true" role="img" />
<a href="https://twitter.com/OwnTracks">
{{ $t("OwnTracks on Twitter") }}
</a>

View File

@@ -1,16 +1,17 @@
import deepmerge from "deepmerge";
const endDate = new Date();
endDate.setUTCHours(0, 0, 0, 0);
const endDateTime = new Date();
endDateTime.setHours(23, 59, 59, 0);
const startDate = new Date(endDate);
startDate.setUTCMonth(startDate.getMonth() - 1);
const startDateTime = new Date(endDateTime);
startDateTime.setMonth(startDateTime.getMonth() - 1);
startDateTime.setHours(0, 0, 0, 0);
const DEFAULT_CONFIG = {
api: {
baseUrl: `${window.location.protocol}//${window.location.host}`,
},
endDate,
endDateTime,
ignorePingLocation: true,
locale: "en",
map: {
@@ -72,7 +73,7 @@ const DEFAULT_CONFIG = {
primaryColor: "#3f51b5",
selectedDevice: null,
selectedUser: null,
startDate,
startDateTime,
verbose: false,
};

View File

@@ -1,8 +1,7 @@
// Regular expression for an ISO 8601 YYYY-MM-DD date.
// Used to validate dates from URL query parameters.
export const ISO_DATE_REGEXP = new RegExp(
/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/
);
// date and time format as expected by the OwnTracks recorder,
// using moment.js formatting tokens.
// https://momentjs.com/docs/#/displaying/format/
export const DATE_TIME_FORMAT = "YYYY-MM-DDTHH:mm:ss";
// https://en.wikipedia.org/wiki/Earth_radius
// Used to calculate the distance between two coordinates.

View File

@@ -3,7 +3,9 @@
"Fit view": "Ansicht anpassen",
"Layer settings": "Ebeneneinstellungen",
"Show/hide layers": "Ebenen ein-/ausblenden",
"Now": "Jetzt",
"Select start date": "Startdatum auswählen",
"to": "bis",
"Select end date": "Enddatum auswählen",
"Select user": "Benutzer auswählen",
"Show all": "Alle anzeigen",
@@ -20,5 +22,10 @@
"OwnTracks website": "OwnTracks Webseite",
"OwnTracks documentation": "OwnTracks Dokumentation",
"OwnTracks on Twitter": "OwnTracks auf Twitter",
"Loading data, please wait...": "Daten werden geladen, bitte warten..."
"Loading data, please wait...": "Daten werden geladen, bitte warten...",
"Timestamp": "Zeitstempel",
"Location": "Standort",
"Address": "Adresse",
"Battery": "Akku",
"Speed": "Geschwindigkeit"
}

View File

@@ -3,7 +3,9 @@
"Fit view": "Fit view",
"Layer settings": "Layer settings",
"Show/hide layers": "Show/hide layers",
"Now": "Now",
"Select start date": "Select start date",
"to": "to",
"Select end date": "Select end date",
"Select user": "Select user",
"Show all": "Show all",
@@ -20,5 +22,10 @@
"OwnTracks website": "OwnTracks website",
"OwnTracks documentation": "OwnTracks documentation",
"OwnTracks on Twitter": "OwnTracks on Twitter",
"Loading data, please wait...": "Loading data, please wait..."
"Loading data, please wait...": "Loading data, please wait...",
"Timestamp": "Timestamp",
"Location": "Location",
"Address": "Address",
"Battery": "Battery",
"Speed": "Speed"
}

View File

@@ -1,5 +1,6 @@
import Vue from "vue";
import App from "@/App.vue";
import config from "@/config";
import i18n from "@/i18n";
import router from "@/router";
import store from "@/store";
@@ -11,6 +12,8 @@ Vue.use(VOutsideEvents);
Vue.config.productionTip = false;
Vue.prototype.$config = config;
new Vue({
i18n,
router,

View File

@@ -1,7 +1,7 @@
import * as types from "@/store/mutation-types";
import * as api from "@/api";
import config from "@/config";
import { isIsoDate } from "@/util";
import { isIsoDateTime } from "@/util";
/** @typedef {import("./types").QueryParams} QueryParams */
/** @typedef {import("./types").User} User */
@@ -28,11 +28,11 @@ const populateStateFromQuery = ({ state, commit }, query) => {
if (query.zoom && !isNaN(parseInt(query.zoom))) {
commit(types.SET_MAP_ZOOM, parseInt(query.zoom));
}
if (query.start && isIsoDate(query.start)) {
commit(types.SET_START_DATE, new Date(query.start));
if (query.start && isIsoDateTime(query.start)) {
commit(types.SET_START_DATE_TIME, query.start);
}
if (query.end && isIsoDate(query.end)) {
commit(types.SET_END_DATE, new Date(query.end));
if (query.end && isIsoDateTime(query.end)) {
commit(types.SET_END_DATE_TIME, query.end);
}
if (query.user) {
commit(types.SET_SELECTED_USER, query.user);
@@ -138,7 +138,11 @@ const getLocationHistory = async ({ commit, state }) => {
}
commit(
types.SET_LOCATION_HISTORY,
await api.getLocationHistory(devices, state.startDate, state.endDate)
await api.getLocationHistory(
devices,
state.startDateTime,
state.endDateTime
)
);
commit(types.SET_IS_LOADING, false);
};
@@ -172,22 +176,22 @@ const setSelectedDevice = async ({ commit, dispatch }, device) => {
};
/**
* Set the start date for loading data and reload the location history.
* Set the start date and time for loading data and reload the location history.
*
* @param {Date} startDate Start date for loading data
* @param {String} startDateTime Start date and time in UTC for loading data
*/
const setStartDate = async ({ commit, dispatch }, startDate) => {
commit(types.SET_START_DATE, startDate);
const setStartDateTime = async ({ commit, dispatch }, startDateTime) => {
commit(types.SET_START_DATE_TIME, startDateTime);
await dispatch("reloadData");
};
/**
* Set the end date for loading data and reload the location history.
* Set the end date and time for loading data and reload the location history.
*
* @param {Date} endDate End date for loading data
* @param {String} endDateTime End date and time in UTC for loading data
*/
const setEndDate = async ({ commit, dispatch }, endDate) => {
commit(types.SET_END_DATE, endDate);
const setEndDateTime = async ({ commit, dispatch }, endDateTime) => {
commit(types.SET_END_DATE_TIME, endDateTime);
await dispatch("reloadData");
};
@@ -203,6 +207,6 @@ export default {
getRecorderVersion,
setSelectedUser,
setSelectedDevice,
setStartDate,
setEndDate,
setStartDateTime,
setEndDateTime,
};

View File

@@ -65,37 +65,7 @@ const locationHistoryLatLngGroups = state => {
return groups;
};
/**
* For the start date selector, disable all dates above the end date
* or current date.
*
* @param {State} state
* @param {Date} state.endDate End date
* @return {DatepickerConfig} Configuration for the `disabled-dates` prop of the `vuejs-datepicker` component
*/
const startDateDisabledDates = state => {
return {
customPredictor: date => date > state.endDate || date > new Date(),
};
};
/**
* For the end date selector, disable all dates below the start date
* or above the current date.
*
* @param {State} state
* @param {Date} state.startDate Start date
* @return {DatepickerConfig} Configuration for the `disabled-dates` prop of the `vuejs-datepicker` component
*/
const endDateDisabledDates = state => {
return {
customPredictor: date => date < state.startDate || date > new Date(),
};
};
export default {
locationHistoryLatLngs,
locationHistoryLatLngGroups,
startDateDisabledDates,
endDateDisabledDates,
};

View File

@@ -19,8 +19,9 @@ export default new Vuex.Store({
locationHistory: {},
selectedUser: config.selectedUser,
selectedDevice: config.selectedUser !== null ? config.selectedDevice : null,
startDate: config.startDate,
endDate: config.endDate,
// Convert to UTC and get rid of milliseconds
startDateTime: config.startDateTime.toISOString().slice(0, 19),
endDateTime: config.endDateTime.toISOString().slice(0, 19),
map: {
center: config.map.center,
zoom: config.map.zoom,

View File

@@ -6,8 +6,8 @@ export const SET_LAST_LOCATIONS = "SET_LAST_LOCATIONS";
export const SET_LOCATION_HISTORY = "SET_LOCATION_HISTORY";
export const SET_SELECTED_USER = "SET_SELECTED_USER";
export const SET_SELECTED_DEVICE = "SET_SELECTED_DEVICE";
export const SET_START_DATE = "SET_START_DATE";
export const SET_END_DATE = "SET_END_DATE";
export const SET_START_DATE_TIME = "SET_START_DATE_TIME";
export const SET_END_DATE_TIME = "SET_END_DATE_TIME";
export const SET_MAP_CENTER = "SET_MAP_CENTER";
export const SET_MAP_ZOOM = "SET_MAP_ZOOM";
export const SET_MAP_LAYER_VISIBILITY = "SET_MAP_LAYER_VISIBILITY";

View File

@@ -25,11 +25,11 @@ export default {
[types.SET_SELECTED_DEVICE](state, selectedDevice) {
state.selectedDevice = selectedDevice;
},
[types.SET_START_DATE](state, startDate) {
state.startDate = startDate;
[types.SET_START_DATE_TIME](state, startDateTime) {
state.startDateTime = startDateTime;
},
[types.SET_END_DATE](state, endDate) {
state.endDate = endDate;
[types.SET_END_DATE_TIME](state, endDateTime) {
state.endDateTime = endDateTime;
},
[types.SET_MAP_CENTER](state, center) {
state.map.center = center;

View File

@@ -102,7 +102,6 @@ pre {
display: flex;
padding: 20px;
white-space: nowrap;
overflow-x: auto;
color: var(--color-primary-text);
background: var(--color-primary);
@@ -163,16 +162,6 @@ pre {
}
}
.vdp-datepicker input {
cursor: pointer;
color: var(--color-text);
background: var(--color-background);
border: 0;
border-radius: 18px;
padding: 8px 16px;
min-width: 130px;
}
.button {
cursor: pointer;
color: var(--color-text);
@@ -183,14 +172,20 @@ pre {
padding: 8px 16px;
text-overflow: ellipsis;
white-space: nowrap;
transition: box-shadow 0.2s;
&:focus {
outline: none;
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.2);
}
&:focus::-moz-focus-inner {
border-color: transparent;
}
&.button-primary {
color: var(--color-primary-text);
background: var(--color-primary);
&:focus::-moz-focus-inner {
border-color: var(--color-primary-text)e;
}
}
&.button-outline {
@@ -215,7 +210,8 @@ pre {
&.button-outline,
&.button-flat {
transition: background 0.2s;
transition: background-color 0.2s, box-shadow 0.2s;
&:hover,
&:focus {
background: rgba(0, 0, 0, 0.2);
@@ -229,12 +225,11 @@ pre {
.dropdown {
display: inline-block;
position: relative;
}
.dropdown-button,
.vdp-datepicker input {
-webkit-appearance: none;
-moz-appearance: none;
// Not nested so it works as the button alone
.dropdown-button {
appearance: none;
background-image: var(--dropdown-arrow);
background-repeat: no-repeat;
@@ -253,6 +248,17 @@ pre {
filter: var(--drop-shadow);
z-index: 1;
&::before {
content: "";
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid var(--color-background);
position: absolute;
top: -20px;
left: 20px;
}
label {
cursor: pointer;
display: block;
@@ -264,18 +270,6 @@ pre {
}
}
.dropdown-body::before,
.vdp-datepicker .vdp-datepicker__calendar::before {
content: "";
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid var(--color-background);
position: absolute;
top: -20px;
left: 20px;
}
.feather {
vertical-align: middle;
}

View File

@@ -1,32 +1,10 @@
.vdp-datepicker {
position: static !important;
.date-time-picker {
display: inline-block;
white-space: initial;
overflow: initial;
width: auto;
input {
width: 120px;
}
.vdp-datepicker__calendar {
color: var(--color-text);
border: 0;
border-radius: 3px;
margin-top: 12px;
.datepicker {
box-shadow: none !important;
filter: var(--drop-shadow);
.cell {
&:not(.blank):not(.disabled).day:hover,
&:not(.blank):not(.disabled).month:hover,
&:not(.blank):not(.disabled).year:hover {
border-color: var(--color-primary);
}
&.selected,
&.selected:hover {
background: var(--color-primary);
color: var(--color-primary-text);
}
}
margin-top: 5px;
}
}

View File

@@ -23,6 +23,7 @@
margin-top: 15px;
margin-right: 15px;
border-radius: 100px;
transition: background-color 0.2s;
&:hover,
&:focus {
@@ -35,4 +36,12 @@
.leaflet-popup-tip {
box-shadow: none;
}
.leaflet-control-container .leaflet-control-attribution {
background: var(--color-background);
a {
color: var(--color-primary);
}
}
}

View File

@@ -1,5 +1,7 @@
import moment from "moment";
import config from "@/config";
import { ISO_DATE_REGEXP, EARTH_RADIUS_IN_KM } from "@/constants";
import { DATE_TIME_FORMAT, EARTH_RADIUS_IN_KM } from "@/constants";
/** @typedef {import("./types").Coordinate} Coordinate */
@@ -19,12 +21,12 @@ export const getApiUrl = path => {
};
/**
* Check if the given string is an ISO 8601 YYYY-MM-DD date.
* Check if the given string is an ISO 8601 YYYY-MM-DDTHH:MM:SS datetime.
*
* @param {String} s Input value to be tested
* @return {Boolean} Whether the input is an ISO 8601 date
* @return {Boolean} Whether the input matches the expected format
*/
export const isIsoDate = s => ISO_DATE_REGEXP.test(s);
export const isIsoDateTime = s => moment(s, DATE_TIME_FORMAT, true).isValid();
/**
* Convert degrees to radians.

View File

@@ -118,7 +118,6 @@ import markerIcon2x from "leaflet/dist/images/marker-icon-2x.png";
import markerShadow from "leaflet/dist/images/marker-shadow.png";
import * as types from "@/store/mutation-types";
import config from "@/config";
import LHeatmap from "@/components/LHeatmap";
import LDeviceLocationPopup from "@/components/LDeviceLocationPopup";
@@ -145,26 +144,27 @@ export default {
},
data() {
return {
attribution: config.map.attribution,
attribution: this.$config.map.attribution,
center: this.$store.state.map.center,
controls: config.map.controls,
heatmap: config.map.heatmap,
maxZoom: config.map.maxZoom,
maxNativeZoom: config.map.maxNativeZoom,
url: config.map.url,
controls: this.$config.map.controls,
heatmap: this.$config.map.heatmap,
maxZoom: this.$config.map.maxZoom,
maxNativeZoom: this.$config.map.maxNativeZoom,
url: this.$config.map.url,
zoom: this.$store.state.map.zoom,
circle: {
...config.map.circle,
color: config.map.circle.color || config.primaryColor,
fillColor: config.map.circle.fillColor || config.primaryColor,
...this.$config.map.circle,
color: this.$config.map.circle.color || this.$config.primaryColor,
fillColor:
this.$config.map.circle.fillColor || this.$config.primaryColor,
},
circleMarker: {
...config.map.circleMarker,
color: config.map.circleMarker.color || config.primaryColor,
...this.$config.map.circleMarker,
color: this.$config.map.circleMarker.color || this.$config.primaryColor,
},
polyline: {
...config.map.polyline,
color: config.map.polyline.color || config.primaryColor,
...this.$config.map.polyline,
color: this.$config.map.polyline.color || this.$config.primaryColor,
},
};
},

View File

@@ -144,8 +144,8 @@ describe("API", () => {
const locationHistory = await api.getUserDeviceLocationHistory(
"foo",
"phone",
new Date(Date.UTC(1970, 0, 1)),
new Date(Date.UTC(1970, 11, 31))
"1970-01-01T00:00:00",
"1970-12-31T23:59:59"
);
expect(locationHistory).toEqual(response.data);
@@ -194,8 +194,8 @@ describe("API", () => {
const locationHistory = await api.getLocationHistory(
{ foo: ["phone", "tablet"], bar: ["laptop"] },
new Date(Date.UTC(1970, 0, 1)),
new Date(Date.UTC(1970, 11, 31))
"1970-01-01T00:00:00",
"1970-12-31T23:59:59"
);
expect(locationHistory).toEqual({
foo: {

View File

@@ -1,7 +1,7 @@
import config from "@/config";
import {
getApiUrl,
isIsoDate,
isIsoDateTime,
degreesToRadians,
distanceBetweenCoordinates,
} from "@/util";
@@ -24,26 +24,36 @@ describe("getApiUrl", () => {
});
});
describe("isIsoDate", () => {
describe("isIsoDateTime", () => {
test("no match", () => {
expect(isIsoDate("foo")).toBe(false);
expect(isIsoDate("2019")).toBe(false);
expect(isIsoDate("2019-09")).toBe(false);
expect(isIsoDate("2019.09.27")).toBe(false);
expect(isIsoDate("2019_09_27")).toBe(false);
expect(isIsoDate("2019/09/27")).toBe(false);
expect(isIsoDate("27-09-2019")).toBe(false);
expect(isIsoDate("27.09.2019")).toBe(false);
expect(isIsoDate("27_09_2019")).toBe(false);
expect(isIsoDate("27/09/2019")).toBe(false);
expect(isIsoDate("0000-00-00")).toBe(false);
expect(isIsoDate("1234-56-78")).toBe(false);
expect(isIsoDateTime("foo")).toBe(false);
expect(isIsoDateTime("2019")).toBe(false);
expect(isIsoDateTime("2019-09")).toBe(false);
expect(isIsoDateTime("2019.09.27")).toBe(false);
expect(isIsoDateTime("2019_09_27")).toBe(false);
expect(isIsoDateTime("2019/09/27")).toBe(false);
expect(isIsoDateTime("27-09-2019")).toBe(false);
expect(isIsoDateTime("27.09.2019")).toBe(false);
expect(isIsoDateTime("27_09_2019")).toBe(false);
expect(isIsoDateTime("27/09/2019")).toBe(false);
expect(isIsoDateTime("0000-00-00")).toBe(false);
expect(isIsoDateTime("1234-56-78")).toBe(false);
expect(isIsoDateTime("0000-00-00T00:00:00")).toBe(false);
expect(isIsoDateTime("0000-01-01T25:60:60")).toBe(false);
expect(isIsoDateTime("2019-12-14T99:00:00")).toBe(false);
expect(isIsoDateTime("2019-12-14 25:60:60")).toBe(false);
});
test("match", () => {
expect(isIsoDate("0000-01-01")).toBe(true);
expect(isIsoDate("2019-09-27")).toBe(true);
expect(isIsoDate("9999-12-31")).toBe(true);
expect(isIsoDateTime("0000-01-01T00:00:00")).toBe(true);
expect(isIsoDateTime("0000-01-01T12:34:56")).toBe(true);
expect(isIsoDateTime("0000-01-01T23:59:59")).toBe(true);
expect(isIsoDateTime("2019-09-27T00:00:00")).toBe(true);
expect(isIsoDateTime("2019-09-27T12:34:56")).toBe(true);
expect(isIsoDateTime("2019-09-27T23:59:59")).toBe(true);
expect(isIsoDateTime("9999-12-31T00:00:00")).toBe(true);
expect(isIsoDateTime("9999-12-31T12:34:56")).toBe(true);
expect(isIsoDateTime("9999-12-31T23:59:59")).toBe(true);
});
});

View File

@@ -1,5 +1,6 @@
const fs = require("fs");
const webpack = require("webpack");
const MomentLocalesPlugin = require("moment-locales-webpack-plugin");
const packageJson = fs.readFileSync("./package.json");
const version = JSON.parse(packageJson).version;
@@ -12,6 +13,7 @@ module.exports = {
PACKAGE_VERSION: `"${version}"`,
},
}),
new MomentLocalesPlugin(),
],
},

128
yarn.lock
View File

@@ -3149,11 +3149,16 @@ core-js@^2.4.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.10.tgz#8a5b8391f8cc7013da703411ce5b585706300d7f"
integrity sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==
core-js@^3.4.3, core-js@^3.4.8:
core-js@^3.4.3:
version "3.4.8"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.4.8.tgz#e0fc0c61f2ef90cbc10c531dbffaa46dfb7152dd"
integrity sha512-b+BBmCZmVgho8KnBUOXpvlqEMguko+0P+kXCwD4vIprsXC6ht1qgPxtb1OK6XgSlrySF71wkwBQ0Hv695bk9gQ==
core-js@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.5.0.tgz#66df8e49be4bd775e6f952a9d083b756ad41c1ed"
integrity sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw==
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -3480,6 +3485,14 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
d@1, d@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
dependencies:
es5-ext "^0.10.50"
type "^1.0.1"
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -4018,6 +4031,32 @@ es-to-primitive@^1.2.0:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es5-ext@^0.10.35, es5-ext@^0.10.50:
version "0.10.53"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
dependencies:
es6-iterator "~2.0.3"
es6-symbol "~3.1.3"
next-tick "~1.0.0"
es6-iterator@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
dependencies:
d "1"
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
es6-symbol@^3.1.0, es6-symbol@^3.1.1, es6-symbol@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
dependencies:
d "^1.0.1"
ext "^1.1.2"
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -4368,6 +4407,13 @@ express@^4.16.3, express@^4.17.1:
utils-merge "1.0.1"
vary "~1.1.2"
ext@^1.1.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==
dependencies:
type "^2.0.0"
extend-shallow@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
@@ -6753,6 +6799,11 @@ lodash.defaultsdeep@^4.6.1:
resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6"
integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==
lodash.difference@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=
lodash.kebabcase@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
@@ -7189,6 +7240,25 @@ mkdirp@0.5.1, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp
dependencies:
minimist "0.0.8"
moment-locales-webpack-plugin@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/moment-locales-webpack-plugin/-/moment-locales-webpack-plugin-1.1.2.tgz#9ae5263ac38d5cba227fc9d76efad025bb685a0c"
integrity sha512-s+JE7lADQjUyeQvqB3sVcfxXncg1o+t5hrRl2GBY66vXuLO2tXIjD+4mNUXQMS10qCGoeK3R3skBrW34gHobBQ==
dependencies:
lodash.difference "^4.5.0"
moment-range@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/moment-range/-/moment-range-4.0.2.tgz#f7c3863df2a1ed7fd1822ba5a7bcf53a78701be9"
integrity sha512-n8sceWwSTjmz++nFHzeNEUsYtDqjgXgcOBzsHi+BoXQU2FW+eU92LUaK8gqOiSu5PG57Q9sYj1Fz4LRDj4FtKA==
dependencies:
es6-symbol "^3.1.0"
moment@^2.24.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
@@ -7289,6 +7359,11 @@ neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
next-tick@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
@@ -10396,6 +10471,16 @@ type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
type@^1.0.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3"
integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@@ -10591,6 +10676,11 @@ uuid@^3.0.1, uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
v-click-outside@^2.0.2:
version "2.1.5"
resolved "https://registry.yarnpkg.com/v-click-outside/-/v-click-outside-2.1.5.tgz#aa69172fb41fcc79b26b9a4bc72a30ccf03f7a3c"
integrity sha512-VPNCOTZK6WZy73lcWc+R7IW1uaBFEO3/Csrs5CzWVOdvE30V8Y1+BE/BtTlcEmeDGx0eqdE7bSCg55Jj37PMJg==
v8-compile-cache@^2.0.3:
version "2.1.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
@@ -10642,6 +10732,16 @@ vue-cli-plugin-i18n@^0.6.0:
vue-i18n "^8.0.0"
vue-i18n-extract "^0.4.13"
vue-ctk-date-time-picker@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/vue-ctk-date-time-picker/-/vue-ctk-date-time-picker-2.4.0.tgz#dde39958ba964027db1e3a78667061e7580ea5a4"
integrity sha512-E4s0kacbL+oqURB+b6o1RNUEdsX8B5WSt6YoQKVZ019ytCv351Bs4FfZajEzo8oc9TVoI3BfcR0hLOYtaGyhEw==
dependencies:
moment "^2.24.0"
moment-range "^4.0.1"
v-click-outside "^2.0.2"
vue "^2.6.9"
vue-eslint-parser@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-6.0.5.tgz#c1c067c2755748e28f3872cd42e8c1c4c1a8059f"
@@ -10733,10 +10833,10 @@ vue-style-loader@^4.1.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-template-compiler@^2.5.21:
version "2.6.10"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
vue-template-compiler@^2.6.11:
version "2.6.11"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080"
integrity sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@@ -10746,20 +10846,20 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue2-leaflet@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/vue2-leaflet/-/vue2-leaflet-2.2.1.tgz#cd67274951751a33df147481fce2b956ba174c7e"
integrity sha512-yBB3Ilv9LTszNn/uxUATq8+Ahw/1FqIasdQehb9m1tJ5T9qeEk8Fr9a6uLzvR+zuz88rnEQVo7/iGUd96E7iLg==
vue2-leaflet@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/vue2-leaflet/-/vue2-leaflet-2.3.0.tgz#829993a76fbce30e5efcc5c0ec01e14470a4080e"
integrity sha512-4wCikjAGKIc7QrlIRwdOiA/5FODg6uxr7HcXfxgaGd29q2YEEaA9pn6rr57RwlbUl5/5vP8DL/iv3+TNr35Wuw==
vue@^2.5.16, vue@^2.6.6:
vue@^2.5.16:
version "2.6.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
vuejs-datepicker@^1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/vuejs-datepicker/-/vuejs-datepicker-1.6.2.tgz#83c1e8fd4108e7f1d01c061a7e344918f25e47ae"
integrity sha512-PkC4vxzFBo7i6FSCUAJfnaWOx6VkKbOqxijSGHHlWxh8FIUKEZVtFychkonVWtK3iwWfhmYtqHcwsmgxefLpLQ==
vue@^2.6.11, vue@^2.6.9:
version "2.6.11"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
vuex@^3.1.2:
version "3.1.2"