diff options
Diffstat (limited to 'web/app')
52 files changed, 484 insertions, 136 deletions
diff --git a/web/app/Application.js b/web/app/Application.js index d0b6713c..91bdc584 100644 --- a/web/app/Application.js +++ b/web/app/Application.js @@ -21,7 +21,8 @@ Ext.define('Traccar.Application', { requires: [ 'Traccar.Style', - 'Traccar.AttributeFormatter' + 'Traccar.AttributeFormatter', + 'Traccar.view.TouchFix62' ], models: [ @@ -51,6 +52,7 @@ Ext.define('Traccar.Application', { 'Devices', 'AllGroups', 'AllDevices', + 'AlarmTypes', 'Positions', 'LatestPositions', 'EventPositions', @@ -148,6 +150,19 @@ Ext.define('Traccar.Application', { } }, + updateNotificationToken: function (token) { + var attributes = Ext.clone(this.user.get('attributes')); + if (!attributes.notificationTokens || attributes.notificationTokens.indexOf(token) < 0) { + if (!attributes.notificationTokens) { + attributes.notificationTokens = token; + } else { + attributes.notificationTokens += ',' + token; + } + this.user.set('attributes', attributes); + this.user.save(); + } + }, + setUser: function (data) { var reader = Ext.create('Ext.data.reader.Json', { model: 'Traccar.model.User' diff --git a/web/app/GeofenceConverter.js b/web/app/GeofenceConverter.js index 4891e7b1..688f2233 100644 --- a/web/app/GeofenceConverter.js +++ b/web/app/GeofenceConverter.js @@ -45,7 +45,7 @@ Ext.define('Traccar.GeofenceConverter', { resolutionAtEquator = mapView.getResolution(); pointResolution = ol.proj.getPointResolution(projection, resolutionAtEquator, center); resolutionFactor = resolutionAtEquator / pointResolution; - radius = Number(coordinates[2]) / ol.proj.METERS_PER_UNIT.m * resolutionFactor; + radius = Number(coordinates[2]) / ol.proj.Units.METERS_PER_UNIT.m * resolutionFactor; geometry = new ol.geom.Circle(center, radius); } } @@ -68,15 +68,14 @@ Ext.define('Traccar.GeofenceConverter', { }, geometryToWkt: function (projection, geometry) { - var result, i, center, radius, edgeCoordinate, earthSphere, groundRadius, points; + var result, i, center, radius, edgeCoordinate, groundRadius, points; if (geometry instanceof ol.geom.Circle) { center = geometry.getCenter(); radius = geometry.getRadius(); edgeCoordinate = [center[0] + radius, center[1]]; center = ol.proj.transform(center, projection, 'EPSG:4326'); - earthSphere = new ol.Sphere(6378137); - groundRadius = earthSphere.haversineDistance(center, - ol.proj.transform(edgeCoordinate, projection, 'EPSG:4326')); + groundRadius = ol.sphere.getDistance( + center, ol.proj.transform(edgeCoordinate, projection, 'EPSG:4326'), 6378137); result = 'CIRCLE ('; result += center[1] + ' ' + center[0] + ', '; result += groundRadius.toFixed(1) + ')'; diff --git a/web/app/Style.js b/web/app/Style.js index 47c49a96..371e05a1 100644 --- a/web/app/Style.js +++ b/web/app/Style.js @@ -81,6 +81,9 @@ Ext.define('Traccar.Style', { mapGeofenceWidth: 5, mapGeofenceRadius: 9, + mapAnimateMarkerDuration: 2000, + mapAnimateMarkerTimeout: 40, + coordinatePrecision: 6, numberPrecision: 2, diff --git a/web/app/controller/Root.js b/web/app/controller/Root.js index 0edc049a..7c0345ad 100644 --- a/web/app/controller/Root.js +++ b/web/app/controller/Root.js @@ -109,6 +109,14 @@ Ext.define('Traccar.controller.Root', { loadApp: function () { var attribution, eventId; + + if (window.webkit && window.webkit.messageHandlers.appInterface) { + window.webkit.messageHandlers.appInterface.postMessage('login'); + } + if (window.appInterface) { + window.appInterface.postMessage('login'); + } + Ext.getStore('Groups').load(); Ext.getStore('Drivers').load(); Ext.getStore('Geofences').load(); diff --git a/web/app/model/ReportStop.js b/web/app/model/ReportStop.js index 1867f73b..9aaa58b0 100644 --- a/web/app/model/ReportStop.js +++ b/web/app/model/ReportStop.js @@ -34,6 +34,10 @@ Ext.define('Traccar.model.ReportStop', { type: 'date', dateFormat: 'c' }, { + name: 'startOdometer', + type: 'float', + convert: Traccar.AttributeFormatter.getConverter('distance') + }, { name: 'address', type: 'string' }, { diff --git a/web/app/model/ReportSummary.js b/web/app/model/ReportSummary.js index 4bc78731..dcc0365c 100644 --- a/web/app/model/ReportSummary.js +++ b/web/app/model/ReportSummary.js @@ -39,6 +39,14 @@ Ext.define('Traccar.model.ReportSummary', { type: 'float', convert: Traccar.AttributeFormatter.getConverter('distance') }, { + name: 'startOdometer', + type: 'float', + convert: Traccar.AttributeFormatter.getConverter('distance') + }, { + name: 'endOdometer', + type: 'float', + convert: Traccar.AttributeFormatter.getConverter('distance') + }, { name: 'engineHours', type: 'int' }, { diff --git a/web/app/model/ReportTrip.js b/web/app/model/ReportTrip.js index 9004b86d..9d45fc87 100644 --- a/web/app/model/ReportTrip.js +++ b/web/app/model/ReportTrip.js @@ -39,6 +39,14 @@ Ext.define('Traccar.model.ReportTrip', { type: 'float', convert: Traccar.AttributeFormatter.getConverter('distance') }, { + name: 'startOdometer', + type: 'float', + convert: Traccar.AttributeFormatter.getConverter('distance') + }, { + name: 'endOdometer', + type: 'float', + convert: Traccar.AttributeFormatter.getConverter('distance') + }, { name: 'duration', type: 'int' }, { diff --git a/web/app/store/AlarmTypes.js b/web/app/store/AlarmTypes.js new file mode 100644 index 00000000..1ee7ffe8 --- /dev/null +++ b/web/app/store/AlarmTypes.js @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +Ext.define('Traccar.store.AlarmTypes', { + extend: 'Ext.data.Store', + fields: ['key', 'name'], + + data: (function () { + var key, items = []; + for (key in Strings) { + if (Strings.hasOwnProperty(key) && key.lastIndexOf('alarm', 0) === 0) { + items.push({ + key: key.charAt(5).toLowerCase() + key.slice(6), + name: Strings[key] + }); + } + } + return items; + })() +}); diff --git a/web/app/store/AllMaintenances.js b/web/app/store/AllMaintenances.js index 745b5d64..8435ad40 100644 --- a/web/app/store/AllMaintenances.js +++ b/web/app/store/AllMaintenances.js @@ -22,7 +22,7 @@ Ext.define('Traccar.store.AllMaintenances', { proxy: { type: 'rest', - url: 'api/maintenances', + url: 'api/maintenance', extraParams: { all: true } diff --git a/web/app/store/CommonUserAttributes.js b/web/app/store/CommonUserAttributes.js index 0ee9e163..a8e92825 100644 --- a/web/app/store/CommonUserAttributes.js +++ b/web/app/store/CommonUserAttributes.js @@ -63,8 +63,8 @@ Ext.define('Traccar.store.CommonUserAttributes', { name: Strings.attributeUiDisableCalendars, valueType: 'boolean' }, { - key: 'ui.disableMaintenances', - name: Strings.attributeUiDisableMaintenances, + key: 'ui.disableMaintenance', + name: Strings.attributeUiDisableMaintenance, valueType: 'boolean' }, { key: 'ui.hidePositionAttributes', diff --git a/web/app/store/KnownCommands.js b/web/app/store/KnownCommands.js index 8bf2e24e..1e82f2b8 100644 --- a/web/app/store/KnownCommands.js +++ b/web/app/store/KnownCommands.js @@ -39,7 +39,7 @@ Ext.define('Traccar.store.KnownCommands', { }, { type: 'setTimezone', parameters: [{ - key: 'timezoneName', + key: 'timezone', name: Strings.commandTimezone, valueType: 'string', dataType: 'timezone' @@ -56,6 +56,13 @@ Ext.define('Traccar.store.KnownCommands', { valueType: 'string' }] }, { + type: 'message', + parameters: [{ + key: 'message', + name: Strings.commandMessage, + valueType: 'string' + }] + }, { type: 'sendUssd', parameters: [{ key: 'phone', diff --git a/web/app/store/Maintenances.js b/web/app/store/Maintenances.js index 43d6ba5b..a7aa4a07 100644 --- a/web/app/store/Maintenances.js +++ b/web/app/store/Maintenances.js @@ -22,7 +22,7 @@ Ext.define('Traccar.store.Maintenances', { proxy: { type: 'rest', - url: 'api/maintenances', + url: 'api/maintenance', writer: { writeAllFields: true } diff --git a/web/app/store/MapTypes.js b/web/app/store/MapTypes.js index 3d322438..88d54bfa 100644 --- a/web/app/store/MapTypes.js +++ b/web/app/store/MapTypes.js @@ -49,5 +49,8 @@ Ext.define('Traccar.store.MapTypes', { }, { key: 'custom', name: Strings.mapCustom + }, { + key: 'customArcgis', + name: Strings.mapCustomArcgis }] }); diff --git a/web/app/view/DeviceMenu.js b/web/app/view/DeviceMenu.js index e4623b94..06b272ad 100644 --- a/web/app/view/DeviceMenu.js +++ b/web/app/view/DeviceMenu.js @@ -57,7 +57,7 @@ Ext.define('Traccar.view.DeviceMenu', { handler: 'onCommandsClick', reference: 'menuCommandsButton' }, { - text: Strings.sharedMaintenances, + text: Strings.sharedMaintenance, glyph: 'xf0ad@FontAwesome', handler: 'onMaintenancesClick', reference: 'menuMaintenancesButton' diff --git a/web/app/view/DeviceMenuController.js b/web/app/view/DeviceMenuController.js index 9355571c..830ea7ec 100644 --- a/web/app/view/DeviceMenuController.js +++ b/web/app/view/DeviceMenuController.js @@ -40,7 +40,7 @@ Ext.define('Traccar.view.DeviceMenuController', { this.lookupReference('menuDeviceAccumulatorsButton').setHidden( !Traccar.app.getUser().get('administrator') && Traccar.app.getUser().get('userLimit') === 0 || Traccar.app.getVehicleFeaturesDisabled()); this.lookupReference('menuMaintenancesButton').setHidden( - Traccar.app.getVehicleFeaturesDisabled() || Traccar.app.getBooleanAttributePreference('ui.disableMaintenances')); + Traccar.app.getVehicleFeaturesDisabled() || Traccar.app.getBooleanAttributePreference('ui.disableMaintenance')); }, onGeofencesClick: function () { @@ -110,7 +110,7 @@ Ext.define('Traccar.view.DeviceMenuController', { onMaintenancesClick: function () { Ext.create('Traccar.view.BaseWindow', { - title: Strings.sharedMaintenances, + title: Strings.sharedMaintenance, items: { xtype: 'linkMaintenancesView', baseObjectName: 'deviceId', diff --git a/web/app/view/EventsController.js b/web/app/view/EventsController.js index 8eb4363f..e11b4ff6 100644 --- a/web/app/view/EventsController.js +++ b/web/app/view/EventsController.js @@ -24,7 +24,11 @@ Ext.define('Traccar.view.EventsController', { listen: { controller: { '*': { - deselectevent: 'deselectEvent' + selectdevice: 'deselectEvent', + selectreport: 'deselectEvent' + }, + 'map': { + deselectfeature: 'deselectFeature' } }, store: { @@ -79,7 +83,14 @@ Ext.define('Traccar.view.EventsController', { Traccar.app.showEvents(false); }, - deselectEvent: function () { + + deselectEvent: function (object) { + if (object) { + this.deselectFeature(); + } + }, + + deselectFeature: function () { this.getView().getSelectionModel().deselectAll(); }, diff --git a/web/app/view/Report.js b/web/app/view/Report.js index f77cfca6..deedb747 100644 --- a/web/app/view/Report.js +++ b/web/app/view/Report.js @@ -63,6 +63,11 @@ Ext.define('Traccar.view.Report', { disabled: true, handler: 'onReportClick' }, { + text: Strings.reportEmail, + reference: 'emailButton', + disabled: true, + handler: 'onReportClick' + }, { text: Strings.reportClear, handler: 'onClearClick' }] diff --git a/web/app/view/ReportController.js b/web/app/view/ReportController.js index 72e7e37e..2f10cde1 100644 --- a/web/app/view/ReportController.js +++ b/web/app/view/ReportController.js @@ -32,12 +32,13 @@ Ext.define('Traccar.view.ReportController', { listen: { controller: { '*': { - selectdevice: 'selectDevice', - showsingleevent: 'showSingleEvent', - deselectfeature: 'deselectFeature' + selectdevice: 'deselectReport', + selectevent: 'deselectReport', + showsingleevent: 'showSingleEvent' }, 'map': { - selectreport: 'selectReport' + selectreport: 'selectReport', + deselectfeature: 'deselectFeature' } }, global: { @@ -145,6 +146,7 @@ Ext.define('Traccar.view.ReportController', { disabled = !reportType || !devices || !time || this.reportProgress; this.lookupReference('showButton').setDisabled(disabled); this.lookupReference('exportButton').setDisabled(reportType === 'chart' || disabled); + this.lookupReference('emailButton').setDisabled(reportType === 'chart' || disabled); }, onReportClick: function (button) { @@ -187,14 +189,15 @@ Ext.define('Traccar.view.ReportController', { to: to.toISOString() } }); - } else if (button.reference === 'exportButton') { + } else { url = this.getGrid().getStore().getProxy().url; - this.downloadFile(url, { + this.excelReport(url, { deviceId: this.deviceId, groupId: this.groupId, type: this.eventType, from: Ext.Date.format(from, 'c'), - to: Ext.Date.format(to, 'c') + to: Ext.Date.format(to, 'c'), + mail: button.reference === 'emailButton' }); } } @@ -229,12 +232,6 @@ Ext.define('Traccar.view.ReportController', { } }, - selectDevice: function (device) { - if (device) { - this.getGrid().getSelectionModel().deselectAll(); - } - }, - selectReport: function (object) { var positionRelated, reportType = this.lookupReference('reportTypeField').getValue(); if (object instanceof Traccar.model.Position) { @@ -249,6 +246,12 @@ Ext.define('Traccar.view.ReportController', { } }, + deselectReport: function (object) { + if (object) { + this.deselectFeature(); + } + }, + deselectFeature: function () { if (this.lookupReference('reportTypeField').getValue() !== 'trips') { this.getGrid().getSelectionModel().deselectAll(); @@ -371,7 +374,7 @@ Ext.define('Traccar.view.ReportController', { }); }, - downloadFile: function (requestUrl, requestParams) { + excelReport: function (requestUrl, requestParams) { Ext.Ajax.request({ url: requestUrl, method: 'GET', @@ -384,7 +387,7 @@ Ext.define('Traccar.view.ReportController', { scope: this, callback: function (options, success, response) { var disposition, filename, type, blob, url, downloadUrl; - if (success) { + if (success && !requestParams.mail) { disposition = response.getResponseHeader('Content-Disposition'); filename = disposition.slice(disposition.indexOf('=') + 1, disposition.length); type = response.getResponseHeader('Content-Type'); @@ -539,6 +542,14 @@ Ext.define('Traccar.view.ReportController', { dataIndex: 'distance', renderer: Traccar.AttributeFormatter.getFormatter('distance') }, { + text: Strings.reportStartOdometer, + dataIndex: 'startOdometer', + renderer: Traccar.AttributeFormatter.getFormatter('distance') + }, { + text: Strings.reportEndOdometer, + dataIndex: 'endOdometer', + renderer: Traccar.AttributeFormatter.getFormatter('distance') + }, { text: Strings.reportAverageSpeed, dataIndex: 'averageSpeed', renderer: Traccar.AttributeFormatter.getFormatter('speed') @@ -566,6 +577,10 @@ Ext.define('Traccar.view.ReportController', { xtype: 'datecolumn', renderer: Traccar.AttributeFormatter.getFormatter('startTime') }, { + text: Strings.reportStartOdometer, + dataIndex: 'startOdometer', + renderer: Traccar.AttributeFormatter.getFormatter('distance') + }, { text: Strings.reportStartAddress, dataIndex: 'startAddress', renderer: Traccar.AttributeFormatter.getFormatter('address') @@ -575,6 +590,10 @@ Ext.define('Traccar.view.ReportController', { xtype: 'datecolumn', renderer: Traccar.AttributeFormatter.getFormatter('endTime') }, { + text: Strings.reportEndOdometer, + dataIndex: 'endOdometer', + renderer: Traccar.AttributeFormatter.getFormatter('distance') + }, { text: Strings.reportEndAddress, dataIndex: 'endAddress', renderer: Traccar.AttributeFormatter.getFormatter('address') @@ -614,6 +633,10 @@ Ext.define('Traccar.view.ReportController', { xtype: 'datecolumn', renderer: Traccar.AttributeFormatter.getFormatter('startTime') }, { + text: Strings.positionOdometer, + dataIndex: 'startOdometer', + renderer: Traccar.AttributeFormatter.getFormatter('distance') + }, { text: Strings.positionAddress, dataIndex: 'address', renderer: Traccar.AttributeFormatter.getFormatter('address') diff --git a/web/app/view/SettingsMenu.js b/web/app/view/SettingsMenu.js index 309133c1..0a81d520 100644 --- a/web/app/view/SettingsMenu.js +++ b/web/app/view/SettingsMenu.js @@ -98,7 +98,7 @@ Ext.define('Traccar.view.SettingsMenu', { reference: 'settingsCommandsButton' }, { hidden: true, - text: Strings.sharedMaintenances, + text: Strings.sharedMaintenance, glyph: 'xf0ad@FontAwesome', handler: 'onMaintenancesClick', reference: 'settingsMaintenancesButton' diff --git a/web/app/view/SettingsMenuController.js b/web/app/view/SettingsMenuController.js index 37e0634c..c8018f66 100644 --- a/web/app/view/SettingsMenuController.js +++ b/web/app/view/SettingsMenuController.js @@ -37,14 +37,15 @@ Ext.define('Traccar.view.SettingsMenuController', { ], init: function () { - var admin, manager, readonly, deviceReadonly; + var admin, manager, readonly; admin = Traccar.app.getUser().get('administrator'); manager = Traccar.app.getUser().get('userLimit') !== 0; readonly = Traccar.app.getPreference('readonly', false); - deviceReadonly = Traccar.app.getUser().get('deviceReadonly'); if (admin) { this.lookupReference('settingsServerButton').setHidden(false); this.lookupReference('settingsStatisticsButton').setHidden(false); + this.lookupReference('settingsComputedAttributesButton').setHidden( + Traccar.app.getBooleanAttributePreference('ui.disableComputedAttributes')); } if (admin || manager) { this.lookupReference('settingsUsersButton').setHidden(false); @@ -60,11 +61,7 @@ Ext.define('Traccar.view.SettingsMenuController', { Traccar.app.getVehicleFeaturesDisabled() || Traccar.app.getBooleanAttributePreference('ui.disableDrivers')); this.lookupReference('settingsCommandsButton').setHidden(Traccar.app.getPreference('limitCommands', false)); this.lookupReference('settingsMaintenancesButton').setHidden( - Traccar.app.getVehicleFeaturesDisabled() || Traccar.app.getBooleanAttributePreference('ui.disableMaintenances')); - } - if (admin || !deviceReadonly && !readonly) { - this.lookupReference('settingsComputedAttributesButton').setHidden( - Traccar.app.getBooleanAttributePreference('ui.disableComputedAttributes')); + Traccar.app.getVehicleFeaturesDisabled() || Traccar.app.getBooleanAttributePreference('ui.disableMaintenance')); } }, @@ -166,7 +163,7 @@ Ext.define('Traccar.view.SettingsMenuController', { onMaintenancesClick: function () { Ext.create('Traccar.view.BaseWindow', { - title: Strings.sharedMaintenances, + title: Strings.sharedMaintenance, items: { xtype: 'maintenancesView' } diff --git a/web/app/view/StateController.js b/web/app/view/StateController.js index 6363fc9c..1ab1aa2f 100644 --- a/web/app/view/StateController.js +++ b/web/app/view/StateController.js @@ -32,7 +32,8 @@ Ext.define('Traccar.view.StateController', { controller: { '*': { selectdevice: 'selectDevice', - selectreport: 'selectReport', + selectreport: 'selectPosition', + selectevent: 'selectPosition', deselectfeature: 'deselectFeature' } }, @@ -167,7 +168,7 @@ Ext.define('Traccar.view.StateController', { } }, - selectReport: function (position) { + selectPosition: function (position) { if (position instanceof Traccar.model.Position) { this.deviceId = null; this.position = position; diff --git a/web/app/view/Statistics.js b/web/app/view/Statistics.js index f033e03a..af199a02 100644 --- a/web/app/view/Statistics.js +++ b/web/app/view/Statistics.js @@ -77,10 +77,10 @@ Ext.define('Traccar.view.Statistics', { text: Strings.statisticsMessagesStored, dataIndex: 'messagesStored' }, { - text: Strings.notificationMail, + text: Strings.notificatorMail, dataIndex: 'mailSent' }, { - text: Strings.notificationSms, + text: Strings.notificatorSms, dataIndex: 'smsSent' }, { text: Strings.statisticsGeocoder, diff --git a/web/app/view/TouchFix62.js b/web/app/view/TouchFix62.js new file mode 100644 index 00000000..300d4f70 --- /dev/null +++ b/web/app/view/TouchFix62.js @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Workaround for bug in ExtJs 6.2.0. + * Resolved in current yet unreleased version + */ + +Ext.define('Traccar.view.TouchFix62', { + override: 'Ext.dom.Element' +}, +function () { + var additiveEvents = this.prototype.additiveEvents, + eventMap = this.prototype.eventMap; + if (Ext.supports.TouchEvents && Ext.firefoxVersion >= 52 && Ext.os.is.Desktop) { + eventMap['touchstart'] = 'mousedown'; + eventMap['touchmove'] = 'mousemove'; + eventMap['touchend'] = 'mouseup'; + eventMap['touchcancel'] = 'mouseup'; + eventMap['click'] = 'click'; + eventMap['dblclick'] = 'dblclick'; + additiveEvents['mousedown'] = 'mousedown'; + additiveEvents['mousemove'] = 'mousemove'; + additiveEvents['mouseup'] = 'mouseup'; + additiveEvents['touchstart'] = 'touchstart'; + additiveEvents['touchmove'] = 'touchmove'; + additiveEvents['touchend'] = 'touchend'; + additiveEvents['touchcancel'] = 'touchcancel'; + additiveEvents['pointerdown'] = 'mousedown'; + additiveEvents['pointermove'] = 'mousemove'; + additiveEvents['pointerup'] = 'mouseup'; + additiveEvents['pointercancel'] = 'mouseup'; + } +}); diff --git a/web/app/view/UnescapedTextAreaField.js b/web/app/view/UnescapedTextAreaField.js new file mode 100644 index 00000000..5de267ce --- /dev/null +++ b/web/app/view/UnescapedTextAreaField.js @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +Ext.define('Traccar.view.UnescapedTextAreaField', { + extend: 'Ext.form.field.TextArea', + xtype: 'unescapedTextAreaField', + + initComponent: function () { + this.callParent(); + this.on('change', this.onValueChange); + }, + + onValueChange: function (field, newValue) { + field.setValue(Ext.String.htmlDecode(newValue)); + } +}); diff --git a/web/app/view/UnescapedTextField.js b/web/app/view/UnescapedTextField.js new file mode 100644 index 00000000..3b1b2798 --- /dev/null +++ b/web/app/view/UnescapedTextField.js @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +Ext.define('Traccar.view.UnescapedTextField', { + extend: 'Ext.form.field.Text', + xtype: 'unescapedTextField', + + initComponent: function () { + this.callParent(); + this.on('change', this.onValueChange); + }, + + onValueChange: function (field, newValue) { + field.setValue(Ext.String.htmlDecode(newValue)); + } +}); diff --git a/web/app/view/dialog/Attribute.js b/web/app/view/dialog/Attribute.js index 2a458977..a85cad05 100644 --- a/web/app/view/dialog/Attribute.js +++ b/web/app/view/dialog/Attribute.js @@ -21,7 +21,8 @@ Ext.define('Traccar.view.dialog.Attribute', { requires: [ 'Traccar.view.dialog.AttributeController', 'Traccar.view.ColorPicker', - 'Traccar.view.CustomNumberField' + 'Traccar.view.CustomNumberField', + 'Traccar.view.UnescapedTextField' ], controller: 'attribute', @@ -33,7 +34,7 @@ Ext.define('Traccar.view.dialog.Attribute', { validitychange: 'onValidityChange' }, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', reference: 'nameTextField', name: 'name', allowBlank: false, diff --git a/web/app/view/dialog/Calendar.js b/web/app/view/dialog/Calendar.js index 9880d4e6..5f00a8be 100644 --- a/web/app/view/dialog/Calendar.js +++ b/web/app/view/dialog/Calendar.js @@ -20,7 +20,8 @@ Ext.define('Traccar.view.dialog.Calendar', { extend: 'Traccar.view.dialog.BaseEdit', requires: [ - 'Traccar.view.dialog.CalendarController' + 'Traccar.view.dialog.CalendarController', + 'Traccar.view.UnescapedTextField' ], controller: 'calendar', @@ -32,7 +33,7 @@ Ext.define('Traccar.view.dialog.Calendar', { xtype: 'fieldset', title: Strings.sharedRequired, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'name', fieldLabel: Strings.sharedName, allowBlank: false diff --git a/web/app/view/dialog/ComputedAttribute.js b/web/app/view/dialog/ComputedAttribute.js index ef6269be..adae7f7b 100644 --- a/web/app/view/dialog/ComputedAttribute.js +++ b/web/app/view/dialog/ComputedAttribute.js @@ -20,7 +20,9 @@ Ext.define('Traccar.view.dialog.ComputedAttribute', { extend: 'Traccar.view.dialog.BaseEdit', requires: [ - 'Traccar.view.dialog.ComputedAttributeController' + 'Traccar.view.dialog.ComputedAttributeController', + 'Traccar.view.UnescapedTextField', + 'Traccar.view.UnescapedTextAreaField' ], controller: 'computedAttribute', @@ -29,7 +31,7 @@ Ext.define('Traccar.view.dialog.ComputedAttribute', { items: { xtype: 'form', items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'description', fieldLabel: Strings.sharedDescription }, { @@ -43,7 +45,8 @@ Ext.define('Traccar.view.dialog.ComputedAttribute', { change: 'onAttributeChange' } }, { - xtype: 'textareafield', + xtype: 'unescapedTextAreaField', + reference: 'expressionField', name: 'expression', fieldLabel: Strings.sharedExpression, allowBlank: false diff --git a/web/app/view/dialog/Device.js b/web/app/view/dialog/Device.js index 50a5e79c..60a8f716 100644 --- a/web/app/view/dialog/Device.js +++ b/web/app/view/dialog/Device.js @@ -20,7 +20,8 @@ Ext.define('Traccar.view.dialog.Device', { requires: [ 'Traccar.view.ClearableComboBox', - 'Traccar.view.dialog.DeviceController' + 'Traccar.view.dialog.DeviceController', + 'Traccar.view.UnescapedTextField' ], controller: 'device', @@ -32,12 +33,12 @@ Ext.define('Traccar.view.dialog.Device', { xtype: 'fieldset', title: Strings.sharedRequired, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'name', fieldLabel: Strings.sharedName, allowBlank: false }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'uniqueId', fieldLabel: Strings.deviceIdentifier, allowBlank: false @@ -56,15 +57,15 @@ Ext.define('Traccar.view.dialog.Device', { displayField: 'name', valueField: 'id' }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'phone', fieldLabel: Strings.sharedPhone }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'model', fieldLabel: Strings.deviceModel }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'contact', fieldLabel: Strings.deviceContact }, { diff --git a/web/app/view/dialog/Driver.js b/web/app/view/dialog/Driver.js index b67e182d..9b1c17b5 100644 --- a/web/app/view/dialog/Driver.js +++ b/web/app/view/dialog/Driver.js @@ -19,6 +19,10 @@ Ext.define('Traccar.view.dialog.Driver', { extend: 'Traccar.view.dialog.BaseEdit', + requires: [ + 'Traccar.view.UnescapedTextField' + ], + title: Strings.sharedDriver, items: { @@ -27,12 +31,12 @@ Ext.define('Traccar.view.dialog.Driver', { xtype: 'fieldset', title: Strings.sharedRequired, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'name', fieldLabel: Strings.sharedName, allowBlank: false }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'uniqueId', fieldLabel: Strings.deviceIdentifier, allowBlank: false diff --git a/web/app/view/dialog/Geofence.js b/web/app/view/dialog/Geofence.js index 65c32c04..1e22cd7b 100644 --- a/web/app/view/dialog/Geofence.js +++ b/web/app/view/dialog/Geofence.js @@ -20,7 +20,8 @@ Ext.define('Traccar.view.dialog.Geofence', { requires: [ 'Traccar.view.ClearableComboBox', - 'Traccar.view.dialog.GeofenceController' + 'Traccar.view.dialog.GeofenceController', + 'Traccar.view.UnescapedTextField' ], controller: 'geofence', @@ -32,7 +33,7 @@ Ext.define('Traccar.view.dialog.Geofence', { xtype: 'fieldset', title: Strings.sharedRequired, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'name', fieldLabel: Strings.sharedName }] @@ -42,7 +43,7 @@ Ext.define('Traccar.view.dialog.Geofence', { collapsible: true, collapsed: true, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'description', fieldLabel: Strings.sharedDescription }, { diff --git a/web/app/view/dialog/Group.js b/web/app/view/dialog/Group.js index 805b422c..61ca193d 100644 --- a/web/app/view/dialog/Group.js +++ b/web/app/view/dialog/Group.js @@ -19,7 +19,8 @@ Ext.define('Traccar.view.dialog.Group', { extend: 'Traccar.view.dialog.BaseEdit', requires: [ - 'Traccar.view.ClearableComboBox' + 'Traccar.view.ClearableComboBox', + 'Traccar.view.UnescapedTextField' ], title: Strings.groupDialog, @@ -30,7 +31,7 @@ Ext.define('Traccar.view.dialog.Group', { xtype: 'fieldset', title: Strings.sharedRequired, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'name', fieldLabel: Strings.sharedName, allowBlank: false diff --git a/web/app/view/dialog/Maintenance.js b/web/app/view/dialog/Maintenance.js index 5705278f..d844d259 100644 --- a/web/app/view/dialog/Maintenance.js +++ b/web/app/view/dialog/Maintenance.js @@ -21,7 +21,8 @@ Ext.define('Traccar.view.dialog.Maintenance', { requires: [ 'Traccar.view.dialog.MaintenanceController', - 'Traccar.view.CustomNumberField' + 'Traccar.view.CustomNumberField', + 'Traccar.view.UnescapedTextField' ], controller: 'maintenance', @@ -37,7 +38,7 @@ Ext.define('Traccar.view.dialog.Maintenance', { xtype: 'fieldset', title: Strings.sharedRequired, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'name', fieldLabel: Strings.sharedName, allowBlank: false diff --git a/web/app/view/dialog/Notification.js b/web/app/view/dialog/Notification.js index dc4362d9..51af5b8e 100644 --- a/web/app/view/dialog/Notification.js +++ b/web/app/view/dialog/Notification.js @@ -41,7 +41,10 @@ Ext.define('Traccar.view.dialog.Notification', { displayField: 'name', valueField: 'type', editable: false, - allowBlank: false + allowBlank: false, + listeners: { + change: 'onTypeChange' + } }, { xtype: 'checkboxfield', inputValue: true, @@ -49,8 +52,22 @@ Ext.define('Traccar.view.dialog.Notification', { name: 'always', fieldLabel: Strings.notificationAlways }, { - fieldLabel: Strings.notificationNotificators, xtype: 'tagfield', + reference: 'alarmsField', + fieldLabel: Strings.sharedAlarms, + maxWidth: Traccar.Style.formFieldWidth, + store: 'AlarmTypes', + valueField: 'key', + displayField: 'name', + queryMode: 'local', + hidden: true, + listeners: { + beforerender: 'onAlarmsLoad', + change: 'onAlarmsChange' + } + }, { + xtype: 'tagfield', + fieldLabel: Strings.notificationNotificators, name: 'notificators', maxWidth: Traccar.Style.formFieldWidth, store: 'AllNotificators', diff --git a/web/app/view/dialog/NotificationController.js b/web/app/view/dialog/NotificationController.js index ad65c333..5da669a4 100644 --- a/web/app/view/dialog/NotificationController.js +++ b/web/app/view/dialog/NotificationController.js @@ -23,5 +23,31 @@ Ext.define('Traccar.view.dialog.NotificationController', { init: function () { this.lookupReference('calendarCombo').setHidden( Traccar.app.getBooleanAttributePreference('ui.disableCalendars')); + }, + + onTypeChange: function (view, value) { + this.lookupReference('alarmsField').setHidden(value !== 'alarm'); + }, + + onAlarmsLoad: function (view) { + var attributes, record = view.up('form').getRecord(); + attributes = record.get('attributes') || {}; + if (attributes['alarms']) { + view.suspendEvents(false); + view.setValue(attributes['alarms'].split(',')); + view.resumeEvents(); + } + }, + + onAlarmsChange: function (view, value) { + var attributes, record = view.up('window').down('form').getRecord(); + attributes = record.get('attributes') || {}; + + value = value.join(); + if (attributes['alarms'] !== value) { + attributes['alarms'] = value; + record.set('attributes', attributes); + record.dirty = true; + } } }); diff --git a/web/app/view/dialog/SavedCommand.js b/web/app/view/dialog/SavedCommand.js index 4759143a..b1aeae73 100644 --- a/web/app/view/dialog/SavedCommand.js +++ b/web/app/view/dialog/SavedCommand.js @@ -19,7 +19,8 @@ Ext.define('Traccar.view.dialog.SavedCommand', { extend: 'Traccar.view.dialog.BaseEdit', requires: [ - 'Traccar.view.dialog.SavedCommandController' + 'Traccar.view.dialog.SavedCommandController', + 'Traccar.view.UnescapedTextField' ], controller: 'savedCommand', @@ -34,7 +35,7 @@ Ext.define('Traccar.view.dialog.SavedCommand', { xtype: 'fieldset', title: Strings.sharedRequired, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'description', fieldLabel: Strings.sharedDescription }, { @@ -42,7 +43,7 @@ Ext.define('Traccar.view.dialog.SavedCommand', { name: 'textChannel', inputValue: true, uncheckedValue: false, - fieldLabel: Strings.notificationSms + fieldLabel: Strings.commandSendSms }, { xtype: 'combobox', name: 'type', diff --git a/web/app/view/dialog/SendCommand.js b/web/app/view/dialog/SendCommand.js index 9e07cbf3..79954739 100644 --- a/web/app/view/dialog/SendCommand.js +++ b/web/app/view/dialog/SendCommand.js @@ -54,7 +54,7 @@ Ext.define('Traccar.view.dialog.SendCommand', { reference: 'textChannelCheckBox', inputValue: true, uncheckedValue: false, - fieldLabel: Strings.notificationSms, + fieldLabel: Strings.commandSendSms, listeners: { change: 'onTextChannelChange' } diff --git a/web/app/view/dialog/Server.js b/web/app/view/dialog/Server.js index fbfb3c6a..b72dbb0c 100644 --- a/web/app/view/dialog/Server.js +++ b/web/app/view/dialog/Server.js @@ -20,7 +20,8 @@ Ext.define('Traccar.view.dialog.Server', { requires: [ 'Traccar.view.ClearableComboBox', - 'Traccar.view.dialog.MapPickerController' + 'Traccar.view.dialog.MapPickerController', + 'Traccar.view.UnescapedTextField' ], controller: 'mapPicker', @@ -39,13 +40,14 @@ Ext.define('Traccar.view.dialog.Server', { displayField: 'name', valueField: 'key' }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'bingKey', fieldLabel: Strings.mapBingKey }, { - xtype: 'textfield', + xtype: 'unescapedTextField', + reference: 'mapUrlField', name: 'mapUrl', - fieldLabel: Strings.mapCustom + fieldLabel: Strings.mapCustomLabel }, { xtype: 'numberfield', reference: 'latitude', @@ -83,7 +85,7 @@ Ext.define('Traccar.view.dialog.Server', { displayField: 'name', valueField: 'key' }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'poiLayer', fieldLabel: Strings.mapPoiLayer }] diff --git a/web/app/view/dialog/User.js b/web/app/view/dialog/User.js index 86f91672..917f110c 100644 --- a/web/app/view/dialog/User.js +++ b/web/app/view/dialog/User.js @@ -20,7 +20,8 @@ Ext.define('Traccar.view.dialog.User', { requires: [ 'Traccar.view.ClearableComboBox', - 'Traccar.view.dialog.UserController' + 'Traccar.view.dialog.UserController', + 'Traccar.view.UnescapedTextField' ], controller: 'user', @@ -32,11 +33,11 @@ Ext.define('Traccar.view.dialog.User', { xtype: 'fieldset', title: Strings.sharedRequired, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'name', fieldLabel: Strings.sharedName }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'email', fieldLabel: Strings.userEmail, allowBlank: false @@ -53,7 +54,7 @@ Ext.define('Traccar.view.dialog.User', { collapsible: true, collapsed: true, items: [{ - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'phone', fieldLabel: Strings.sharedPhone }, { @@ -94,7 +95,7 @@ Ext.define('Traccar.view.dialog.User', { displayField: 'name', valueField: 'key' }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'poiLayer', fieldLabel: Strings.mapPoiLayer }] @@ -164,7 +165,7 @@ Ext.define('Traccar.view.dialog.User', { disabled: true, reference: 'userLimitField' }, { - xtype: 'textfield', + xtype: 'unescapedTextField', name: 'token', reference: 'tokenField', fieldLabel: Strings.userToken, diff --git a/web/app/view/edit/DevicesController.js b/web/app/view/edit/DevicesController.js index 2b4ff14f..16e54b21 100644 --- a/web/app/view/edit/DevicesController.js +++ b/web/app/view/edit/DevicesController.js @@ -35,7 +35,8 @@ Ext.define('Traccar.view.edit.DevicesController', { listen: { controller: { '*': { - selectreport: 'selectReport' + selectreport: 'deselectDevice', + selectevent: 'deselectDevice' }, 'root': { selectdevice: 'selectDevice' @@ -109,12 +110,10 @@ Ext.define('Traccar.view.edit.DevicesController', { this.lookupReference('deviceCommandButton').setDisabled(empty || readonly); }, - onSelectionChange: function (selection, selected) { - this.updateButtons(selected); - if (selected.length > 0) { - this.fireEvent('selectdevice', selected[0], true); - } else { - this.fireEvent('deselectfeature'); + onSelectionChange: function (el, records) { + if (records && records.length) { + this.updateButtons(records); + this.fireEvent('selectdevice', records[0], true); } }, @@ -124,8 +123,8 @@ Ext.define('Traccar.view.edit.DevicesController', { this.getView().getView().focusRow(device); }, - selectReport: function (position) { - if (position !== undefined) { + deselectDevice: function (object) { + if (object) { this.deselectFeature(); } }, diff --git a/web/app/view/edit/Drivers.js b/web/app/view/edit/Drivers.js index 9aac8cd6..7bd10a68 100644 --- a/web/app/view/edit/Drivers.js +++ b/web/app/view/edit/Drivers.js @@ -46,7 +46,7 @@ Ext.define('Traccar.view.edit.Drivers', { dataIndex: 'name', filter: 'string' }, { - text: Strings.sharedDescription, + text: Strings.deviceIdentifier, dataIndex: 'uniqueId', filter: 'string' }] diff --git a/web/app/view/edit/Groups.js b/web/app/view/edit/Groups.js index 06974201..8b09316c 100644 --- a/web/app/view/edit/Groups.js +++ b/web/app/view/edit/Groups.js @@ -76,7 +76,7 @@ Ext.define('Traccar.view.edit.Groups', { handler: 'onMaintenancesClick', reference: 'toolbarMaintenancesButton', glyph: 'xf0ad@FontAwesome', - tooltip: Strings.sharedMaintenances, + tooltip: Strings.sharedMaintenance, tooltipType: 'title' }] }, diff --git a/web/app/view/edit/GroupsController.js b/web/app/view/edit/GroupsController.js index 2e62a283..ae96a248 100644 --- a/web/app/view/edit/GroupsController.js +++ b/web/app/view/edit/GroupsController.js @@ -41,7 +41,7 @@ Ext.define('Traccar.view.edit.GroupsController', { Traccar.app.getBooleanAttributePreference('ui.disableComputedAttributes')); this.lookupReference('toolbarCommandsButton').setHidden(Traccar.app.getPreference('limitCommands', false)); this.lookupReference('toolbarMaintenancesButton').setHidden( - Traccar.app.getVehicleFeaturesDisabled() || Traccar.app.getBooleanAttributePreference('ui.disableMaintenances')); + Traccar.app.getVehicleFeaturesDisabled() || Traccar.app.getBooleanAttributePreference('ui.disableMaintenance')); }, onGeofencesClick: function () { @@ -117,7 +117,7 @@ Ext.define('Traccar.view.edit.GroupsController', { onMaintenancesClick: function () { var group = this.getView().getSelectionModel().getSelection()[0]; Ext.create('Traccar.view.BaseWindow', { - title: Strings.sharedMaintenances, + title: Strings.sharedMaintenance, items: { xtype: 'linkMaintenancesView', baseObjectName: 'groupId', diff --git a/web/app/view/edit/Notifications.js b/web/app/view/edit/Notifications.js index 9e24d3d0..9cf97b19 100644 --- a/web/app/view/edit/Notifications.js +++ b/web/app/view/edit/Notifications.js @@ -60,6 +60,23 @@ Ext.define('Traccar.view.edit.Notifications', { renderer: Traccar.AttributeFormatter.getFormatter('always'), filter: 'boolean' }, { + text: Strings.sharedAlarms, + dataIndex: 'attributes', + renderer: function (value) { + var i, key, result = '', alarms = value && value['alarms']; + if (alarms) { + alarms = alarms.split(','); + for (i = 0; i < alarms.length; i++) { + key = 'alarm' + alarms[i].charAt(0).toUpperCase() + alarms[i].slice(1); + if (result) { + result += ', '; + } + result += Strings[key] || key; + } + } + return result; + } + }, { text: Strings.notificationNotificators, dataIndex: 'notificators', flex: 2, diff --git a/web/app/view/edit/SavedCommands.js b/web/app/view/edit/SavedCommands.js index 794e95e4..9e5f4869 100644 --- a/web/app/view/edit/SavedCommands.js +++ b/web/app/view/edit/SavedCommands.js @@ -56,7 +56,7 @@ Ext.define('Traccar.view.edit.SavedCommands', { }, renderer: Traccar.AttributeFormatter.getFormatter('commandType') }, { - text: Strings.notificationSms, + text: Strings.commandSendSms, dataIndex: 'textChannel', renderer: Traccar.AttributeFormatter.getFormatter('textChannel'), filter: 'boolean' diff --git a/web/app/view/edit/Users.js b/web/app/view/edit/Users.js index 2bcaefad..5d9a14f0 100644 --- a/web/app/view/edit/Users.js +++ b/web/app/view/edit/Users.js @@ -101,7 +101,7 @@ Ext.define('Traccar.view.edit.Users', { handler: 'onMaintenancesClick', reference: 'userMaintenancesButton', glyph: 'xf0ad@FontAwesome', - tooltip: Strings.sharedMaintenances, + tooltip: Strings.sharedMaintenance, tooltipType: 'title' }] }, diff --git a/web/app/view/edit/UsersController.js b/web/app/view/edit/UsersController.js index 9d998162..9e810435 100644 --- a/web/app/view/edit/UsersController.js +++ b/web/app/view/edit/UsersController.js @@ -51,7 +51,7 @@ Ext.define('Traccar.view.edit.UsersController', { Traccar.app.getBooleanAttributePreference('ui.disableCalendars')); this.lookupReference('userCommandsButton').setHidden(Traccar.app.getPreference('limitCommands', false)); this.lookupReference('userMaintenancesButton').setHidden( - Traccar.app.getVehicleFeaturesDisabled() || Traccar.app.getBooleanAttributePreference('ui.disableMaintenances')); + Traccar.app.getVehicleFeaturesDisabled() || Traccar.app.getBooleanAttributePreference('ui.disableMaintenance')); }, onEditClick: function () { @@ -215,7 +215,7 @@ Ext.define('Traccar.view.edit.UsersController', { onMaintenancesClick: function () { var user = this.getView().getSelectionModel().getSelection()[0]; Ext.create('Traccar.view.BaseWindow', { - title: Strings.sharedMaintenances, + title: Strings.sharedMaintenance, items: { xtype: 'linkMaintenancesView', baseObjectName: 'userId', diff --git a/web/app/view/map/BaseMap.js b/web/app/view/map/BaseMap.js index 07e391ff..6c6522bd 100644 --- a/web/app/view/map/BaseMap.js +++ b/web/app/view/map/BaseMap.js @@ -30,7 +30,7 @@ Ext.define('Traccar.view.map.BaseMap', { }, initMap: function () { - var server, layer, type, bingKey, lat, lon, zoom, maxZoom, target, poiLayer; + var server, layer, type, bingKey, lat, lon, zoom, maxZoom, target, poiLayer, self = this; server = Traccar.app.getServer(); @@ -41,11 +41,18 @@ Ext.define('Traccar.view.map.BaseMap', { case 'custom': layer = new ol.layer.Tile({ source: new ol.source.XYZ({ - url: server.get('mapUrl'), + url: Ext.String.htmlDecode(server.get('mapUrl')), attributions: '' }) }); break; + case 'customArcgis': + layer = new ol.layer.Tile({ + source: new ol.source.TileArcGISRest({ + url: Ext.String.htmlDecode(server.get('mapUrl')) + }) + }); + break; case 'bingRoad': layer = new ol.layer.Tile({ source: new ol.source.BingMaps({ @@ -96,7 +103,7 @@ Ext.define('Traccar.view.map.BaseMap', { if (y < 0) { y = 'M' + -y; } - return 'http://online{}.map.bdimg.com/onlinelabel/?qt=tile&x={x}&y={y}&z={z}&styles=pl' + return 'https://online{}.map.bdimg.com/onlinelabel/?qt=tile&x={x}&y={y}&z={z}&styles=pl' .replace('{}', index).replace('{x}', x).replace('{y}', y).replace('{z}', z); }, tileGrid: new ol.tilegrid.TileGrid({ @@ -108,7 +115,7 @@ Ext.define('Traccar.view.map.BaseMap', { 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5 ] }), - attributions: '© <a href="http://map.baidu.com/">Baidu</a>' + attributions: '© <a href="https://map.baidu.com/">Baidu</a>' }) }); break; @@ -130,16 +137,16 @@ Ext.define('Traccar.view.map.BaseMap', { }) }); break; - case 'osm': + case 'wikimedia': layer = new ol.layer.Tile({ - source: new ol.source.OSM({}) + source: new ol.source.OSM({ + url: 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png' + }) }); break; default: layer = new ol.layer.Tile({ - source: new ol.source.OSM({ - url: 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png' - }) + source: new ol.source.OSM({}) }); break; } @@ -207,19 +214,19 @@ Ext.define('Traccar.view.map.BaseMap', { }); this.map.on('click', function (e) { - var i, features = this.map.getFeaturesAtPixel(e.pixel, { + var i, features = self.map.getFeaturesAtPixel(e.pixel, { layerFilter: function (layer) { return !layer.get('name'); } }); if (features) { for (i = 0; i < features.length; i++) { - this.fireEvent('selectfeature', features[i]); + self.fireEvent('selectfeature', features[i]); } } else { - this.fireEvent('deselectfeature'); + self.fireEvent('deselectfeature'); } - }, this); + }); }, listeners: { @@ -232,7 +239,12 @@ Ext.define('Traccar.view.map.BaseMap', { } } }, function () { + var projection; proj4.defs('BD-MC', '+proj=merc +lon_0=0 +units=m +ellps=clrk66 +no_defs'); proj4.defs('EPSG:3395', '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs'); - ol.proj.get('EPSG:3395').setExtent([-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244]); + ol.proj.proj4.register(proj4); + projection = ol.proj.get('EPSG:3395'); + if (projection) { + projection.setExtent([-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244]); + } }); diff --git a/web/app/view/map/GeofenceMap.js b/web/app/view/map/GeofenceMap.js index 6b06ae98..8cef574b 100644 --- a/web/app/view/map/GeofenceMap.js +++ b/web/app/view/map/GeofenceMap.js @@ -105,13 +105,14 @@ Ext.define('Traccar.view.map.GeofenceMap', { }, addInteraction: function (type) { + var self = this; this.draw = new ol.interaction.Draw({ features: this.features, type: type }); this.draw.on('drawstart', function () { - this.features.clear(); - }, this); + self.features.clear(); + }); this.map.addInteraction(this.draw); }, diff --git a/web/app/view/map/MapController.js b/web/app/view/map/MapController.js index 38b2340c..7ba0ee3b 100644 --- a/web/app/view/map/MapController.js +++ b/web/app/view/map/MapController.js @@ -83,7 +83,8 @@ Ext.define('Traccar.view.map.MapController', { var feature = new ol.Feature( Traccar.GeofenceConverter.wktToGeometry(this.getView().getMapView(), geofence.get('area'))); feature.setStyle(this.getAreaStyle( - geofence.get('name'), geofence.get('attributes') ? geofence.get('attributes').color : null)); + Ext.String.htmlDecode(geofence.get('name')), + geofence.get('attributes') ? geofence.get('attributes').color : null)); this.getView().getGeofencesSource().addFeature(feature); return true; }, this); diff --git a/web/app/view/map/MapMarkerController.js b/web/app/view/map/MapMarkerController.js index bf6b0d49..785fcc68 100644 --- a/web/app/view/map/MapMarkerController.js +++ b/web/app/view/map/MapMarkerController.js @@ -130,7 +130,7 @@ Ext.define('Traccar.view.map.MapMarkerController', { }, updateDevice: function (store, data) { - var i, device, deviceId, marker, style; + var i, device, deviceId, deviceName, marker, style; if (!Ext.isArray(data)) { data = [data]; @@ -148,8 +148,9 @@ Ext.define('Traccar.view.map.MapMarkerController', { this.updateDeviceMarker(style, this.getDeviceColor(device), device.get('category')); marker.changed(); } - if (style.getText().getText() !== device.get('name')) { - style.getText().setText(device.get('name')); + deviceName = Ext.String.htmlDecode(device.get('name')); + if (style.getText().getText() !== deviceName) { + style.getText().setText(deviceName); marker.changed(); } } @@ -187,6 +188,39 @@ Ext.define('Traccar.view.map.MapMarkerController', { } }, + animateMarker: function (marker, geometry, course) { + var start, end, duration, timeout, line, updatePosition, self; + + start = marker.getGeometry().getCoordinates(); + end = geometry.getCoordinates(); + line = new ol.geom.LineString([start, end]); + duration = Traccar.Style.mapAnimateMarkerDuration; + timeout = Traccar.Style.mapAnimateMarkerTimeout; + self = this; + + updatePosition = function (position, marker) { + var coordinate, style; + coordinate = marker.get('line').getCoordinateAt(position / (duration / timeout)); + style = marker.getStyle(); + marker.setGeometry(new ol.geom.Point(coordinate)); + if (position < duration / timeout) { + setTimeout(updatePosition, timeout, position + 1, marker); + } else { + if (style.getImage().angle !== marker.get('nextCourse')) { + self.rotateMarker(style, marker.get('nextCourse')); + } + marker.set('animating', false); + } + }; + + marker.set('line', line); + marker.set('nextCourse', course); + if (!marker.get('animating')) { + marker.set('animating', true); + updatePosition(1, marker); + } + }, + updateLatest: function (store, data) { var i, position, device, deviceStore; @@ -209,15 +243,13 @@ Ext.define('Traccar.view.map.MapMarkerController', { }, updateAccuracy: function (position, device) { - var center, radius, feature, mapView, projection, pointResolution; - mapView = this.getView().getMapView(); + var center, radius, feature; feature = this.accuracyCircles[position.get('deviceId')]; if (position.get('accuracy')) { - projection = mapView.getProjection(); center = ol.proj.fromLonLat([position.get('longitude'), position.get('latitude')]); - pointResolution = ol.proj.getPointResolution(projection, mapView.getResolution(), center); - radius = position.get('accuracy') / ol.proj.METERS_PER_UNIT.m * mapView.getResolution() / pointResolution; + radius = Ext.getStore('DistanceUnits').convertValue( + position.get('accuracy'), Traccar.app.getAttributePreference('distanceUnit'), true); if (feature) { feature.getGeometry().setCenter(center); @@ -248,11 +280,7 @@ Ext.define('Traccar.view.map.MapMarkerController', { deviceId = position.get('deviceId'); if (deviceId in this.latestMarkers) { marker = this.latestMarkers[deviceId]; - style = marker.getStyle(); - if (style.getImage().angle !== position.get('course')) { - this.rotateMarker(style, position.get('course')); - } - marker.setGeometry(geometry); + this.animateMarker(marker, geometry, position.get('course')); } else { marker = new ol.Feature(geometry); marker.set('record', device); @@ -260,7 +288,7 @@ Ext.define('Traccar.view.map.MapMarkerController', { style = this.getLatestMarker(this.getDeviceColor(device), position.get('course'), device.get('category')); - style.getText().setText(device.get('name')); + style.getText().setText(Ext.String.htmlDecode(device.get('name'))); marker.setStyle(style); marker.setId(device.get('id')); this.latestMarkers[deviceId] = marker; @@ -457,9 +485,6 @@ Ext.define('Traccar.view.map.MapMarkerController', { if (this.selectedMarker) { if (this.selectedMarker.get('event')) { this.getView().getMarkersSource().removeFeature(this.selectedMarker); - if (!marker || !marker.get('event')) { - this.fireEvent('deselectevent'); - } } else if (!Ext.getStore('ReportRoute').showMarkers && this.selectedMarker.get('record') instanceof Traccar.model.Position) { this.getView().getMarkersSource().removeFeature(this.selectedMarker); @@ -496,12 +521,13 @@ Ext.define('Traccar.view.map.MapMarkerController', { this.reportMarkers[position.get('id')] = this.addReportMarker(position); } this.selectMarker(this.reportMarkers[position.get('id')], center); + } else if (this.selectedMarker) { + this.selectMarker(null, false); } }, selectEvent: function (position) { var marker; - this.fireEvent('deselectfeature'); if (position) { marker = this.addReportMarker(position); marker.set('event', true); diff --git a/web/app/view/permissions/SavedCommands.js b/web/app/view/permissions/SavedCommands.js index 52b759bb..b57c07a0 100644 --- a/web/app/view/permissions/SavedCommands.js +++ b/web/app/view/permissions/SavedCommands.js @@ -40,7 +40,7 @@ Ext.define('Traccar.view.permissions.SavedCommands', { }, renderer: Traccar.AttributeFormatter.getFormatter('commandType') }, { - text: Strings.notificationSms, + text: Strings.commandSendSms, dataIndex: 'textChannel', flex: 1, minWidth: Traccar.Style.columnWidthNormal, |