38 changed files with 1474 additions and 175 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,25 @@ |
|||||
|
{ |
||||
|
"widgetsBundle": { |
||||
|
"alias": "control_widgets", |
||||
|
"title": "Control widgets", |
||||
|
"image": null |
||||
|
}, |
||||
|
"widgetTypes": [ |
||||
|
{ |
||||
|
"alias": "device_terminal", |
||||
|
"name": "Device terminal", |
||||
|
"descriptor": { |
||||
|
"type": "rpc", |
||||
|
"sizeX": 9.5, |
||||
|
"sizeY": 5.5, |
||||
|
"resources": [], |
||||
|
"templateHtml": "<div style=\"height: 100%;\" id=\"device-terminal\"></div>", |
||||
|
"templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", |
||||
|
"controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n var greetings = 'Welcome to ThingsBoard RPC commands terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = angular.copy(command).trim();\n if (localCommand == 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (cmdObj.args.length && cmdObj.args[0]) {\n try {\n params = JSON.parse(cmdObj.args[0]);\n } catch (e) {\n params = cmdObj.args[0];\n }\n }\n performRpc(this, cmdObj.name, params);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout).then(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n \nself.onDestroy = function() {\n}\n", |
||||
|
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}", |
||||
|
"dataKeySettingsSchema": "{}\n", |
||||
|
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"Device terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,138 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
export function utf8Encode(str) { |
||||
|
var result; |
||||
|
|
||||
|
if (angular.isUndefined(Uint8Array)) { // eslint-disable-line no-undef
|
||||
|
result = utf8ToBytes(str); |
||||
|
} else { |
||||
|
result = new Uint8Array(utf8ToBytes(str)); // eslint-disable-line no-undef
|
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
export function utf8Decode(bytes) { |
||||
|
return utf8Slice(bytes, 0, bytes.length); |
||||
|
} |
||||
|
|
||||
|
function utf8Slice (buf, start, end) { |
||||
|
var res = '' |
||||
|
var tmp = '' |
||||
|
end = Math.min(buf.length, end || Infinity) |
||||
|
start = start || 0; |
||||
|
|
||||
|
for (var i = start; i < end; i++) { |
||||
|
if (buf[i] <= 0x7F) { |
||||
|
res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i]) |
||||
|
tmp = '' |
||||
|
} else { |
||||
|
tmp += '%' + buf[i].toString(16) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return res + decodeUtf8Char(tmp) |
||||
|
} |
||||
|
|
||||
|
function decodeUtf8Char (str) { |
||||
|
try { |
||||
|
return decodeURIComponent(str) |
||||
|
} catch (err) { |
||||
|
return String.fromCharCode(0xFFFD) // UTF 8 invalid char
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function utf8ToBytes (string, units) { |
||||
|
units = units || Infinity |
||||
|
var codePoint |
||||
|
var length = string.length |
||||
|
var leadSurrogate = null |
||||
|
var bytes = [] |
||||
|
var i = 0 |
||||
|
|
||||
|
for (; i < length; i++) { |
||||
|
codePoint = string.charCodeAt(i) |
||||
|
|
||||
|
// is surrogate component
|
||||
|
if (codePoint > 0xD7FF && codePoint < 0xE000) { |
||||
|
// last char was a lead
|
||||
|
if (leadSurrogate) { |
||||
|
// 2 leads in a row
|
||||
|
if (codePoint < 0xDC00) { |
||||
|
if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) |
||||
|
leadSurrogate = codePoint |
||||
|
continue |
||||
|
} else { |
||||
|
// valid surrogate pair
|
||||
|
codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000 |
||||
|
leadSurrogate = null |
||||
|
} |
||||
|
} else { |
||||
|
// no lead yet
|
||||
|
|
||||
|
if (codePoint > 0xDBFF) { |
||||
|
// unexpected trail
|
||||
|
if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) |
||||
|
continue |
||||
|
} else if (i + 1 === length) { |
||||
|
// unpaired lead
|
||||
|
if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) |
||||
|
continue |
||||
|
} else { |
||||
|
// valid lead
|
||||
|
leadSurrogate = codePoint |
||||
|
continue |
||||
|
} |
||||
|
} |
||||
|
} else if (leadSurrogate) { |
||||
|
// valid bmp char, but last char was a lead
|
||||
|
if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) |
||||
|
leadSurrogate = null |
||||
|
} |
||||
|
|
||||
|
// encode utf8
|
||||
|
if (codePoint < 0x80) { |
||||
|
if ((units -= 1) < 0) break |
||||
|
bytes.push(codePoint) |
||||
|
} else if (codePoint < 0x800) { |
||||
|
if ((units -= 2) < 0) break |
||||
|
bytes.push( |
||||
|
codePoint >> 0x6 | 0xC0, |
||||
|
codePoint & 0x3F | 0x80 |
||||
|
) |
||||
|
} else if (codePoint < 0x10000) { |
||||
|
if ((units -= 3) < 0) break |
||||
|
bytes.push( |
||||
|
codePoint >> 0xC | 0xE0, |
||||
|
codePoint >> 0x6 & 0x3F | 0x80, |
||||
|
codePoint & 0x3F | 0x80 |
||||
|
) |
||||
|
} else if (codePoint < 0x200000) { |
||||
|
if ((units -= 4) < 0) break |
||||
|
bytes.push( |
||||
|
codePoint >> 0x12 | 0xF0, |
||||
|
codePoint >> 0xC & 0x3F | 0x80, |
||||
|
codePoint >> 0x6 & 0x3F | 0x80, |
||||
|
codePoint & 0x3F | 0x80 |
||||
|
) |
||||
|
} else { |
||||
|
throw new Error('Invalid code point') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return bytes |
||||
|
} |
||||
@ -0,0 +1,680 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
import tinycolor from 'tinycolor2'; |
||||
|
|
||||
|
import TbGoogleMap from './google-map'; |
||||
|
import TbOpenStreetMap from './openstreet-map'; |
||||
|
|
||||
|
import {processPattern, arraysEqual, toLabelValueMap, fillPattern, fillPatternWithActions} from './widget-utils'; |
||||
|
|
||||
|
export default class TbMapWidgetV2 { |
||||
|
constructor(mapProvider, drawRoutes, ctx, useDynamicLocations, $element) { |
||||
|
var tbMap = this; |
||||
|
this.ctx = ctx; |
||||
|
if (!$element) { |
||||
|
$element = ctx.$container; |
||||
|
} |
||||
|
this.utils = ctx.$scope.$injector.get('utils'); |
||||
|
this.drawRoutes = drawRoutes; |
||||
|
this.markers = []; |
||||
|
if (this.drawRoutes) { |
||||
|
this.polylines = []; |
||||
|
} |
||||
|
|
||||
|
this.locationSettings = {}; |
||||
|
|
||||
|
var settings = ctx.settings; |
||||
|
|
||||
|
this.callbacks = {}; |
||||
|
this.callbacks.onLocationClick = function(){}; |
||||
|
|
||||
|
if (settings.defaultZoomLevel) { |
||||
|
if (settings.defaultZoomLevel > 0 && settings.defaultZoomLevel < 21) { |
||||
|
this.defaultZoomLevel = Math.floor(settings.defaultZoomLevel); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.dontFitMapBounds = settings.fitMapBounds === false; |
||||
|
|
||||
|
if (!useDynamicLocations) { |
||||
|
this.subscription = this.ctx.defaultSubscription; |
||||
|
} |
||||
|
|
||||
|
this.configureLocationsSettings(); |
||||
|
|
||||
|
var minZoomLevel = this.drawRoutes ? 18 : 15; |
||||
|
|
||||
|
var initCallback = function() { |
||||
|
tbMap.update(); |
||||
|
tbMap.resize(); |
||||
|
}; |
||||
|
|
||||
|
this.ctx.$scope.onTooltipAction = function(event, actionName, dsIndex) { |
||||
|
tbMap.onTooltipAction(event, actionName, dsIndex); |
||||
|
}; |
||||
|
this.tooltipActionsMap = {}; |
||||
|
var descriptors = this.ctx.actionsApi.getActionDescriptors('tooltipAction'); |
||||
|
descriptors.forEach(function (descriptor) { |
||||
|
tbMap.tooltipActionsMap[descriptor.name] = descriptor; |
||||
|
}); |
||||
|
|
||||
|
if (mapProvider === 'google-map') { |
||||
|
this.map = new TbGoogleMap($element, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.gmApiKey, settings.gmDefaultMapType); |
||||
|
} else if (mapProvider === 'openstreet-map') { |
||||
|
this.map = new TbOpenStreetMap($element, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
setCallbacks(callbacks) { |
||||
|
Object.assign(this.callbacks, callbacks); |
||||
|
} |
||||
|
|
||||
|
clearLocations() { |
||||
|
if (this.locations) { |
||||
|
var tbMap = this; |
||||
|
this.locations.forEach(function(location) { |
||||
|
if (location.marker) { |
||||
|
tbMap.map.removeMarker(location.marker); |
||||
|
} |
||||
|
if (location.polyline) { |
||||
|
tbMap.map.removePolyline(location.polyline); |
||||
|
} |
||||
|
}); |
||||
|
this.locations = null; |
||||
|
this.markers = []; |
||||
|
if (this.drawRoutes) { |
||||
|
this.polylines = []; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
setSubscription(subscription) { |
||||
|
this.subscription = subscription; |
||||
|
this.clearLocations(); |
||||
|
} |
||||
|
|
||||
|
configureLocationsSettings() { |
||||
|
|
||||
|
this.locationSettings.latKeyName = this.ctx.settings.latKeyName || 'latitude'; |
||||
|
this.locationSettings.lngKeyName = this.ctx.settings.lngKeyName || 'longitude'; |
||||
|
|
||||
|
this.locationSettings.tooltipPattern = this.ctx.settings.tooltipPattern || "<b>${entityName}</b><br/><br/><b>Latitude:</b> ${"+this.locationSettings.latKeyName+":7}<br/><b>Longitude:</b> ${"+this.locationSettings.lngKeyName+":7}"; |
||||
|
|
||||
|
this.locationSettings.showLabel = this.ctx.settings.showLabel !== false; |
||||
|
this.locationSettings.displayTooltip = true; |
||||
|
this.locationSettings.labelColor = this.ctx.widgetConfig.color || '#000000', |
||||
|
this.locationSettings.label = this.ctx.settings.label || "${entityName}"; |
||||
|
this.locationSettings.color = this.ctx.settings.color ? tinycolor(this.ctx.settings.color).toHexString() : "#FE7569"; |
||||
|
|
||||
|
this.locationSettings.useColorFunction = this.ctx.settings.useColorFunction === true; |
||||
|
if (angular.isDefined(this.ctx.settings.colorFunction) && this.ctx.settings.colorFunction.length > 0) { |
||||
|
try { |
||||
|
this.locationSettings.colorFunction = new Function('data, dsData, dsIndex', this.ctx.settings.colorFunction); |
||||
|
} catch (e) { |
||||
|
this.locationSettings.colorFunction = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.locationSettings.useMarkerImageFunction = this.ctx.settings.useMarkerImageFunction === true; |
||||
|
if (angular.isDefined(this.ctx.settings.markerImageFunction) && this.ctx.settings.markerImageFunction.length > 0) { |
||||
|
try { |
||||
|
this.locationSettings.markerImageFunction = new Function('data, images, dsData, dsIndex', this.ctx.settings.markerImageFunction); |
||||
|
} catch (e) { |
||||
|
this.locationSettings.markerImageFunction = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.locationSettings.markerImages = this.ctx.settings.markerImages || []; |
||||
|
|
||||
|
if (!this.locationSettings.useMarkerImageFunction && |
||||
|
angular.isDefined(this.ctx.settings.markerImage) && |
||||
|
this.ctx.settings.markerImage.length > 0) { |
||||
|
this.locationSettings.markerImage = this.ctx.settings.markerImage; |
||||
|
this.locationSettings.useMarkerImage = true; |
||||
|
this.locationSettings.markerImageSize = this.ctx.settings.markerImageSize || 34; |
||||
|
} |
||||
|
|
||||
|
if (this.drawRoutes) { |
||||
|
this.locationSettings.strokeWeight = this.ctx.settings.strokeWeight || 2; |
||||
|
this.locationSettings.strokeOpacity = this.ctx.settings.strokeOpacity || 1.0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onTooltipAction(event, actionName, dsIndex) { |
||||
|
var descriptor = this.tooltipActionsMap[actionName]; |
||||
|
if (descriptor) { |
||||
|
var datasource = this.subscription.datasources[dsIndex]; |
||||
|
var entityId = {}; |
||||
|
entityId.id = datasource.entityId; |
||||
|
entityId.entityType = datasource.entityType; |
||||
|
var entityName = datasource.entityName; |
||||
|
this.ctx.actionsApi.handleWidgetAction(event, descriptor, entityId, entityName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
update() { |
||||
|
|
||||
|
var tbMap = this; |
||||
|
|
||||
|
function updateLocationLabel(location) { |
||||
|
if (location.settings.showLabel && location.settings.labelReplaceInfo.variables.length) { |
||||
|
location.settings.labelText = fillPattern(location.settings.label, |
||||
|
location.settings.labelReplaceInfo, tbMap.subscription.data); |
||||
|
tbMap.map.updateMarkerLabel(location.marker, location.settings); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function calculateLocationColor(location, dataMap) { |
||||
|
if (location.settings.useColorFunction && location.settings.colorFunction) { |
||||
|
var color; |
||||
|
try { |
||||
|
color = location.settings.colorFunction(dataMap.dataMap, dataMap.dsDataMap, location.dsIndex); |
||||
|
} catch (e) {/**/} |
||||
|
if (!color) { |
||||
|
color = '#FE7569'; |
||||
|
} |
||||
|
return tinycolor(color).toHexString(); |
||||
|
} else { |
||||
|
return location.settings.color; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function updateLocationColor(location, color, image) { |
||||
|
if (!location.settings.calculatedColor || location.settings.calculatedColor !== color) { |
||||
|
if (!location.settings.useMarkerImage && !image) { |
||||
|
tbMap.map.updateMarkerColor(location.marker, color); |
||||
|
} |
||||
|
if (location.polyline) { |
||||
|
tbMap.map.updatePolylineColor(location.polyline, location.settings, color); |
||||
|
} |
||||
|
location.settings.calculatedColor = color; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function calculateLocationMarkerImage(location, dataMap) { |
||||
|
if (location.settings.useMarkerImageFunction && location.settings.markerImageFunction) { |
||||
|
var image = null; |
||||
|
try { |
||||
|
image = location.settings.markerImageFunction(dataMap.dataMap, location.settings.markerImages, dataMap.dsDataMap, location.dsIndex); |
||||
|
} catch (e) { |
||||
|
image = null; |
||||
|
} |
||||
|
return image; |
||||
|
} else { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function updateLocationMarkerImage(location, image) { |
||||
|
if (image && (!location.settings.calculatedImage || !angular.equals(location.settings.calculatedImage, image))) { |
||||
|
tbMap.map.updateMarkerImage(location.marker, location.settings, image.url, image.size); |
||||
|
location.settings.calculatedImage = image; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function updateLocationStyle(location, dataMap) { |
||||
|
updateLocationLabel(location); |
||||
|
var color = calculateLocationColor(location, dataMap); |
||||
|
var image = calculateLocationMarkerImage(location, dataMap); |
||||
|
updateLocationColor(location, color, image); |
||||
|
updateLocationMarkerImage(location, image); |
||||
|
} |
||||
|
|
||||
|
function locationRowClick($event, location) { |
||||
|
var descriptors = tbMap.ctx.actionsApi.getActionDescriptors('markerClick'); |
||||
|
if (descriptors.length) { |
||||
|
var datasource = tbMap.subscription.datasources[location.dsIndex]; |
||||
|
var entityId = {}; |
||||
|
entityId.id = datasource.entityId; |
||||
|
entityId.entityType = datasource.entityType; |
||||
|
var entityName = datasource.entityName; |
||||
|
tbMap.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function updateLocation(location, data, dataMap) { |
||||
|
var locationChanged = false; |
||||
|
if (location.latIndex > -1 && location.lngIndex > -1) { |
||||
|
var latData = data[location.latIndex].data; |
||||
|
var lngData = data[location.lngIndex].data; |
||||
|
var lat, lng, latLng; |
||||
|
if (latData.length > 0 && lngData.length > 0) { |
||||
|
if (tbMap.drawRoutes) { |
||||
|
// Create or update route
|
||||
|
var latLngs = []; |
||||
|
for (var i = 0; i < latData.length; i++) { |
||||
|
lat = latData[i][1]; |
||||
|
lng = lngData[i][1]; |
||||
|
latLng = tbMap.map.createLatLng(lat, lng); |
||||
|
if (i == 0 || !latLngs[latLngs.length - 1].equals(latLng)) { |
||||
|
latLngs.push(latLng); |
||||
|
} |
||||
|
} |
||||
|
if (latLngs.length > 0) { |
||||
|
var markerLocation = latLngs[latLngs.length - 1]; |
||||
|
if (!location.marker) { |
||||
|
location.marker = tbMap.map.createMarker(markerLocation, location.settings, |
||||
|
function (event) { |
||||
|
tbMap.callbacks.onLocationClick(location); |
||||
|
locationRowClick(event, location); |
||||
|
}, [location.dsIndex] |
||||
|
); |
||||
|
} else { |
||||
|
tbMap.map.setMarkerPosition(location.marker, markerLocation); |
||||
|
} |
||||
|
} |
||||
|
if (!location.polyline) { |
||||
|
location.polyline = tbMap.map.createPolyline(latLngs, location.settings); |
||||
|
tbMap.polylines.push(location.polyline); |
||||
|
locationChanged = true; |
||||
|
} else { |
||||
|
var prevPath = tbMap.map.getPolylineLatLngs(location.polyline); |
||||
|
if (!prevPath || !arraysEqual(prevPath, latLngs)) { |
||||
|
tbMap.map.setPolylineLatLngs(location.polyline, latLngs); |
||||
|
locationChanged = true; |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
// Create or update marker
|
||||
|
lat = latData[latData.length - 1][1]; |
||||
|
lng = lngData[lngData.length - 1][1]; |
||||
|
latLng = tbMap.map.createLatLng(lat, lng); |
||||
|
if (!location.marker) { |
||||
|
location.marker = tbMap.map.createMarker(latLng, location.settings, |
||||
|
function (event) { |
||||
|
tbMap.callbacks.onLocationClick(location); |
||||
|
locationRowClick(event, location); |
||||
|
}, [location.dsIndex]); |
||||
|
tbMap.markers.push(location.marker); |
||||
|
locationChanged = true; |
||||
|
} else { |
||||
|
var prevPosition = tbMap.map.getMarkerPosition(location.marker); |
||||
|
if (!prevPosition.equals(latLng)) { |
||||
|
tbMap.map.setMarkerPosition(location.marker, latLng); |
||||
|
locationChanged = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
updateLocationStyle(location, dataMap); |
||||
|
} |
||||
|
} |
||||
|
return locationChanged; |
||||
|
} |
||||
|
|
||||
|
function loadLocations(data, datasources) { |
||||
|
var bounds = tbMap.map.createBounds(); |
||||
|
tbMap.locations = []; |
||||
|
var dataMap = toLabelValueMap(data, datasources); |
||||
|
var currentDatasource = null; |
||||
|
var currentDatasourceIndex = -1; |
||||
|
var latIndex = -1; |
||||
|
var lngIndex = -1; |
||||
|
|
||||
|
for (var i=0;i<data.length;i++) { |
||||
|
var dataKeyData = data[i]; |
||||
|
var dataKey = dataKeyData.dataKey; |
||||
|
if (dataKeyData.datasource != currentDatasource) { |
||||
|
currentDatasource = dataKeyData.datasource; |
||||
|
currentDatasourceIndex++; |
||||
|
latIndex = -1; |
||||
|
lngIndex = -1; |
||||
|
} |
||||
|
var nameToCheck; |
||||
|
if (dataKey.locationAttrName) { |
||||
|
nameToCheck = dataKey.locationAttrName; |
||||
|
} else { |
||||
|
nameToCheck = dataKey.label; |
||||
|
} |
||||
|
if (nameToCheck === tbMap.locationSettings.latKeyName) { |
||||
|
latIndex = i; |
||||
|
} else if (nameToCheck === tbMap.locationSettings.lngKeyName) { |
||||
|
lngIndex = i; |
||||
|
} |
||||
|
if (latIndex > -1 && lngIndex > -1) { |
||||
|
var location = { |
||||
|
latIndex: latIndex, |
||||
|
lngIndex: lngIndex, |
||||
|
dsIndex: currentDatasourceIndex, |
||||
|
settings: angular.copy(tbMap.locationSettings) |
||||
|
}; |
||||
|
if (location.settings.showLabel) { |
||||
|
location.settings.label = tbMap.utils.createLabelFromDatasource(currentDatasource, location.settings.label); |
||||
|
location.settings.labelReplaceInfo = processPattern(location.settings.label, datasources, currentDatasourceIndex); |
||||
|
location.settings.labelText = location.settings.label; |
||||
|
} |
||||
|
if (location.settings.displayTooltip) { |
||||
|
location.settings.tooltipPattern = tbMap.utils.createLabelFromDatasource(currentDatasource, location.settings.tooltipPattern); |
||||
|
location.settings.tooltipReplaceInfo = processPattern(location.settings.tooltipPattern, datasources, currentDatasourceIndex); |
||||
|
} |
||||
|
|
||||
|
tbMap.locations.push(location); |
||||
|
updateLocation(location, data, dataMap); |
||||
|
if (location.polyline) { |
||||
|
tbMap.map.extendBounds(bounds, location.polyline); |
||||
|
} else if (location.marker) { |
||||
|
tbMap.map.extendBoundsWithMarker(bounds, location.marker); |
||||
|
} |
||||
|
latIndex = -1; |
||||
|
lngIndex = -1; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
tbMap.map.fitBounds(bounds); |
||||
|
} |
||||
|
|
||||
|
function updateLocations(data, datasources) { |
||||
|
var locationsChanged = false; |
||||
|
var bounds = tbMap.map.createBounds(); |
||||
|
var dataMap = toLabelValueMap(data, datasources); |
||||
|
for (var p = 0; p < tbMap.locations.length; p++) { |
||||
|
var location = tbMap.locations[p]; |
||||
|
locationsChanged |= updateLocation(location, data, dataMap); |
||||
|
if (location.polyline) { |
||||
|
tbMap.map.extendBounds(bounds, location.polyline); |
||||
|
} else if (location.marker) { |
||||
|
tbMap.map.extendBoundsWithMarker(bounds, location.marker); |
||||
|
} |
||||
|
} |
||||
|
if (locationsChanged) { |
||||
|
tbMap.map.fitBounds(bounds); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (this.map && this.map.inited() && this.subscription) { |
||||
|
if (this.subscription.data) { |
||||
|
if (!this.locations) { |
||||
|
loadLocations(this.subscription.data, this.subscription.datasources); |
||||
|
} else { |
||||
|
updateLocations(this.subscription.data, this.subscription.datasources); |
||||
|
} |
||||
|
var tooltips = this.map.getTooltips(); |
||||
|
for (var t=0; t < tooltips.length; t++) { |
||||
|
var tooltip = tooltips[t]; |
||||
|
var text = fillPattern(tooltip.pattern, tooltip.replaceInfo, this.subscription.data); |
||||
|
text = fillPatternWithActions(text, 'onTooltipAction', tooltip.markerArgs); |
||||
|
tooltip.popup.setContent(text); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
resize() { |
||||
|
if (this.map && this.map.inited()) { |
||||
|
this.map.invalidateSize(); |
||||
|
if (this.locations && this.locations.length > 0) { |
||||
|
var bounds = this.map.createBounds(); |
||||
|
for (var m = 0; m < this.markers.length; m++) { |
||||
|
this.map.extendBoundsWithMarker(bounds, this.markers[m]); |
||||
|
} |
||||
|
if (this.polylines) { |
||||
|
for (var p = 0; p < this.polylines.length; p++) { |
||||
|
this.map.extendBounds(bounds, this.polylines[p]); |
||||
|
} |
||||
|
} |
||||
|
this.map.fitBounds(bounds); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static settingsSchema(mapProvider, drawRoutes) { |
||||
|
var schema; |
||||
|
if (mapProvider === 'google-map') { |
||||
|
schema = angular.copy(googleMapSettingsSchema); |
||||
|
} else if (mapProvider === 'openstreet-map') { |
||||
|
schema = angular.copy(openstreetMapSettingsSchema); |
||||
|
} |
||||
|
angular.merge(schema.schema.properties, commonMapSettingsSchema.schema.properties); |
||||
|
schema.schema.required = schema.schema.required.concat(commonMapSettingsSchema.schema.required); |
||||
|
schema.form = schema.form.concat(commonMapSettingsSchema.form); |
||||
|
if (drawRoutes) { |
||||
|
angular.merge(schema.schema.properties, routeMapSettingsSchema.schema.properties); |
||||
|
schema.schema.required = schema.schema.required.concat(routeMapSettingsSchema.schema.required); |
||||
|
schema.form = schema.form.concat(routeMapSettingsSchema.form); |
||||
|
} |
||||
|
return schema; |
||||
|
} |
||||
|
|
||||
|
static dataKeySettingsSchema(/*mapProvider*/) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
static actionSources() { |
||||
|
return { |
||||
|
'markerClick': { |
||||
|
name: 'widget-action.marker-click', |
||||
|
multiple: false |
||||
|
}, |
||||
|
'tooltipAction': { |
||||
|
name: 'widget-action.tooltip-tag-action', |
||||
|
multiple: true |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
const googleMapSettingsSchema = |
||||
|
{ |
||||
|
"schema":{ |
||||
|
"title":"Google Map Configuration", |
||||
|
"type":"object", |
||||
|
"properties":{ |
||||
|
"gmApiKey":{ |
||||
|
"title":"Google Maps API Key", |
||||
|
"type":"string" |
||||
|
}, |
||||
|
"gmDefaultMapType":{ |
||||
|
"title":"Default map type", |
||||
|
"type":"string", |
||||
|
"default":"roadmap" |
||||
|
} |
||||
|
}, |
||||
|
"required":[ |
||||
|
"gmApiKey" |
||||
|
] |
||||
|
}, |
||||
|
"form":[ |
||||
|
"gmApiKey", |
||||
|
{ |
||||
|
"key":"gmDefaultMapType", |
||||
|
"type":"rc-select", |
||||
|
"multiple":false, |
||||
|
"items":[ |
||||
|
{ |
||||
|
"value":"roadmap", |
||||
|
"label":"Roadmap" |
||||
|
}, |
||||
|
{ |
||||
|
"value":"satellite", |
||||
|
"label":"Satellite" |
||||
|
}, |
||||
|
{ |
||||
|
"value":"hybrid", |
||||
|
"label":"Hybrid" |
||||
|
}, |
||||
|
{ |
||||
|
"value":"terrain", |
||||
|
"label":"Terrain" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}; |
||||
|
|
||||
|
const openstreetMapSettingsSchema = |
||||
|
{ |
||||
|
"schema":{ |
||||
|
"title":"Openstreet Map Configuration", |
||||
|
"type":"object", |
||||
|
"properties":{ |
||||
|
}, |
||||
|
"required":[ |
||||
|
] |
||||
|
}, |
||||
|
"form":[ |
||||
|
] |
||||
|
}; |
||||
|
|
||||
|
const commonMapSettingsSchema = |
||||
|
{ |
||||
|
"schema":{ |
||||
|
"title":"Map Configuration", |
||||
|
"type":"object", |
||||
|
"properties":{ |
||||
|
"defaultZoomLevel":{ |
||||
|
"title":"Default map zoom level (1 - 20)", |
||||
|
"type":"number" |
||||
|
}, |
||||
|
"fitMapBounds":{ |
||||
|
"title":"Fit map bounds to cover all markers", |
||||
|
"type":"boolean", |
||||
|
"default":true |
||||
|
}, |
||||
|
"latKeyName":{ |
||||
|
"title":"Latitude key name", |
||||
|
"type":"string", |
||||
|
"default":"latitude" |
||||
|
}, |
||||
|
"lngKeyName":{ |
||||
|
"title":"Longitude key name", |
||||
|
"type":"string", |
||||
|
"default":"longitude" |
||||
|
}, |
||||
|
"showLabel":{ |
||||
|
"title":"Show label", |
||||
|
"type":"boolean", |
||||
|
"default":true |
||||
|
}, |
||||
|
"label":{ |
||||
|
"title":"Label (pattern examples: '${entityName}', '${entityName}: (Text ${keyName} units.)' )", |
||||
|
"type":"string", |
||||
|
"default":"${entityName}" |
||||
|
}, |
||||
|
"tooltipPattern":{ |
||||
|
"title":"Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')", |
||||
|
"type":"string", |
||||
|
"default":"<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}" |
||||
|
}, |
||||
|
"color":{ |
||||
|
"title":"Color", |
||||
|
"type":"string" |
||||
|
}, |
||||
|
"useColorFunction":{ |
||||
|
"title":"Use color function", |
||||
|
"type":"boolean", |
||||
|
"default":false |
||||
|
}, |
||||
|
"colorFunction":{ |
||||
|
"title":"Color function: f(data, dsData, dsIndex)", |
||||
|
"type":"string" |
||||
|
}, |
||||
|
"markerImage":{ |
||||
|
"title":"Custom marker image", |
||||
|
"type":"string" |
||||
|
}, |
||||
|
"markerImageSize":{ |
||||
|
"title":"Custom marker image size (px)", |
||||
|
"type":"number", |
||||
|
"default":34 |
||||
|
}, |
||||
|
"useMarkerImageFunction":{ |
||||
|
"title":"Use marker image function", |
||||
|
"type":"boolean", |
||||
|
"default":false |
||||
|
}, |
||||
|
"markerImageFunction":{ |
||||
|
"title":"Marker image function: f(data, images, dsData, dsIndex)", |
||||
|
"type":"string" |
||||
|
}, |
||||
|
"markerImages":{ |
||||
|
"title":"Marker images", |
||||
|
"type":"array", |
||||
|
"items":{ |
||||
|
"title":"Marker image", |
||||
|
"type":"string" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"required":[] |
||||
|
}, |
||||
|
"form":[ |
||||
|
"defaultZoomLevel", |
||||
|
"fitMapBounds", |
||||
|
"latKeyName", |
||||
|
"lngKeyName", |
||||
|
"showLabel", |
||||
|
"label", |
||||
|
{ |
||||
|
"key": "tooltipPattern", |
||||
|
"type": "textarea" |
||||
|
}, |
||||
|
{ |
||||
|
"key":"color", |
||||
|
"type":"color" |
||||
|
}, |
||||
|
"useColorFunction", |
||||
|
{ |
||||
|
"key":"colorFunction", |
||||
|
"type":"javascript" |
||||
|
}, |
||||
|
{ |
||||
|
"key":"markerImage", |
||||
|
"type":"image" |
||||
|
}, |
||||
|
"markerImageSize", |
||||
|
"useMarkerImageFunction", |
||||
|
{ |
||||
|
"key":"markerImageFunction", |
||||
|
"type":"javascript" |
||||
|
}, |
||||
|
{ |
||||
|
"key":"markerImages", |
||||
|
"items":[ |
||||
|
{ |
||||
|
"key":"markerImages[]", |
||||
|
"type":"image" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}; |
||||
|
|
||||
|
const routeMapSettingsSchema = |
||||
|
{ |
||||
|
"schema":{ |
||||
|
"title":"Route Map Configuration", |
||||
|
"type":"object", |
||||
|
"properties":{ |
||||
|
"strokeWeight": { |
||||
|
"title": "Stroke weight", |
||||
|
"type": "number", |
||||
|
"default": 2 |
||||
|
}, |
||||
|
"strokeOpacity": { |
||||
|
"title": "Stroke opacity", |
||||
|
"type": "number", |
||||
|
"default": 1.0 |
||||
|
} |
||||
|
}, |
||||
|
"required":[ |
||||
|
] |
||||
|
}, |
||||
|
"form":[ |
||||
|
"strokeWeight", |
||||
|
"strokeOpacity" |
||||
|
] |
||||
|
}; |
||||
@ -0,0 +1,209 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
const varsRegex = /\$\{([^\}]*)\}/g; |
||||
|
|
||||
|
const linkActionRegex = /\<link-act name=['"]([^['"]*)['"]\>([^\<]*)\<\/link-act\>/g; |
||||
|
const buttonActionRegex = /\<button-act name=['"]([^['"]*)['"]\>([^\<]*)\<\/button-act\>/g; |
||||
|
|
||||
|
export function processPattern(pattern, datasources, dsIndex) { |
||||
|
var match = varsRegex.exec(pattern); |
||||
|
var replaceInfo = {}; |
||||
|
replaceInfo.variables = []; |
||||
|
while (match !== null) { |
||||
|
var variableInfo = {}; |
||||
|
variableInfo.dataKeyIndex = -1; |
||||
|
var variable = match[0]; |
||||
|
var label = match[1]; |
||||
|
var valDec = 2; |
||||
|
var splitVals = label.split(':'); |
||||
|
if (splitVals.length > 1) { |
||||
|
label = splitVals[0]; |
||||
|
valDec = parseFloat(splitVals[1]); |
||||
|
} |
||||
|
variableInfo.variable = variable; |
||||
|
variableInfo.valDec = valDec; |
||||
|
|
||||
|
var keyIndex = -1; |
||||
|
|
||||
|
if (label.startsWith('#')) { |
||||
|
var keyIndexStr = label.substring(1); |
||||
|
var n = Math.floor(Number(keyIndexStr)); |
||||
|
if (String(n) === keyIndexStr && n >= 0) { |
||||
|
keyIndex = n; |
||||
|
} |
||||
|
} |
||||
|
var offset = 0; |
||||
|
for (var i=0;i<datasources.length;i++) { |
||||
|
var datasource = datasources[i]; |
||||
|
if (dsIndex == i) { |
||||
|
if (keyIndex > -1) { |
||||
|
if (keyIndex < datasource.dataKeys.length) { |
||||
|
variableInfo.dataKeyIndex = offset + keyIndex; |
||||
|
} |
||||
|
} else { |
||||
|
for (var k = 0; k < datasource.dataKeys.length; k++) { |
||||
|
var dataKey = datasource.dataKeys[k]; |
||||
|
if (dataKey.label === label) { |
||||
|
variableInfo.dataKeyIndex = offset + k; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
offset += datasource.dataKeys.length; |
||||
|
} |
||||
|
replaceInfo.variables.push(variableInfo); |
||||
|
match = varsRegex.exec(pattern); |
||||
|
} |
||||
|
return replaceInfo; |
||||
|
} |
||||
|
|
||||
|
export function fillPattern(pattern, replaceInfo, data) { |
||||
|
var text = angular.copy(pattern); |
||||
|
for (var v = 0; v < replaceInfo.variables.length; v++) { |
||||
|
var variableInfo = replaceInfo.variables[v]; |
||||
|
var txtVal = ''; |
||||
|
if (variableInfo.dataKeyIndex > -1 && data[variableInfo.dataKeyIndex]) { |
||||
|
var varData = data[variableInfo.dataKeyIndex].data; |
||||
|
if (varData.length > 0) { |
||||
|
var val = varData[varData.length - 1][1]; |
||||
|
if (isNumber(val)) { |
||||
|
txtVal = padValue(val, variableInfo.valDec, 0); |
||||
|
} else { |
||||
|
txtVal = val; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
text = text.split(variableInfo.variable).join(txtVal); |
||||
|
} |
||||
|
return text; |
||||
|
} |
||||
|
|
||||
|
function createLink(actionName, actionText, actionCallbackName, additionalArgs) { |
||||
|
var args = 'event,\''+actionName+'\''; |
||||
|
if (additionalArgs && additionalArgs.length) { |
||||
|
args += ','+additionalArgs.join(); |
||||
|
} |
||||
|
return '<a href="#" onclick="angular.element(this).scope().'+actionCallbackName+'('+args+'); return false;">'+actionText+'</a>'; |
||||
|
} |
||||
|
|
||||
|
function createButton(actionName, actionText, actionCallbackName, additionalArgs) { |
||||
|
var args = 'event,\''+actionName+'\''; |
||||
|
if (additionalArgs && additionalArgs.length) { |
||||
|
args += ','+additionalArgs.join(); |
||||
|
} |
||||
|
return '<button onclick="angular.element(this).scope().'+actionCallbackName+'('+args+'); return false;">'+actionText+'</button>'; |
||||
|
} |
||||
|
|
||||
|
export function fillPatternWithActions(pattern, actionCallbackName, additionalArgs) { |
||||
|
var text = angular.copy(pattern); |
||||
|
var match = linkActionRegex.exec(pattern); |
||||
|
var actionTags; |
||||
|
var actionName; |
||||
|
var actionText; |
||||
|
var actionHtml; |
||||
|
while (match !== null) { |
||||
|
actionTags = match[0]; |
||||
|
actionName = match[1]; |
||||
|
actionText = match[2]; |
||||
|
actionHtml = createLink(actionName, actionText, actionCallbackName, additionalArgs); |
||||
|
text = text.split(actionTags).join(actionHtml); |
||||
|
match = linkActionRegex.exec(pattern); |
||||
|
} |
||||
|
match = buttonActionRegex.exec(pattern); |
||||
|
while (match !== null) { |
||||
|
actionTags = match[0]; |
||||
|
actionName = match[1]; |
||||
|
actionText = match[2]; |
||||
|
actionHtml = createButton(actionName, actionText, actionCallbackName, additionalArgs); |
||||
|
text = text.split(actionTags).join(actionHtml); |
||||
|
match = buttonActionRegex.exec(pattern); |
||||
|
} |
||||
|
return text; |
||||
|
} |
||||
|
|
||||
|
export function toLabelValueMap(data, datasources) { |
||||
|
var dataMap = {}; |
||||
|
var dsDataMap = []; |
||||
|
for (var d=0;d<datasources.length;d++) { |
||||
|
dsDataMap[d] = {}; |
||||
|
} |
||||
|
for (var i = 0; i < data.length; i++) { |
||||
|
var dataKey = data[i].dataKey; |
||||
|
var label = dataKey.label; |
||||
|
var keyData = data[i].data; |
||||
|
var val = null; |
||||
|
if (keyData.length > 0) { |
||||
|
val = keyData[keyData.length-1][1]; |
||||
|
} |
||||
|
dataMap[label] = val; |
||||
|
var dsIndex = datasources.indexOf(data[i].datasource); |
||||
|
dsDataMap[dsIndex][label] = val; |
||||
|
} |
||||
|
return { |
||||
|
dataMap: dataMap, |
||||
|
dsDataMap: dsDataMap |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function isNumber(n) { |
||||
|
return !isNaN(parseFloat(n)) && isFinite(n); |
||||
|
} |
||||
|
|
||||
|
export function padValue(val, dec, int) { |
||||
|
var i = 0; |
||||
|
var s, strVal, n; |
||||
|
|
||||
|
val = parseFloat(val); |
||||
|
n = (val < 0); |
||||
|
val = Math.abs(val); |
||||
|
|
||||
|
if (dec > 0) { |
||||
|
strVal = val.toFixed(dec).toString().split('.'); |
||||
|
s = int - strVal[0].length; |
||||
|
|
||||
|
for (; i < s; ++i) { |
||||
|
strVal[0] = '0' + strVal[0]; |
||||
|
} |
||||
|
|
||||
|
strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1]; |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
strVal = Math.round(val).toString(); |
||||
|
s = int - strVal.length; |
||||
|
|
||||
|
for (; i < s; ++i) { |
||||
|
strVal = '0' + strVal; |
||||
|
} |
||||
|
|
||||
|
strVal = (n ? '-' : '') + strVal; |
||||
|
} |
||||
|
|
||||
|
return strVal; |
||||
|
} |
||||
|
|
||||
|
export function arraysEqual(a, b) { |
||||
|
if (a === b) return true; |
||||
|
if (a === null || b === null) return false; |
||||
|
if (a.length != b.length) return false; |
||||
|
|
||||
|
for (var i = 0; i < a.length; ++i) { |
||||
|
if (!a[i].equals(b[i])) return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
Loading…
Reference in new issue