committed by
GitHub
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