@@ -99,4 +99,4 @@ CenterDisplayWidget.propTypes={
style: PropTypes.object,
mode: PropTypes.string
};
-export default CenterDisplayWidget;
\ No newline at end of file
+export default CenterDisplayWidget;
diff --git a/viewer/components/DepthWidgetFlex.jsx b/viewer/components/DepthWidgetFlex.jsx
index d255203f7..a11737a12 100644
--- a/viewer/components/DepthWidgetFlex.jsx
+++ b/viewer/components/DepthWidgetFlex.jsx
@@ -36,7 +36,7 @@ const DepthDisplayFlex=(props)=>{
const iprops={...props};
iprops.unit=props.dunit;
iprops.formatter=(v)=>{
- return formatter.formatDistance(v,props.dunit,props.digits,props.fillRight);
+ return formatter.formatDistance(v,props.dunit,props.digits,props.maxFrac);
}
if (iprops.offset && iprops.value != null){
iprops.value+=parseFloat(iprops.offset );
@@ -85,18 +85,19 @@ DepthDisplayFlex.predefined={
displayName:"unit",
list:DEPTH_UNITS,
default:'m',
- description:'Select the unit for the depth display'},
+ description:'Select the unit for the depth display'
+ },
digits:{
type:'NUMBER',
default:0,
- description:'minimal number of digits for the depth display, set to 0 to let the system choose',
+ description:'minimal number of digits for the depth display, 0=global default',
list:[0,10]
},
- fillRight:{
- type:'BOOLEAN',
- default: false,
- description: 'let the fractional part extend to have the requested number of digits',
- condition: {digits:(all,dv)=>dv>0}
+ maxFrac:{
+ type:'NUMBER',
+ default:1,
+ description: 'max. number of decimal places',
+ list:[0,10]
},
offset: new EditableFloatParameterUI({
name:'offset',
@@ -155,4 +156,4 @@ DepthBelowWater.predefined={
value: keys.nav.gps.depthBelowWaterline
},
caption:'DBW'
-}
\ No newline at end of file
+}
diff --git a/viewer/components/DirectWidget.jsx b/viewer/components/DirectWidget.jsx
index 1831a781b..7494fbcc5 100644
--- a/viewer/components/DirectWidget.jsx
+++ b/viewer/components/DirectWidget.jsx
@@ -1,4 +1,4 @@
-/**
+ /**
* Created by andreas on 23.02.16.
*/
@@ -7,6 +7,7 @@ import PropTypes from 'prop-types';
import Value from './Value.jsx';
import {WidgetFrame, WidgetProps} from "./WidgetBase";
import {useStringsChanged} from "../hoc/Resizable";
+import {concatsp} from "../util/helper";
const DirectWidget=(wprops)=>{
let props;
@@ -16,22 +17,31 @@ const DirectWidget=(wprops)=>{
props={...wprops,value:'Error: '+e}
}
let val;
- let vdef=props.default||'0';
- if (props.value !== undefined) {
- if (props.minValue != null && parseFloat(props.value) < props.minValue)val=vdef;
- else if(props.maxValue != null && parseFloat(props.value) > props.maxValue)val=vdef;
- else val=props.formatter?props.formatter(props.value):vdef+"";
- }
- else{
- if (! isNaN(vdef) && props.formatter) val=props.formatter(vdef);
- else val=vdef+"";
+ let vdef=props.default||'---';
+ try {
+ if (props.value != null) {
+ let outOfRange=0;
+ if(parseFloat(props.value) < props.minValue) outOfRange=-1;
+ if(parseFloat(props.value) > props.maxValue) outOfRange=+1;
+ if(outOfRange) {
+ vdef=props.formatter?props.formatter(null):vdef; // placeholder with correct with
+ if (outOfRange<0) vdef=vdef.replace(/./g,'<'); // underflow
+ if (outOfRange>0) vdef=vdef.replace(/./g,'>'); // overflow
+ throw new Error();
+ }
+ }
+ val=props.formatter?props.formatter(props.value):props.value;
+ val=(val==null?'':''+val)||vdef;
+ }catch(error){
+ val=vdef;
}
const display={
value:val
};
const resizeSequence=useStringsChanged(display,wprops.mode==='gps')
+ if(props.addClass) props.addClass=concatsp(props.addClass,'DirectWidget');
return (
-
+
@@ -59,4 +69,4 @@ DirectWidget.editableParameters={
value: true
};
-export default DirectWidget;
\ No newline at end of file
+export default DirectWidget;
diff --git a/viewer/components/Value.jsx b/viewer/components/Value.jsx
index 822d78aec..88660df4f 100644
--- a/viewer/components/Value.jsx
+++ b/viewer/components/Value.jsx
@@ -8,8 +8,11 @@ import PropTypes from 'prop-types';
*/
const Value=function(props){
if (! props.value) return null;
- let prefix=(props.value+"").replace(/[^ ].*/,'');
- let remain=(props.value+"").replace(/^ */,'');
+ let val=''+props.value;
+ val=val.replaceAll('-','\u2012'); // replace - by digit wide hyphen (figure dash)
+ val=val.replaceAll(':','\uA789'); // replace : with raised colon, looks better in time format 00:00
+ let prefix=val.replace(/[^ ].*/,'');
+ let remain=val.replace(/^ */,'');
return(
{prefix.replace(/ /g,'0')}
@@ -21,4 +24,4 @@ const Value=function(props){
Value.propTypes={
value: PropTypes.string
}
-export default Value;
\ No newline at end of file
+export default Value;
diff --git a/viewer/components/WidgetList.js b/viewer/components/WidgetList.js
index 46a8d2d02..2b902f167 100644
--- a/viewer/components/WidgetList.js
+++ b/viewer/components/WidgetList.js
@@ -22,10 +22,10 @@ import {SKPitchWidget, SKRollWidget} from "./SKWidgets";
import {CombinedWidget} from "./CombinedWidget";
import Formatter from "../util/formatter";
import {DepthBelowKeel, DepthBelowTransducer, DepthBelowWater} from "./DepthWidgetFlex";
+const degrees='\u00b0';
let widgetList=[
{
name: 'SOG',
- default: "---",
caption: 'SOG',
storeKeys: {
value: keys.nav.gps.speed,
@@ -38,8 +38,7 @@ let widgetList=[
},
{
name: 'COG',
- default: "---",
- unit: "\u00b0",
+ unit: degrees,
caption: 'COG',
storeKeys:{
value: keys.nav.gps.course,
@@ -47,70 +46,128 @@ let widgetList=[
},
formatter: 'formatDirection360',
editableParameters: {
- formatterParameters: true
+ unit: false,
}
},
{
name: 'HDM',
- default: "---",
- unit: "\u00b0",
+ unit: degrees,
caption: 'HDM',
storeKeys:{
value: keys.nav.gps.headingMag
},
formatter: 'formatDirection360',
editableParameters: {
- formatterParameters: true
+ unit: false,
}
},
{
name: 'HDT',
- default: "---",
- unit: "\u00b0",
+ unit: degrees,
caption: 'HDT',
storeKeys:{
value: keys.nav.gps.headingTrue
},
formatter: 'formatDirection360',
editableParameters: {
- formatterParameters: true
+ unit: false,
}
},
{
name: 'Position',
- default: "-------------",
caption: 'BOAT',
storeKeys:{
value: keys.nav.gps.position,
- isAverage: keys.nav.gps.positionAverageOn
+ isAverage: keys.nav.gps.positionAverageOn,
+ gpsValid: keys.nav.gps.valid,
+ },
+ formatter: 'formatLonLats',
+ editableParameters: {
+ unit: false,
+ },
+ translateFunction: (props)=>{
+ return {...props,
+ unit: props.gpsValid?'OK':'ERROR',
+ addClass: props.gpsValid?'ok':'error',
+ }
},
- formatter: 'formatLonLats'
-
},
{
name: 'TimeStatus',
caption: 'GPS',
wclass: TimeStatusWidget,
- storeKeys: TimeStatusWidget.storeKeys
+ storeKeys: TimeStatusWidget.storeKeys,
+ },
+ {
+ name: 'GNSSStatus',
+ caption: 'GNSS Status',
+ storeKeys:{
+ fix: keys.nav.gps.fixType,
+ qual: keys.nav.gps.fixQuality,
+ sats: keys.nav.gps.satInview,
+ used: keys.nav.gps.satUsed,
+ hdop: keys.nav.gps.HDOP,
+ valid: keys.nav.gps.valid,
+ },
+ formatter: 'formatString',
+ editableParameters: {
+ unit: false,
+ value: false,
+ },
+ translateFunction: (props)=>{
+ const ok = props.valid && (props.fix??3)>1 && (props.qual??1)>0;
+ const warn = (props.hdop??0)>5;
+ let q = props.qual??'';
+ if(q==0) q='';
+ if(q==1) q='';
+ if(q==2) q='SBAS';
+ if(q==4) q='fixed RTK';
+ if(q==5) q='floating RTK';
+ if(q==6) q='dead reckoning';
+ if(q==8) q='simulated';
+ return {...props,
+ unit: warn?'HDOP':ok?'OK':'ERR',
+ addClass: warn?'warning':ok?'ok':'error',
+ value: `${props.fix??'-'}D ${props.used??'--'}/${props.sats??'--'} H${props.hdop??'--.-'} ${q}`
+ }
+ },
},
{
name: 'ETA',
- caption: 'ETA',
- wclass: EtaWidget,
- storeKeys: EtaWidget.storeKeys
+ storeKeys:{
+ value: keys.nav.wp.eta,
+ time:keys.nav.gps.rtime,
+ name: keys.nav.wp.name,
+ server: keys.nav.wp.server
+ },
+ formatter: 'formatTime',
+ translateFunction: (props)=>{
+ return {...props,
+ value: !props.value?null:props.kind=='TTG'?new Date(props.value-props.time):props.value,
+ unit: props.name,
+ caption: (props.caption||' ')+props.kind,
+ disconnect: props.server === false,
+ addClass: (!props.formatterParameters||props.formatterParameters?.[0])?'med':null,
+ }
+ },
+ editableParameters: {
+ unit: false,
+ kind: {type:'SELECT',list:['ETA','TTG'],default:'ETA'},
+ }
},
{
name: 'DST',
- default: "---",
caption: 'DST',
storeKeys:{
value: keys.nav.wp.distance,
+ name: keys.nav.wp.name,
server: keys.nav.wp.server
},
- updateFunction: (state)=>{
+ translateFunction: (state)=>{
return {
value: state.value,
+ caption: state.caption+' '+state.name,
disconnect: state.server === false
}
},
@@ -122,20 +179,27 @@ let widgetList=[
},
{
name: 'BRG',
- default: "---",
- unit: "\u00b0",
+ unit: degrees,
caption: 'BRG',
storeKeys:{
- value: keys.nav.wp.course
+ value: keys.nav.wp.course,
+ name: keys.nav.wp.name,
+ server: keys.nav.wp.server
+ },
+ translateFunction: (state)=>{
+ return {
+ value: state.value,
+ caption: state.caption+' '+state.name,
+ disconnect: state.server === false
+ }
},
formatter: 'formatDirection360',
editableParameters: {
- formatterParameters: true
+ unit: false,
}
},
{
name: 'VMG',
- default: "---",
caption: 'VMG',
storeKeys: {
value: keys.nav.wp.vmg
@@ -148,7 +212,6 @@ let widgetList=[
},
{
name: 'STW',
- default: '---',
caption: 'STW',
storeKeys:{
value: keys.nav.gps.waterSpeed
@@ -160,8 +223,7 @@ let widgetList=[
},
{
name: 'WindAngle',
- default: "---",
- unit: "\u00b0",
+ unit: degrees,
caption: 'Wind Angle',
storeKeys:WindStoreKeys,
formatter: 'formatString',
@@ -170,6 +232,7 @@ let widgetList=[
formatter: false,
value: false,
caption: false,
+ unit: false,
kind: {type:'SELECT',list:['auto','trueAngle','trueDirection','apparent'],default:'auto'},
show360: {type:'BOOLEAN',default: false,description:'always show 360°'},
leadingZero:{type:'BOOLEAN',default: false,description:'show leading zeroes (012)'}
@@ -177,8 +240,8 @@ let widgetList=[
translateFunction: (props)=>{
const captions={
A:'AWA',
+ TA: 'TWA',
TD: 'TWD',
- TA: 'TWA'
};
const formatter={
A: (v)=>Formatter.formatDirection(v,undefined,!props.show360,props.leadingZero),
@@ -195,7 +258,6 @@ let widgetList=[
},
{
name: 'WindSpeed',
- default: "---",
caption: 'Wind Speed',
storeKeys:WindStoreKeys,
formatter: 'formatSpeed',
@@ -220,31 +282,34 @@ let widgetList=[
},
{
name: 'WaterTemp',
- default: '---',
- unit: '°',
caption: 'Water Temp',
storeKeys: {
value: keys.nav.gps.waterTemp
},
formatter: 'formatTemperature',
- formatterParameters: 'celsius'
+ editableParameters: {
+ unit: false,
+ },
+ translateFunction: (props)=>{
+ let u=(props?.formatterParameters?.[0]||'').toUpperCase()[0]||'';
+ return {...props, unit: '°'+u }
+ }
},
{
name: 'AnchorBearing',
- default: "---",
- unit: "\u00b0",
+ unit: degrees,
caption: 'ACHR-BRG',
storeKeys:{
value:keys.nav.anchor.direction
},
formatter: 'formatDirection360',
editableParameters: {
+ unit: false,
formatterParameters: true
}
},
{
name: 'AnchorDistance',
- default: "---",
caption: 'ACHR-DST',
storeKeys:{
value:keys.nav.anchor.distance
@@ -257,7 +322,6 @@ let widgetList=[
},
{
name: 'AnchorWatchDistance',
- default: "---",
caption: 'ACHR-WATCH',
storeKeys:{
value:keys.nav.anchor.watchDistance
@@ -271,51 +335,81 @@ let widgetList=[
{
name: 'RteDistance',
- default: "---",
- caption: 'RTE-Dst',
+ caption: 'RTE-DST',
storeKeys:{
- value:keys.nav.route.remain
+ value:keys.nav.route.remain,
+ server: keys.nav.wp.server,
},
editableParameters: {
unit:false
},
- formatter: 'formatDistance'
+ formatter: 'formatDistance',
+ translateFunction: (props)=>{
+ return {...props,
+ disconnect: props.server === false,
+ }
+ },
},
{
name: 'RteEta',
- default: " --:--:-- ",
- unit: "h",
- caption: 'RTE-ETA',
storeKeys:{
- value:keys.nav.route.eta
+ value:keys.nav.route.eta,
+ time:keys.nav.gps.rtime,
+ server: keys.nav.wp.server,
},
- formatter: 'formatTime'
+ formatter: 'formatTime',
+ translateFunction: (props)=>{
+ return {...props,
+ value: !props.value?null:props.kind=='TTG'?new Date(props.value-props.time):props.value,
+ caption: (props.caption||'RTE-')+props.kind,
+ disconnect: props.server === false,
+ addClass: props.formatterParameters?.[0]?'med':null,
+ }
+ },
+ editableParameters: {
+ unit: false,
+ kind: {type:'SELECT',list:['ETA','TTG'],default:'ETA'},
+ }
},
{
name: 'LargeTime',
- default: "--:--",
caption: 'Time',
storeKeys:{
- value:keys.nav.gps.rtime
+ value:keys.nav.gps.rtime,
+ gpsValid: keys.nav.gps.valid,
+ visible: keys.properties.showClock
},
- formatter: 'formatClock'
+ formatter: 'formatTime',
+ translateFunction: (props)=>{
+ return {...props,
+ unit: props.gpsValid?'OK':'ERROR',
+ addClass: (props.gpsValid?'ok':'error')+((!props.formatterParameters||props.formatterParameters?.[0])?' med':''),
+ }
+ },
+ editableParameters: {
+ unit: false,
+ }
},
{
name: 'WpPosition',
- default: "-------------",
caption: 'MRK',
storeKeys:{
value:keys.nav.wp.position,
- server: keys.nav.wp.server
+ server: keys.nav.wp.server,
+ name: keys.nav.wp.name
},
updateFunction: (state)=>{
return {
value: state.value,
+ unit: state.name,
disconnect: state.server === false
}
},
- formatter: 'formatLonLats'
+ formatter: 'formatLonLats',
+ editableParameters: {
+ unit: false,
+ },
},
{
name: 'Zoom',
@@ -342,23 +436,26 @@ let widgetList=[
name: 'WindDisplay',
wclass: WindWidget,
},
+ {
+ name: 'WindGraphics',
+ wclass: WindGraphics
+ },
{
name: 'DepthDisplay',
- default: "---",
- caption: 'DPT',
- unit: 'm',
+ caption: 'DBT',
storeKeys:{
value:keys.nav.gps.depthBelowTransducer
},
- formatter: 'formatDecimal',
- formatterParameters: [3,1,true],
+ formatter: 'formatDistance',
+ formatterParameters: ['m',3,1],
editableParameters: {
- maxValue: {type:'NUMBER',default:12000,description:'consider any value above this (in meters) as invalid'}
+ unit: false,
+ maxValue: {type:'NUMBER',default:999,description:'consider any value above this (in meters) as invalid'}
}
},
{
- name: 'DepthBelowTransducer',
- wclass: DepthBelowTransducer
+ name: 'DepthBelowTransducer',
+ wclass: DepthBelowTransducer
},
{
name: 'DepthBelowKeel',
@@ -372,13 +469,13 @@ let widgetList=[
name: 'XteDisplay',
wclass: XteWidget,
},
- {
- name: 'WindGraphics',
- wclass: WindGraphics
- },
{
name: "DateTime",
- wclass: DateTimeWidget
+ caption: 'Date/Time',
+ storeKeys:{
+ value:keys.nav.gps.rtime
+ },
+ formatter: 'formatDateTime'
},
{
name: 'Empty',
@@ -414,7 +511,6 @@ let widgetList=[
},
{
name: 'Default', //a way to access the default widget providing all parameters in the layout
- default: "---",
},
{
name: 'RadialGauge',
@@ -430,7 +526,6 @@ let widgetList=[
},
{
name: 'signalKPressureHpa',
- default: "---",
formatter: 'skPressure',
editableParameters: {
unit:false
@@ -438,9 +533,14 @@ let widgetList=[
},
{
name:'signalKCelsius',
- default: "---",
- unit:'°',
- formatter: 'skTemperature'
+ formatter: 'skTemperature',
+ editableParameters: {
+ unit: false,
+ },
+ translateFunction: (props)=>{
+ let u=(props?.formatterParameters?.[0]||'').toUpperCase()[0]||'';
+ return {...props, unit: '°'+u }
+ }
},
{
name: 'signalKRoll',
diff --git a/viewer/components/WindGraphics.jsx b/viewer/components/WindGraphics.jsx
index 48fa46879..93fb9a98f 100644
--- a/viewer/components/WindGraphics.jsx
+++ b/viewer/components/WindGraphics.jsx
@@ -75,7 +75,7 @@ const WindGraphics = (props) => {
ctx.arc(width / 2, height / 2, radius * 0.97, 0, 2 * Math.PI);
ctx.stroke();
let start, end;
- if (current.suffix === 'A') {
+ if (current.suffix.endsWith('A')) {
// Write left partial circle
ctx.beginPath();
ctx.strokeStyle = colors.red; // red
@@ -115,12 +115,8 @@ const WindGraphics = (props) => {
// Move the pointer from 0,0 to center position
ctx.translate(width / 2, height / 2);
ctx.font = fontSize + "px "+globalstore.getData(keys.properties.fontBase);
- let show180=false;
- if (!props.show360 && current.suffix !== 'TD') {
- if (winddirection > 180) winddirection -= 360;
- show180=true;
- }
- let txt = Formatter.formatDirection(winddirection,undefined,show180,true);
+ let a180 = !(props.show360 || current.suffix.endsWith('D'));
+ let txt = Formatter.formatDirection(winddirection,false,a180,true);
let xFactor = -1.0;
if (winddirection < 0) xFactor = -1.0;
ctx.fillStyle = colors.text;
@@ -141,13 +137,16 @@ const WindGraphics = (props) => {
setTimeout(drawWind, 0);
}
setTimeout(drawWind, 0);
- let current = getWindData(props);
- let windSpeed = props.formatter(current.windSpeed);
+ let wind = getWindData(props);
+ let a180 = !(props.show360 || wind.suffix.endsWith('D'));
+ let angle = Formatter.formatDirection(wind.windAngle,false,a180);
+ let unit = ((props.formatterParameters instanceof Array) && props.formatterParameters.length > 0) ? props.formatterParameters[0] : 'kn';
+ let speed = Formatter.formatSpeed(wind.windSpeed,unit);
return (
-
+
- {windSpeed}
- {current.suffix}
+ {speed}
+ {wind.suffix}
);
@@ -172,7 +171,6 @@ WindGraphics.predefined= {
default: 'auto',
description:'which wind data to be shown\nauto will try apparent, trueAngle, trueDirection and display the first found data'
},
- formatter: true,
formatterParameters: true,
caption: true
},
@@ -180,4 +178,4 @@ WindGraphics.predefined= {
caption: 'Wind'
}
-export default WindGraphics;
\ No newline at end of file
+export default WindGraphics;
diff --git a/viewer/components/WindWidget.jsx b/viewer/components/WindWidget.jsx
index 82069e1dc..01d923827 100644
--- a/viewer/components/WindWidget.jsx
+++ b/viewer/components/WindWidget.jsx
@@ -16,21 +16,13 @@ export const getWindData=(props)=>{
if (kind !== 'true' && kind !== 'apparent' && kind !== 'trueAngle' && kind !== 'trueDirection') kind='auto';
if (kind === 'auto'){
if (props.windAngle !== undefined && props.windSpeed !== undefined){
- windAngle=props.windAngle;
- windSpeed=props.windSpeed;
- suffix='A';
- }
- else{
- if (props.windAngleTrue !== undefined){
- windAngle=props.windAngleTrue;
- windSpeed=props.windSpeedTrue;
- suffix="TA";
- }
- else{
- windAngle=props.windDirectionTrue;
- windSpeed=props.windSpeedTrue;
- suffix="TD";
- }
+ kind = 'apparent';
+ } else if (props.windAngleTrue !== undefined && props.windSpeedTrue !== undefined){
+ kind = 'trueAngle';
+ } else if (props.windDirectionTrue !== undefined && props.windSpeedTrue !== undefined){
+ kind = 'trueDirection';
+ } else {
+ kind = 'apparent';
}
}
if (kind === 'apparent'){
@@ -71,7 +63,6 @@ export const WindProps={
}
const WindWidget = (props) => {
- let wind = getWindData(props);
const names = {
A: {
speed: 'AWS',
@@ -86,32 +77,30 @@ const WindWidget = (props) => {
angle: 'TWA'
}
}
- let windSpeedStr = props.formatter(wind.windSpeed);
- let show180=false;
- if (!props.show360 && wind.suffix !== 'TD') {
- show180=true;
- if (wind.windAngle > 180) wind.windAngle -= 360;
- }
+ let wind = getWindData(props);
+ let a180 = !(props.show360 || wind.suffix.endsWith('D'));
+ let angle = Formatter.formatDirection(wind.windAngle,false,a180,true);
+ let unit = ((props.formatterParameters instanceof Array) && props.formatterParameters.length > 0) ? props.formatterParameters[0] : 'kn';
+ let speed = Formatter.formatSpeed(wind.windSpeed,unit);
return (
-
+
{(props.mode === 'horizontal') ?
-
+
- {Formatter.formatDirection(wind.windAngle,undefined,show180)}
- °
- /{windSpeedStr}
- {props.unit}
+ {angle}/{speed}
:
-
- {Formatter.formatDirection(wind.windAngle,undefined,show180)}
-
-
- {windSpeedStr}
-
+
+
}
@@ -128,6 +117,7 @@ WindWidget.propTypes={
WindWidget.predefined= {
storeKeys: WindStoreKeys,
+ formatter: 'formatSpeed',
editableParameters: {
show360: {type: 'BOOLEAN', default: false},
kind: {
@@ -136,10 +126,9 @@ WindWidget.predefined= {
default: 'auto',
description: 'which wind data to be shown\nauto will try apparent, trueAngle, trueDirection and display the first found data'
},
- formatter: true,
+ formatter: false,
formatterParameters: true
},
- formatter :'formatSpeed'
-}
+};
-export default WindWidget;
\ No newline at end of file
+export default WindWidget;
diff --git a/viewer/components/XteWidget.jsx b/viewer/components/XteWidget.jsx
index 981078976..5af8d3de2 100644
--- a/viewer/components/XteWidget.jsx
+++ b/viewer/components/XteWidget.jsx
@@ -105,10 +105,10 @@ XteWidget.predefined={
markerXte: keys.nav.wp.xte,
},
editableParameters:{
- xteMax:{type:'FLOAT',displayName:"XTE max",default:1,list:[0.1,10], description:'The end points of the XTE graph (1.0).\nAlways provide this in the unit you choose for the formatter'},
+ xteMax:{type:'FLOAT',displayName:"XTE max",default:1,list:[0.01,100], description:'The end points of the XTE graph (1.0).\nAlways provide this in the unit you choose for the formatter'},
formatterParameters:true
},
formatter: 'formatDistance'
};
-export default XteWidget;
\ No newline at end of file
+export default XteWidget;
diff --git a/viewer/style/avnav_viewer_new.less b/viewer/style/avnav_viewer_new.less
index 239a905f3..c98d0dedb 100644
--- a/viewer/style/avnav_viewer_new.less
+++ b/viewer/style/avnav_viewer_new.less
@@ -1204,6 +1204,9 @@ span.valuePrefix{
.mdText2();
width: 6em;
}
+ .unit {
+ font-weight: normal;
+ }
.aisData {
display: inline-block;
text-align: left;
diff --git a/viewer/style/properties.less b/viewer/style/properties.less
index 45945dac0..ed6ad1d5d 100644
--- a/viewer/style/properties.less
+++ b/viewer/style/properties.less
@@ -33,6 +33,7 @@
--avnav-night-opacity: 1;
--avnav-headline-height: 4em;
//widgets
+ --avnav-widget-border-width: 2px;
--avnav-left-widgets-width: 8.8em;
--avnav-horizontal-widgets-height: 4em;
--avnav-widget-head-color: @_widgetHeadColor; //header background
@@ -59,4 +60,4 @@
--avnav-widget-head-color: rgba(50,47,47,0.6);
--avnav-widget-color: var(--avnav-back-color); //widget background
--avnav-widget-fore-color: var(--avnav-fore-color);
-}
\ No newline at end of file
+}
diff --git a/viewer/style/widgets.less b/viewer/style/widgets.less
index 09980f161..120b97bc4 100644
--- a/viewer/style/widgets.less
+++ b/viewer/style/widgets.less
@@ -12,26 +12,37 @@
font-size: @infoFontSize;
opacity: 0.7;
}
+#navpage {
+ .widgetContainer {
+ padding: var(--avnav-widget-border-width);
+ gap: var(--avnav-widget-border-width);
+ }
+ .widgetContainer:empty { display: none; }
+ .widgetContainer.vertical { padding-bottom: 0; }
+ .widgetContainer.bottomLeft { padding-right: 0; }
+}
.widget{
position: relative;
z-index: 100;
- margin: 0.1em;
+ margin: 0;
overflow: hidden;
pointer-events: all;
+ font-variant-numeric: tabular-nums;
background: var(--avnav-back-color);
color:var(--avnav-fore-color);
.flex-shrink(0);
.flex-grow(1);
+ .flex-basis(auto);
.flex-display();
.flex-direction(column);
.flex-justify-content(flex-start);
.widgetHead {
margin: 0;
- padding: 0.1em;
+ padding: 0 0.2em;
display: flex;
.flex-direction(row);
.flex-justify-content(space-between);
- height: 0.7em;
+ white-space: nowrap;
.infoLeft{
.widgetInfo();
}
@@ -40,14 +51,16 @@
}
}
.widgetData{
- text-align: right;
- max-width: calc(100% - 0.2em);
- margin-left: auto;
- margin-right: 0.1em;
+ text-align: center;
+ padding: 0 0.1em;
+ width: 100%;
max-height: 100%;
min-height: 0;
min-width: 0;
}
+ canvas.widgetData{
+ padding: 0;
+ }
&.average .infoLeft{
color:var(--avnav-attention-color);
}
@@ -57,6 +70,7 @@
}
.editing &{
.flex-grow(0);
+ min-width: 3em;
}
#gpspage.editing &{
.flex-grow(1);
@@ -86,7 +100,7 @@
.widgetData{
padding-top: 0;
display: block;
- white-space: pre;
+ white-space: pre-wrap;
}
.centeredWidget {
.widgetData{
@@ -106,7 +120,7 @@
}
.widgetContainer.horizontal{
.flex-wrap(wrap);
- .flex-align-items(center);
+ .flex-align-items(top);
max-height: @horizontalContainerHeight;
.twoRows &{
max-height: @horizontalContainerDoubleHeight;
@@ -131,11 +145,9 @@
.widget{
min-width: 0;
.flex-shrink(0);
- margin-left: 0.1em;
- margin-right: 0.1em;
- padding-left: 0.1em;
- padding-right: 0.1em;
- width: calc(100% - 0.2em);
+ margin-left: 0;
+ margin-right: 2px;
+ width: 100%;
}
.editing &{
.widget{
@@ -159,7 +171,7 @@
}
//------------------ dedicated widgets -------------------------
-//widgets have their name from the widget list and maybe some additonial fixed name as classes
+//widgets have their name from the widget list and maybe some additional fixed name as classes
@bigFont: 3em;
@bigFontVertical: 2em;
@smallFont: 1em;
@@ -168,101 +180,96 @@
@size1: 7em;
@size15: 9em;
@size2: 11em;
+
+.blink(@period:1s) {
+ animation: blinker @period linear infinite;
+}
+
+@keyframes blinker {
+ 50% { opacity: 0; }
+}
+
.widget{
+ width: min-content; // allow dynamic scaling to size of content
background-color: var(--avnav-widget-color);
color: var(--avnav-widget-fore-color);
- .bigWidget(@size){
+
+ .widgetHead {
+ background: var(--avnav-widget-head-color);
+ }
+
+ .widgetData {
+ line-height: 1em;
+// background: yellow;
+ }
+
+ .bigWidget(){
.widgetData{
font-size: @bigFont;
+ white-space: nowrap;
}
- width: @size;
- .vertical &{
- .widgetData{
+ .vertical & .widgetData {
font-size: @bigFontVertical;
}
}
- }
- .smallWidget(@size){
+
+ .medWidget(){
.widgetData{
- font-size: @smallFont;
+ text-align: center;
+ font-size: @timeFont;
+ white-space: normal;
}
- width: @size;
- .vertical &{
- .widgetData{
- font-size: @smallFont;
- }
+ .horizontal & .widgetData {
+ padding-top: 0.5em;
}
}
- .timeWidget(@font){
- .widgetData{
- font-size: @font;
- }
- width: 7em;
- .vertical &{
+
+ .smallWidget(){
.widgetData{
- font-size: @font;
- }
- }
- }
- &.SOG{
- .bigWidget(@size2);
- }
- &.VMG{
- .bigWidget(@size2);
- }
- &.COG{
- .bigWidget(@size1);
- }
- &.BRG{
- .bigWidget(@size1);
- }
- &.DST{
- .bigWidget(@size2);
- }
- &.WindAngle,&.WindSpeed{
- .bigWidget(@size1);
- }
- &.AnchorBearing{
- .bigWidget(@size1);
- }
- &.AnchorDistance{
- .bigWidget(@size2);
- }
- &.AnchorWatchDistance{
- .bigWidget(@size15);
- }
- &.RteDistance{
- .bigWidget(@size2);
- }
- &.RteDistance{
- .timeWidget(@timeFont);
- }
- &.LargeTime{
- .timeWidget(@clockFont);
- }
- &.zoomWidget{
- .smallWidget(@size1);
- .widgetData{
- text-align: center;
- font-size: @timeFont;
+ font-size: @smallFont;
+ line-height: 1.2em;
+ white-space: normal;
}
- .vertical &{
- .widgetData{
- font-size: @timeFont;
+ .horizontal & .widgetData {
+ padding-top: 0.2em;
}
}
- .rzoom{
- display: inline-block;
- }
+
+ .bigWidget(); // big is the default
+
+ // shortcuts for manual use
+ &.big { .bigWidget(); }
+ &.med { .medWidget(); }
+ &.small { .smallWidget(); }
+
+ &.DateTime { .smallWidget(); }
+// &.ETA { .medWidget(); }
+// &.RteEta { .medWidget(); }
+// &.TimeStatus { .medWidget(); }
+ &.GNSSStatus { .smallWidget(); }
+ &.error .infoRight {
+ .blink();
+ color: red;
+ font-weight: bold;
+ }
+ &.warning .infoRight {
+ .blink(2s);
+ color: orange;
+ font-weight: bold;
+ }
+ &.ok .infoRight {
+ opacity: 1;
+ color: green;
}
&.Position,&.WpPosition{
- .smallWidget(@size1);
+ .smallWidget();
.widgetData{
text-align: center;
+ font-family: monospace;
}
}
&.timeStatusWidget{
- .smallWidget(@size1);
+ .smallWidget();
.status{
width: 1.5em;
height: 1.5em;
@@ -282,7 +289,7 @@
}
}
&.etaWidget{
- .smallWidget(@size1);
+ .smallWidget();
.widgetData{
text-align: center;
margin-left: auto;
@@ -294,14 +301,21 @@
}
}
&.aisTargetWidget{
- .smallWidget(@size1);
+ .smallWidget();
.aisFront{
display: inline-block;
font-size: 1.5em;
+ line-height: 1.2em;
}
.label{
width: 2em;
}
+ .widgetData{
+ display: flex;
+ span{
+ flex: 1 0 1em;
+ }
+ }
.widgetData ~ .widgetData{
padding-top: 0;
}
@@ -342,15 +356,17 @@
margin: 0;
}
}
+ .widgetData{
+ display: block;
+ }
}
-
}
&.activeRouteWidget{
&.approach{
background-color: var(--avnav-attention-color);
}
- .smallWidget(@size1);
+ .smallWidget();
.routeName{
margin-right: 0.2em;
.mdText2();
@@ -403,7 +419,7 @@
border: @borderAttentionLarge;
border-color: var(--avnav-attention-color);
}
-.smallWidget(@size1);
+.smallWidget();
.widgetData{
.routeInfo {
width: 4.5em;
@@ -437,7 +453,7 @@
}
&.centerDisplayWidget{
-.smallWidget(@size1);
+ .smallWidget();
.widgetData ~ .widgetData{
margin-top: 0;
}
@@ -464,28 +480,33 @@
.horizontal &{
min-width: 10em;
}
+ .Position {
+ font-family: monospace;
+ }
}
&.windWidget{
-.smallWidget(@size15);
-padding-top: 0;
-padding-left: 0;
-padding-right: 0;
+ .smallDisplay & {
+ .medWidget();
+ .resize {
+ flex-direction: column;
+ }
.widgetData{
- font-size: @timeFont;
+ padding-top: 0.5em;
+ }
}
}
&.DepthDisplay{
-.bigWidget(@size2);
+ .bigWidget();
}
&.xteWidget{
-.smallWidget(@size1);
+ .smallWidget();
canvas{
margin-right: auto;
margin-left: auto;
}
}
&.windGraphics{
-.smallWidget(@size2);
+ .smallWidget();
.windSpeed {
text-align: right;
font-size: @timeFont;
@@ -506,8 +527,8 @@ canvas{
}
}
canvas{
- height: 90%;
- width: 90%;
+// width: 85%;
+ height: 80%;
}
.vertical &{
height: 11em;
diff --git a/viewer/util/formatter.js b/viewer/util/formatter.js
index e764f1410..50b04d722 100644
--- a/viewer/util/formatter.js
+++ b/viewer/util/formatter.js
@@ -15,37 +15,49 @@ function pad(num, size, pad='0') {
* @param axis
* @returns {string}
*/
-const formatLonLatsDecimal=function(coordinate,axis){
- coordinate = Helper.to180(coordinate); // normalize to ±180°
-
- let abscoordinate = Math.abs(coordinate);
- let coordinatedegrees = Math.floor(abscoordinate);
-
- let coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
- let numdecimal=2;
- //correctly handle the toFixed(x) - will do math rounding
- if (coordinateminutes.toFixed(numdecimal) == 60){
- coordinatedegrees+=1;
- coordinateminutes=0;
- }
- if( coordinatedegrees < 10 ) {
- coordinatedegrees = "0" + coordinatedegrees;
- }
- if (coordinatedegrees < 100 && axis == 'lon'){
- coordinatedegrees = "0" + coordinatedegrees;
+const formatLonLatsDecimal=function(coordinate,axis,format='DDM',hemFirst=false){
+ if(coordinate==null) {
+ let str="____\u00B0__.___'";
+ if(format=='DD') str="____._____\u00B0"; // use _ to prevent line breaks
+ if(format=='DMS') str="____\u00B0__'__._\"";
+ return hemFirst?'_'+str:str+'_';
}
- let str = coordinatedegrees + "\u00B0";
-
- if( coordinateminutes < 10 ) {
- str +="0";
- }
- str += coordinateminutes.toFixed(numdecimal) + "'";
+ coordinate = Helper.to180(coordinate); // normalize to ±180°
+ let deg = Math.abs(coordinate);
+ let padding = 2;
+ let str = '\u00A0';
+ let hem = coordinate < 0 ? "S" :"N";
if (axis == "lon") {
- str += coordinate < 0 ? "W" :"E";
+ padding = 3;
+ str = '';
+ hem = coordinate < 0 ? "W" :"E";
+ }
+ if(format=='DD') {
+ str += pad(deg.toFixed(5),padding+6) + "\u00B0";
+ } else if(format=='DMS') {
+ let DEG = Math.floor(deg);
+ let min = 60*(deg-DEG);
+ let MIN = Math.floor(min);
+ let sec = 60*(min-MIN);
+ if (sec.toFixed(1).startsWith('60.')){
+ MIN+=1;
+ sec=0;
+ if(MIN==60){
+ MIN=0;
+ DEG+=1;
+ }
+ }
+ str += pad(DEG,padding) + "\u00B0" + pad(MIN,2) + "'" + pad(sec.toFixed(1),4) + '"';
} else {
- str += coordinate < 0 ? "S" :"N";
+ let DEG = Math.floor(deg);
+ let min = 60*(deg-DEG);
+ if (min.toFixed(3).startsWith('60.')){
+ DEG+=1;
+ min=0;
+ }
+ str += pad(DEG,padding) + "\u00B0" + pad(min.toFixed(3),6) + "'";
}
- return str;
+ return hemFirst?hem+str:str+hem;
};
/**
@@ -53,114 +65,100 @@ const formatLonLatsDecimal=function(coordinate,axis){
* @param {Point} lonlat
* @returns {string}
*/
-const formatLonLats=function(lonlat){
- if (! lonlat || isNaN(lonlat.lat) || isNaN(lonlat.lon)){
- return "-----";
- }
- let ns=this.formatLonLatsDecimal(lonlat.lat, 'lat');
- let ew=this.formatLonLatsDecimal(lonlat.lon, 'lon');
- return ns + ', ' + ew;
+const formatLonLats=function(lonlat,format='DDM',hemFirst=false){
+ let lat=this.formatLonLatsDecimal(lonlat?.lat, 'lat', format, hemFirst);
+ let lon=this.formatLonLatsDecimal(lonlat?.lon, 'lon', format, hemFirst);
+ return lat + ' ' + lon;
};
-formatLonLats.parameters=[];
+formatLonLats.parameters=[
+ {name:'format',type:'SELECT',list:['DD','DDM','DMS'],default:'DDM'},
+ {name:'hemFirst',type:'BOOLEAN',default:false}
+];
+
/**
* format a number with a fixed number of fractions
* @param number
- * @param fix
- * @param fract
- * @param addSpace if set - add a space for positive numbers
- * @param prefixZero if set - use 0 instead of space to fill the fixed digits
- * @returns {string}
+ * @param fix number of integer digits (before .)
+ * @param fract number of fractional digits (after .)
+ * @param addSpace if set - add a padding space for sign
+ * @param prefixZero if set - print leading zeroes, not space
+ * @returns number as string, always with decimal point
*/
const formatDecimal=function(number,fix,fract,addSpace,prefixZero){
- let sign="";
number=parseFloat(number);
- if (isNaN(number)){
- let rt="";
- while (fix > 0) {
- rt+="-";
- fix--;
- }
- return rt;
- }
- if (addSpace !== undefined && addSpace) sign=" ";
- if (number < 0) {
- number=-number;
- sign="-";
- }
- let rt=(prefixZero?"":sign)+number.toFixed(fract);
- let v=10;
- fix-=1;
- while (fix > 0){
- if (number < v){
- if (prefixZero) rt="0"+rt;
- else rt=" "+rt;
- }
- v=v*10;
- fix-=1;
+ if (!isFinite(number)) return '-'.repeat(fix)+(fract?'.'+'-'.repeat(fract):'');
+ let sign = addSpace ? ' ' : '';
+ if (number < 0) { number=-number; sign='-'; }
+ let str = number.toFixed(fract); // formatted number w/o sign
+ let n = fix+fract+(fract?1:0); // expected length of string w/o sign
+ if(prefixZero || fix<0) {
+ return sign+'0'.repeat(Math.max(0,n-str.length))+str; // add sign and padding zeroes
+ } else {
+ return ' '.repeat(Math.max(0,n-str.length))+sign+str; // add padding spaces and sign
}
- return prefixZero?(sign+rt):rt;
};
formatDecimal.parameters=[
- {name:'fix',type:'NUMBER'},
- {name: 'fract',type:'NUMBER'},
- {name: 'addSpace',type:'BOOLEAN'},
- {name: 'prefixZero',type:'BOOLEAN'}
+ {name:'fix',type:'NUMBER',description:'number of integer digits (before .)'},
+ {name:'fract',type:'NUMBER',description:'number of fractional digits (after .)'},
+ {name:'addSpace',type:'BOOLEAN',description:'add single padding space for sign'},
+ {name:'prefixZero',type:'BOOLEAN',description:'add leading zeroes'}
];
+
+// like formatDecimal, but with OPTional decimal point if number is integer
const formatDecimalOpt=function(number,fix,fract,addSpace,prefixZero){
number=parseFloat(number);
- if (isNaN(number)) return formatDecimal(number,fix,fract,addSpace,prefixZero);
- if (Math.floor(number) == number){
- return formatDecimal(number,fix,0,addSpace,prefixZero);
- }
- return formatDecimal(number,fix,fract,addSpace,prefixZero);
+ let isint = Math.floor(number) == number;
+ return formatDecimal(number,fix,isint?0:fract,addSpace,prefixZero);
};
+formatDecimalOpt.parameters=formatDecimal.parameters;
-formatDecimalOpt.parameters=[
- {name:'fix',type:'NUMBER'},
- {name: 'fract',type:'NUMBER'},
- {name: 'addSpace',type:'BOOLEAN'},
- {name: 'prefixZero',type:'BOOLEAN'}
-];
+// clamp x to a<=x<=b
+function clamp(a,x,b) {
+ return Math.max(a,Math.min(x,b));
+}
/**
- * format number with N digits
+ * format number with N significant digits
+ * naming: the number 12.345 has 5 TOTAL digits, 2 INTEGER digits, 3 FRACTIONAL digits
* at max N-1 digits after decimal point
- * there are at least N digits and a decimal point at a variable position
- * like the display of a multimeter in auto-range mode
- * bigger numbers: more digits are appended to the right if necessary
- * smaller numbers: up to maxPlaces decimal places are added or they get rounded to zero
+ * there are at least N total digits and the decimal point at a variable position and and optional sign
+ * it's like the display of a multimeter in auto-range mode
+ * bigger numbers: more integer digits are appended to the left if necessary, fractional digits are removed
+ * smaller numbers: up to maxFrac fractional digits are added (can get rounded to zero)
* negative numbers: minus sign is added if necessary
- * @param digits = number of (significant) digits in total, negative: padding space is added for sign
- * @param maxPlaces = max. number of decimal places (after the decimal point, default = digits-1)
+ * @param digits = number of total digits, negative: single padding space is added for sign
+ * @param maxFrac = max. number of fractional digits (default = digits-1), negative: fixed value of fractional digits
* @param leadingZeroes = use leading zeroes instead of spaces
- * returns string with at least digits(+1 if digits<0) characters
+ * returns string with at least digits (+1 if digits<0) (+1 if maxFrac!=0) characters
*/
-const formatFloat=function(number, digits, maxPlaces, leadingZeroes=false) {
- if (digits == null) digits=3;
+const formatFloat=function(number, digits, maxFrac, leadingZeroes=false) {
+ if (!digits) digits=3;
let signed = digits<0;
digits = Math.abs(digits);
- if(maxPlaces==null) maxPlaces=digits-1;
- if(isNaN(number)) return '-'.repeat(digits+(signed?1:0)-maxPlaces)+(maxPlaces?'.'+'-'.repeat(maxPlaces):'');
+ if(maxFrac==null) maxFrac=digits-1;
+ maxFrac=clamp(0,maxFrac,digits-1);
+ number=parseFloat(number); // null-->NaN
+ if(!isFinite(number)) return '-'.repeat(digits+(signed?1:0)-maxFrac)+(maxFrac?'.'+'-'.repeat(maxFrac):'');
if(digits==0) return number.toFixed(0);
- if(number<0 && !signed) digits-=1;
+ if(number<0 && !signed) digits-=1; // make room for unexpected sign
let sign = number<0 ? '-' : signed ? ' ' : '';
number = Math.abs(number);
- let decPlaces = digits-1-Math.floor(Math.log10(Math.abs(number)));
- decPlaces = Math.max(0,Math.min(decPlaces,Math.max(0,maxPlaces)));
+ let decPlaces = digits-1-Math.floor(Math.log10(number));
+ decPlaces = clamp(0,decPlaces,maxFrac);
let str = number.toFixed(decPlaces);
let n = digits+(str.includes('.')?1:0); // expected length of string w/o sign
if(leadingZeroes) {
- return sign+'0'.repeat(Math.max(0,n-str.length))+str; // add sign and padding zeroes
+ return sign+'0'.repeat(Math.max(0,n-str.length))+str; // -001.23
} else {
- return ' '.repeat(Math.max(0,n-str.length))+sign+str; // add padding spaces and sign
+ return ' '.repeat(Math.max(0,n-str.length))+sign+str; // __-1.23
}
};
formatFloat.parameters=[
- {name:'digits',type:'NUMBER',default: 3,description:"number of (significant) digits in total, negative: padding space is added for sign"},
- {name:'maxPlaces',type:'NUMBER',default:2,description:"max. number of decimal places (after the decimal point, default = digits-1)"},
- {name: 'leadingZeroes', type: 'BOOLEAN',description: "use leading zeroes instead of spaces"}
+ {name:'digits',type:'NUMBER',default:3,description:"number of (significant) digits in total, negative: padding space is added for sign"},
+ {name:'maxFrac',type:'NUMBER',default:2,list:[0,20],description:"max. number of decimal places (after the decimal point, default = digits-1)"},
+ {name:'leadingZeroes',type:'BOOLEAN',description: "use leading zeroes instead of spaces"}
];
/**
* format a distance
@@ -170,75 +168,57 @@ formatFloat.parameters=[
* @param opt_fixed if > 0 set this much digits at min
* @param opt_fillRight if set - extend the fractional part
*/
-const formatDistance=function(distance,opt_unit,opt_fixed,opt_fillRight){
+const formatDistance=function(distance,opt_unit,digits,maxFrac){
let number=parseFloat(distance);
- if (isNaN(number)) return " -"; //4 spaces
let factor=unitToFactor(opt_unit||'nm');
- number=number/factor;
- let fract=0;
- let fixed=undefined;
- if (number < 1) {
- fract = 2;
- fixed = 1;
- }
- else if (number < 10){
- fract=1;
- fixed=1;
- }
- else if (number < 100){
- fract=1;
- fixed=2;
- }
- else{
- fixed=1+Math.floor(Math.log10(Math.abs(number)));
- }
- if (opt_fixed == null || opt_fixed < (fixed+fract)){
- fixed=undefined;
- }
- if (fixed != null){
- if (opt_fillRight){
- fract+=opt_fixed-(fixed+fract);
- }
- else{
- fixed+=opt_fixed-(fixed+fract);
- }
- }
- return formatDecimal(number,fixed,fract,false,true);
+ return formatFloat(number/factor,digits,maxFrac);
};
formatDistance.parameters=[
{name:'unit',type:'SELECT',list:DEPTH_UNITS,default:'nm'},
- {name:'numDigits', type: 'NUMBER',default: 0, description:'Always show at least this number of digits. Leave at 0 to have this flexible.'},
- {name:'fillRight', type: 'BOOLEAN',default: false, description:'let the fractional part extend to have the requested number of digits (only if numDigits > 0)'}
+ {name:'digits',type:'NUMBER',default:3,description:"number of (significant) digits in total, negative: padding space is added for sign"},
+ {name:'maxFrac',type:'NUMBER',default:1,description:"max. number of decimal places (after the decimal point, default = digits-1)"},
];
/**
*
* @param speed in m/s
- * @param opt_unit one of kn,ms,kmh
+ * @param opt_unit one of kn,ms,kmh,bft
* @returns {*}
*/
const formatSpeed=function(speed,opt_unit){
let number=parseFloat(speed);
- if (isNaN(number)) return " -"; //2 spaces
- let factor=3600/navcompute.NM;
- if (opt_unit == 'ms') factor=1;
- if (opt_unit == 'kmh') factor=3.6;
- number=number*factor;
- if (number < 100){
- return formatDecimal(number,undefined,1,false);
+ if (opt_unit == 'bft') {
+ let v=number*3600/navcompute.NM;
+ if(v<=1) return ' 0';
+ if(v<=3) return ' 1';
+ if(v<=6) return ' 2';
+ if(v<=10) return ' 3';
+ if(v<=16) return ' 4';
+ if(v<=21) return ' 5';
+ if(v<=27) return ' 6';
+ if(v<=33) return ' 7';
+ if(v<=40) return ' 8';
+ if(v<=47) return ' 9';
+ if(v<=55) return '10';
+ if(v<=63) return '11';
+ return '12';
}
- return formatDecimal(number,undefined,0,false);
+ let factor=3600/navcompute.NM;
+ if (opt_unit == 'ms' || opt_unit == 'm/s') factor=1;
+ if (opt_unit == 'kmh' || opt_unit == 'km/h') factor=3.6;
+ number*=factor;
+ return formatFloat(number,3,1);
};
formatSpeed.parameters=[
- {name:'unit',type:'SELECT',list:['kn','ms','kmh'],default:'kn'}
+ {name:'unit',type:'SELECT',list:['kn','ms','kmh','bft','m/s','km/h'],default:'kn'}
];
const formatDirection=function(dir,opt_rad,opt_180,opt_lz){
dir=opt_rad ? Helper.degrees(dir) : dir;
dir=opt_180 ? Helper.to180(dir) : Helper.to360(dir);
- return formatDecimal(dir,3,0,(!!opt_lz && !!opt_180),!!opt_lz);
+ return formatDecimal(dir,3,0,opt_180,opt_lz);
};
formatDirection.parameters=[
{name:'inputRadian',type:'BOOLEAN',default:false},
@@ -247,7 +227,7 @@ formatDirection.parameters=[
];
const formatDirection360=function(dir,opt_lz){
- return formatDecimal(dir,3,0,false,!!opt_lz);
+ return formatDecimal(dir,3,0,false,opt_lz);
};
formatDirection360.parameters=[
{name:'leadingZero',type:'BOOLEAN',default: false,description:'show leading zeroes (012)'}
@@ -258,49 +238,47 @@ formatDirection360.parameters=[
* @param {Date} curDate
* @returns {string}
*/
-const formatTime=function(curDate){
- if (! curDate || ! (curDate instanceof Date)) return "--:--:--";
- let datestr=this.formatDecimal(curDate.getHours(),2,0).replace(" ","0")+":"+
- this.formatDecimal(curDate.getMinutes(),2,0).replace(" ","0")+":"+
- this.formatDecimal(curDate.getSeconds(),2,0).replace(" ","0");
- return datestr;
+const formatTime=function(curDate, seconds=true){
+ if (!(curDate instanceof Date)) return "--:--"+(seconds?':--':'');
+ return this.formatDecimal(curDate.getHours(),2,0,false,true)+":"+
+ this.formatDecimal(curDate.getMinutes(),2,0,false,true)+(seconds?":"+
+ this.formatDecimal(curDate.getSeconds(),2,0,false,true):'');
};
-formatTime.parameters=[]
+formatTime.parameters=[
+ {name:'seconds',type:'BOOLEAN',default:true}
+];
/**
*
* @param {Date} curDate
* @returns {string} hh:mm
*/
const formatClock=function(curDate){
- if (! curDate || ! (curDate instanceof Date)) return "--:--";
- let datestr=this.formatDecimal(curDate.getHours(),2,0).replace(" ","0")+":"+
- this.formatDecimal(curDate.getMinutes(),2,0).replace(" ","0");
- return datestr;
+ if (!(curDate instanceof Date)) return "--:--";
+ return this.formatDecimal(curDate.getHours(),2,0,false,true)+":"+
+ this.formatDecimal(curDate.getMinutes(),2,0,false,true);
};
-formatClock.parameters=[]
+formatClock.parameters=[];
/**
* format date and time
* @param {Date} curDate
* @returns {string}
*/
const formatDateTime=function(curDate){
- if (! curDate || ! (curDate instanceof Date)) return "----/--/-- --:--:--";
- let datestr=this.formatDecimal(curDate.getFullYear(),4,0)+"/"+
+ if (!(curDate instanceof Date)) return "----/--/-- --:--:--";
+ return this.formatDecimal(curDate.getFullYear(),4,0,false,true)+"/"+
this.formatDecimal(curDate.getMonth()+1,2,0,false,true)+"/"+
this.formatDecimal(curDate.getDate(),2,0,false,true)+" "+
this.formatDecimal(curDate.getHours(),2,0,false,true)+":"+
this.formatDecimal(curDate.getMinutes(),2,0,false,true)+":"+
this.formatDecimal(curDate.getSeconds(),2,0,false,true);
- return datestr;
};
formatDateTime.parameters=[];
const formatDate=function(curDate){
- if (! curDate || ! (curDate instanceof Date)) return "----/--/--";
- let datestr=this.formatDecimal(curDate.getFullYear(),4,0)+"/"+
- this.formatDecimal(curDate.getMonth()+1,2,0)+"/"+
- this.formatDecimal(curDate.getDate(),2,0);
- return datestr;
+ if (!(curDate instanceof Date)) return "----/--/--";
+ return this.formatDecimal(curDate.getFullYear(),4,0,false,true)+"/"+
+ this.formatDecimal(curDate.getMonth()+1,2,0,false,true)+"/"+
+ this.formatDecimal(curDate.getDate(),2,0,false,true);
};
formatDate.parameters=[];
@@ -312,13 +290,13 @@ const formatPressure=function(data,opt_unit){
try {
if (!opt_unit || opt_unit.toLowerCase() === 'pa') return formatDecimal(data);
if (opt_unit.toLowerCase() === 'hpa') {
- return (parseFloat(data)/100).toFixed(2)
+ return (parseFloat(data)/100).toFixed(2);
}
if (opt_unit.toLowerCase() === 'bar') {
return formatDecimal(parseFloat(data)/100000,2,4,false);
}
}catch(e){
- return "-----";
+ return "---";
}
}
formatPressure.parameters=[
@@ -328,17 +306,20 @@ formatPressure.parameters=[
const formatTemperature=function(data,opt_unit){
try{
if (! opt_unit || opt_unit.toLowerCase().match(/^k/)){
- return formatDecimal(data,3,1);
+ return formatFloat(data,3,1);
}
if (opt_unit.toLowerCase().match(/^c/)){
- return formatDecimal(parseFloat(data)-273.15,3,1)
+ return formatFloat(parseFloat(data)-273.15,3,1)
+ }
+ if (opt_unit.toLowerCase().match(/^f/)){
+ return formatFloat(parseFloat(data)*9/5+32,3,1)
}
}catch(e){
- return "-----"
+ return "---"
}
}
formatTemperature.parameters=[
- {name:'unit',type:'SELECT',list:['celsius','kelvin'],default:'kelvin'}
+ {name:'unit',type:'SELECT',list:['celsius','kelvin','fahrenheit'],default:'kelvin'}
]
const skTemperature=formatTemperature;
diff --git a/viewer/util/helper.js b/viewer/util/helper.js
index 22499315b..0b39f3d63 100644
--- a/viewer/util/helper.js
+++ b/viewer/util/helper.js
@@ -145,9 +145,7 @@ Helper.getParam=(key)=>{
};
Helper.to360=(a)=>{
- while (a < 360) {
- a += 360;
- }
+ while (a < 0) { a += 360; }
return a % 360;
};
@@ -195,13 +193,7 @@ export const concat=(...args)=>{
});
return rt;
}
-export const concatsp=(...args)=>{
- let rt="";
- args.forEach((a)=>{
- if (a !== undefined) rt+=" "+a;
- });
- return rt;
-}
+export const concatsp=(...args)=>args.filter(i=>i!=null).join(' ');
export const unsetOrTrue=(item)=>{
return !!(item === undefined || item);
}
diff --git a/viewer/util/keys.jsx b/viewer/util/keys.jsx
index fd07a1871..faa5bf75e 100644
--- a/viewer/util/keys.jsx
+++ b/viewer/util/keys.jsx
@@ -121,6 +121,14 @@ let keys={
speed: V,
rtime: V,
valid: K,
+ fixType: K,
+ satUsed: K,
+ satInview: K,
+ fixType: K,
+ fixQuality: K,
+ PDOP: K,
+ HDOP: K,
+ VDOP: K,
windAngle: V,
windSpeed: V,
trueWindAngle: V,