6 Commits

Author SHA1 Message Date
Linus Groh
bc3670df99 Release 2.0.0-beta.8 2020-01-26 00:57:08 +00:00
Linus Groh
95613753a9 Enable ESLint max-len rule 2020-01-26 00:49:15 +00:00
Linus Groh
cfa3052a0a Show name and face on location history popups 2020-01-26 00:40:30 +00:00
Linus Groh
0bd84f4de5 s/@return/@returns 2020-01-26 00:38:36 +00:00
Linus Groh
85e51643bf Add missing alt/title to device face image 2020-01-25 23:37:41 +00:00
Linus Groh
6cbdf30580 Use computed prop for device name in location popup 2020-01-25 23:33:10 +00:00
12 changed files with 92 additions and 25 deletions

View File

@@ -7,6 +7,12 @@ module.exports = {
rules: {
"no-console": process.env.NODE_ENV === "production" ? "error" : "warn",
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn",
"max-len": [
"error",
{
ignoreUrls: true,
},
],
"prettier/prettier": [
"error",
{

View File

@@ -1,5 +1,13 @@
# Changelog
## 2.0.0-beta.8 (2020-01-26)
- Add friendly device name and face images to location history popups
- Add missing `alt`/`title` to device face image
- Fix all JSDoc `@return` directives to `@returns`
- Use computed prop for device name in location popup
- Enable ESLint `max-len` rule
## 2.0.0-beta.7 (2020-01-24)
This release doesn't really affect end-users but greatly improves the development experience.

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "owntracks-ui",
"version": "2.0.0-beta.7",
"version": "2.0.0-beta.8",
"author": {
"name": "Linus Groh",
"email": "mail@linusgroh.de"

View File

@@ -18,7 +18,7 @@ import { getApiUrl } from "@/util";
*
* @param {String} path API resource path
* @param {QueryParams} [params] Query parameters
* @return {Promise} Promise returned by the fetch function
* @returns {Promise} Promise returned by the fetch function
*/
const fetchApi = (path, params = {}) => {
const url = getApiUrl(path);
@@ -30,7 +30,7 @@ const fetchApi = (path, params = {}) => {
/**
* Get the recorder's version.
*
* @return {String} Version
* @returns {String} Version
*/
export const getVersion = async () => {
const response = await fetchApi("/api/0/version");
@@ -42,7 +42,7 @@ export const getVersion = async () => {
/**
* Get all users.
*
* @return {Array.<User>} Array of usernames
* @returns {Array.<User>} Array of usernames
*/
export const getUsers = async () => {
const response = await fetchApi("/api/0/list");
@@ -55,7 +55,8 @@ export const getUsers = async () => {
* Get all devices for the provided users.
*
* @param {Array.<User>} users Array of usernames
* @return {Object.<User, Array.<Device>>} Object mapping each username to an array of device names
* @returns {Object.<User, Array.<Device>>}
* Object mapping each username to an array of device names
*/
export const getDevices = async users => {
const devices = {};
@@ -75,7 +76,7 @@ export const getDevices = async users => {
*
* @param {User} [user] Get last locations of all devices from this user
* @param {Device} [device] Get last location of specific device
* @return {Array.<LastLocation>} Array of last location objects
* @returns {Array.<LastLocation>} Array of last location objects
*/
export const getLastLocations = async (user, device) => {
const params = {};
@@ -97,7 +98,7 @@ export const getLastLocations = async (user, device) => {
* @param {Device} device Device name
* @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
* @returns {LocationHistory} Array of location history objects
*/
export const getUserDeviceLocationHistory = async (
user,
@@ -119,10 +120,12 @@ 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 {Object.<User, Array.<Device>>} devices
* Devices of which the history should be fetched
* @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
* @returns {Object.<User, Object.<Device, LocationHistory>>}
* Array of location history objects
*/
export const getLocationHistory = async (devices, start, end) => {
const locationHistory = {};
@@ -145,7 +148,8 @@ export const getLocationHistory = async (devices, start, end) => {
};
/**
* Connect to the WebSocket API, reconnect when necessary and handle received messages.
* Connect to the WebSocket API, reconnect when necessary and handle received
* messages.
*
* @param {webSocketLocationCallback} [callback] Callback for location messages
*/

View File

@@ -1,9 +1,13 @@
<template>
<LPopup>
<div v-if="name" class="device">{{ name }}</div>
<div v-else class="device">{{ user }}/{{ device }}</div>
<div class="device">{{ deviceName }}</div>
<div class="wrapper">
<img v-if="face" :src="faceImageDataURI" alt="" />
<img
v-if="face"
:src="faceImageDataURI"
:alt="$t('Image of {deviceName}', { deviceName })"
:title="$t('Image of {deviceName}', { deviceName })"
/>
<ul class="info-list">
<li :title="$t('Timestamp')">
<ClockIcon size="1x" aria-hidden="true" role="img" />
@@ -122,13 +126,23 @@ export default {
},
computed: {
/**
* Return the face image as a data URI string which can be used for an image's src attribute
* Return the face image as a data URI string which can be used for an
* image's src attribute.
*
* @return {String} base64-encoded face image data URI
* @returns {String} base64-encoded face image data URI
*/
faceImageDataURI() {
return `data:image/png;base64,${this.face}`;
},
/**
* Return the device name for displaying with <user identifier>/<device
* identifier> as fallback.
*
* @returns {String} device name for displaying
*/
deviceName() {
return this.name ? this.name : `${this.user}/${this.device}`;
},
},
};
</script>

View File

@@ -23,6 +23,7 @@
"OwnTracks documentation": "OwnTracks Dokumentation",
"OwnTracks on Twitter": "OwnTracks auf Twitter",
"Loading data, please wait...": "Daten werden geladen, bitte warten...",
"Image of {deviceName}": "Bild von {deviceName}",
"Timestamp": "Zeitstempel",
"Location": "Standort",
"Address": "Adresse",

View File

@@ -23,6 +23,7 @@
"OwnTracks documentation": "OwnTracks documentation",
"OwnTracks on Twitter": "OwnTracks on Twitter",
"Loading data, please wait...": "Loading data, please wait...",
"Image of {deviceName}": "Image of {deviceName}",
"Timestamp": "Timestamp",
"Location": "Location",
"Address": "Address",

View File

@@ -12,8 +12,9 @@ import { distanceBetweenCoordinates } from "@/util";
* array of all coordinates.
*
* @param {State} state
* @param {MultiLocationHistory} state.locationHistory Location history of selected users and devices
* @return {Array.<L.LatLng>} All coordinates
* @param {MultiLocationHistory} state.locationHistory
* Location history of selected users and devices
* @returns {Array.<L.LatLng>} All coordinates
*/
const locationHistoryLatLngs = state => {
const latLngs = [];
@@ -33,8 +34,9 @@ const locationHistoryLatLngs = state => {
* coordinates does not exceed `config.map.maxPointDistance`.
*
* @param {State} state
* @param {MultiLocationHistory} state.locationHistory Location history of selected users and devices
* @return {Array.<Array.<L.LatLng>>} Groups of coherent coordinates
* @param {MultiLocationHistory} state.locationHistory
* Location history of selected users and devices
* @returns {Array.<Array.<L.LatLng>>} Groups of coherent coordinates
*/
const locationHistoryLatLngGroups = state => {
const groups = [];

View File

@@ -1,3 +1,5 @@
/* eslint max-len: 0 */
/**
* A coordinate with latitude and longitude.
*

View File

@@ -10,7 +10,7 @@ import { DATE_TIME_FORMAT, EARTH_RADIUS_IN_KM } from "@/constants";
* base URL configuration into account.
*
* @param {String} path Path to the API resource
* @return {URL} Final API URL
* @returns {URL} Final API URL
*/
export const getApiUrl = path => {
const normalizedBaseUrl = config.api.baseUrl.endsWith("/")
@@ -24,7 +24,7 @@ export const getApiUrl = path => {
* 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 matches the expected format
* @returns {Boolean} Whether the input matches the expected format
*/
export const isIsoDateTime = s => moment(s, DATE_TIME_FORMAT, true).isValid();
@@ -32,7 +32,7 @@ export const isIsoDateTime = s => moment(s, DATE_TIME_FORMAT, true).isValid();
* Convert degrees to radians.
*
* @param {Number} degrees Angle in degrees
* @return {Number} Angle in radians
* @returns {Number} Angle in radians
*/
export const degreesToRadians = degrees => (degrees * Math.PI) / 180;
@@ -44,7 +44,7 @@ export const degreesToRadians = degrees => (degrees * Math.PI) / 180;
*
* @param {Coordinate} c1 First coordinate
* @param {Coordinate} c2 Second coordinate
* @return {Number} Distance in meters
* @returns {Number} Distance in meters
*/
export const distanceBetweenCoordinates = (c1, c2) => {
const r = EARTH_RADIUS_IN_KM * 1000;

View File

@@ -66,7 +66,11 @@
<template v-for="(userDevices, user) in locationHistory">
<template v-for="(deviceLocations, device) in userDevices">
<LCircleMarker
v-for="(l, n) in deviceLocations"
v-for="(l, n) in deviceLocationsWithNameAndFace(
user,
device,
deviceLocations
)"
:key="`${user}-${device}-${n}`"
:lat-lng="[l.lat, l.lon]"
v-bind="circleMarker"
@@ -74,6 +78,8 @@
<LDeviceLocationPopup
:user="user"
:device="device"
:name="l.name"
:face="l.face"
:timestamp="l.tst"
:lat="l.lat"
:lon="l.lon"
@@ -202,6 +208,29 @@ export default {
});
}
},
/**
* Find a the last location object for a user/device combination from the
* local cache and backfill name and face attributes to each item from the
* passed array of location objects.
*
* @param {User} user Username
* @param {Device} device Device name
* @param {LocationHistory} deviceLocations Device name
* @returns {LocationHistory} Updated location history
*/
deviceLocationsWithNameAndFace(user, device, deviceLocations) {
const lastLocation = this.lastLocations.find(
l => l.username === user && l.device === device
);
if (!lastLocation) {
return deviceLocations;
}
return deviceLocations.map(l => ({
...l,
name: lastLocation.name,
face: lastLocation.face,
}));
},
},
};
</script>