feature: add GUI (#6)
4
GUI-help.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# GUI help for signalk-raymarine-autopilot
|
||||||
|
|
||||||
|

|
||||||
|
|
BIN
GUI-help.png
Normal file
After Width: | Height: | Size: 210 KiB |
62
README.md
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
[](https://greenkeeper.io/)
|
[](https://greenkeeper.io/)
|
||||||
|
|
||||||
|
<p align="center"><img src="./small-rayremote.png"></p>
|
||||||
|
|
||||||
|
`signalk-raymarine-autopilot` signal is composed of 2 modules:
|
||||||
|
- [A graphical interface that emulates a Raymarine remote control](./GUI-help.md "GUI help")
|
||||||
|
- A back-end API described below.
|
||||||
|
|
||||||
# API
|
# API
|
||||||
|
|
||||||
@ -44,3 +49,60 @@ The `value` is in degrees and is the amount to change. So when in `auto` at a he
|
|||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Change Target Heading or Wind Angle by key
|
||||||
|
|
||||||
|
The `value` is a direct key (`+1`, `+10`, `-1`, `-10`, `-1-10`, `+1+10`).
|
||||||
|
The key `+1` add +1 degree to heading, `+10` add +10 degree...
|
||||||
|
The special key `-1-10` and `+1+10` is dedicated to take a tack to port or starbord.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "changeHeadingByKey",
|
||||||
|
"value": "+1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Take a tack to port or starboard
|
||||||
|
|
||||||
|
The `value` is `port` or `starboard`.
|
||||||
|
This command must be send only when your pilot is in `auto` or `wind` mode.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "tackTo",
|
||||||
|
"value": "port"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Silence alarm
|
||||||
|
|
||||||
|
This command silence a Raymarine alarms.
|
||||||
|
You can silence alarm directly by the numeric code or by a `signal k` notification path.
|
||||||
|
This command does not silence a `signal k` notification.
|
||||||
|
It is your Raymarine equipment that receives the acknowledgement command
|
||||||
|
and returns a new notification status on the NMEA2000 bus with the `normal` state value.
|
||||||
|
See the code in `index.js` for the complete list of `raymarineAlarmGroupCodes` and `alarmsId` keys / values.
|
||||||
|
|
||||||
|
In acknowledgement by the numeric code, the `value` is :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "silenceAlarm",
|
||||||
|
"value": {
|
||||||
|
"groupId": "0x01",
|
||||||
|
"alarmId": "0x47"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In acknowledgement by `signal k` notification path, the `value` is the path you receive in your websocket :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "silenceAlarm",
|
||||||
|
"value": {
|
||||||
|
"signalkPath": "notifications.autopilot.PilotWarningWindShift"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
173
index.js
@ -22,7 +22,134 @@ const state_commands = {
|
|||||||
"wind": "%s,3,126208,%s,%s,17,01,63,ff,00,f8,04,01,3b,07,03,04,04,00,01,05,ff,ff",
|
"wind": "%s,3,126208,%s,%s,17,01,63,ff,00,f8,04,01,3b,07,03,04,04,00,01,05,ff,ff",
|
||||||
"route": "%s,3,126208,%s,%s,17,01,63,ff,00,f8,04,01,3b,07,03,04,04,80,01,05,ff,ff",
|
"route": "%s,3,126208,%s,%s,17,01,63,ff,00,f8,04,01,3b,07,03,04,04,80,01,05,ff,ff",
|
||||||
"standby": "%s,3,126208,%s,%s,17,01,63,ff,00,f8,04,01,3b,07,03,04,04,00,00,05,ff,ff"
|
"standby": "%s,3,126208,%s,%s,17,01,63,ff,00,f8,04,01,3b,07,03,04,04,00,00,05,ff,ff"
|
||||||
}
|
}
|
||||||
|
const keys_code = {
|
||||||
|
"+1": "07,f8",
|
||||||
|
"+10": "08,f7",
|
||||||
|
"-1": "05,fa",
|
||||||
|
"-10": "06,f9",
|
||||||
|
"-1-10": "21,de",
|
||||||
|
"+1+10": "22,dd"
|
||||||
|
}
|
||||||
|
const raymarineAlarmGroupCodes = {
|
||||||
|
"instrument": 0x00,
|
||||||
|
"autopilot": 0x01,
|
||||||
|
"radar": 0x02,
|
||||||
|
"chartplotter": 0x03,
|
||||||
|
"ais": 0x04
|
||||||
|
};
|
||||||
|
const alarmsId = {
|
||||||
|
"NoAlarm": 0x0,
|
||||||
|
"ShallowDepth": 0x1,
|
||||||
|
"DeepDepth": 0x2,
|
||||||
|
"ShallowAnchor": 0x3,
|
||||||
|
"DeepAnchor": 0x4,
|
||||||
|
"OffCourse": 0x5,
|
||||||
|
"AWAHigh": 0x6,
|
||||||
|
"AWALow": 0x7,
|
||||||
|
"AWSHigh": 0x8,
|
||||||
|
"AWSLow": 0x9,
|
||||||
|
"TWAHigh": 0xa,
|
||||||
|
"TWALow": 0xb,
|
||||||
|
"TWSHigh": 0xc,
|
||||||
|
"TWSLow": 0xd,
|
||||||
|
"WPArrival": 0xe,
|
||||||
|
"BoatSpeedHigh": 0xf,
|
||||||
|
"BoatSpeedLow": 0x10,
|
||||||
|
"SeaTempHigh": 0x11,
|
||||||
|
"SeaTempLow": 0x12,
|
||||||
|
"PilotWatch": 0x13,
|
||||||
|
"PilotOffCourse": 0x14,
|
||||||
|
"PilotWindShift": 0x15,
|
||||||
|
"PilotLowBattery": 0x16,
|
||||||
|
"PilotLastMinuteOfWatch": 0x17,
|
||||||
|
"PilotNoNMEAData": 0x18,
|
||||||
|
"PilotLargeXTE": 0x19,
|
||||||
|
"PilotNMEADataError": 0x1a,
|
||||||
|
"PilotCUDisconnected": 0x1b,
|
||||||
|
"PilotAutoRelease": 0x1c,
|
||||||
|
"PilotWayPointAdvance": 0x1d,
|
||||||
|
"PilotDriveStopped": 0x1e,
|
||||||
|
"PilotTypeUnspecified": 0x1f,
|
||||||
|
"PilotCalibrationRequired": 0x20,
|
||||||
|
"PilotLastHeading": 0x21,
|
||||||
|
"PilotNoPilot": 0x22,
|
||||||
|
"PilotRouteComplete": 0x23,
|
||||||
|
"PilotVariableText": 0x24,
|
||||||
|
"GPSFailure": 0x25,
|
||||||
|
"MOB": 0x26,
|
||||||
|
"Seatalk1Anchor": 0x27,
|
||||||
|
"PilotSwappedMotorPower": 0x28,
|
||||||
|
"PilotStandbyTooFastToFish": 0x29,
|
||||||
|
"PilotNoGPSFix": 0x2a,
|
||||||
|
"PilotNoGPSCOG": 0x2b,
|
||||||
|
"PilotStartUp": 0x2c,
|
||||||
|
"PilotTooSlow": 0x2d,
|
||||||
|
"PilotNoCompass": 0x2e,
|
||||||
|
"PilotRateGyroFault": 0x2f,
|
||||||
|
"PilotCurrentLimit": 0x30,
|
||||||
|
"PilotWayPointAdvancePort": 0x31,
|
||||||
|
"PilotWayPointAdvanceStbd": 0x32,
|
||||||
|
"PilotNoWindData": 0x33,
|
||||||
|
"PilotNoSpeedData": 0x34,
|
||||||
|
"PilotSeatalkFail1": 0x35,
|
||||||
|
"PilotSeatalkFail2": 0x36,
|
||||||
|
"PilotWarningTooFastToFish": 0x37,
|
||||||
|
"PilotAutoDocksideFail": 0x38,
|
||||||
|
"PilotTurnTooFast": 0x39,
|
||||||
|
"PilotNoNavData": 0x3a,
|
||||||
|
"PilotLostWaypointData": 0x3b,
|
||||||
|
"PilotEEPROMCorrupt": 0x3c,
|
||||||
|
"PilotRudderFeedbackFail": 0x3d,
|
||||||
|
"PilotAutolearnFail1": 0x3e,
|
||||||
|
"PilotAutolearnFail2": 0x3f,
|
||||||
|
"PilotAutolearnFail3": 0x40,
|
||||||
|
"PilotAutolearnFail4": 0x41,
|
||||||
|
"PilotAutolearnFail5": 0x42,
|
||||||
|
"PilotAutolearnFail6": 0x43,
|
||||||
|
"PilotWarningCalRequired": 0x44,
|
||||||
|
"PilotWarningOffCourse": 0x45,
|
||||||
|
"PilotWarningXTE": 0x46,
|
||||||
|
"PilotWarningWindShift": 0x47,
|
||||||
|
"PilotWarningDriveShort": 0x48,
|
||||||
|
"PilotWarningClutchShort": 0x49,
|
||||||
|
"PilotWarningSolenoidShort": 0x4a,
|
||||||
|
"PilotJoystickFault": 0x4b,
|
||||||
|
"PilotNoJoystickData": 0x4c,
|
||||||
|
"notassigned": 0x4d,
|
||||||
|
"notassigned": 0x4e,
|
||||||
|
"notassigned": 0x4f,
|
||||||
|
"PilotInvalidCommand": 0x50,
|
||||||
|
"AISTXMalfunction": 0x51,
|
||||||
|
"AISAntennaVSWRfault": 0x52,
|
||||||
|
"AISRxchannel1malfunction": 0x53,
|
||||||
|
"AISRxchannel2malfunction": 0x54,
|
||||||
|
"AISNosensorpositioninuse": 0x55,
|
||||||
|
"AISNovalidSOGinformation": 0x56,
|
||||||
|
"AISNovalidCOGinformation": 0x57,
|
||||||
|
"AIS12Valarm": 0x58,
|
||||||
|
"AIS6Valarm": 0x59,
|
||||||
|
"AISNoisethresholdexceededchannelA": 0x5a,
|
||||||
|
"AISNoisethresholdexceededchannelB": 0x5b,
|
||||||
|
"AISTransmitterPAfault": 0x5c,
|
||||||
|
"AIS3V3alarm": 0x5d,
|
||||||
|
"AISRxchannel70malfunction": 0x5e,
|
||||||
|
"AISHeadinglost/invalid": 0x5f,
|
||||||
|
"AISinternalGPSlost": 0x60,
|
||||||
|
"AISNosensorposition": 0x61,
|
||||||
|
"AISLockfailure": 0x62,
|
||||||
|
"AISInternalGGAtimeout": 0x63,
|
||||||
|
"AISProtocolstackrestart": 0x64,
|
||||||
|
"PilotNoIPScommunications": 0x65,
|
||||||
|
"PilotPower-OnorSleep-SwitchResetWhileEngaged": 0x66,
|
||||||
|
"PilotUnexpectedResetWhileEngaged": 0x67,
|
||||||
|
"AISDangerousTarget": 0x68,
|
||||||
|
"AISLostTarget": 0x69,
|
||||||
|
"AISSafetyRelatedMessage(usedtosilence)": 0x6a,
|
||||||
|
"AISConnectionLost": 0x6b,
|
||||||
|
"NoFix": 0x6c
|
||||||
|
};
|
||||||
|
const key_command = "%s,7,126720,%s,%s,22,3b,9f,f0,81,86,21,%s,ff,ff,ff,ff,ff,c1,c2,cd,66,80,d3,42,b1,c8"
|
||||||
const heading_command = "%s,3,126208,%s,%s,14,01,50,ff,00,f8,03,01,3b,07,03,04,06,%s,%s"
|
const heading_command = "%s,3,126208,%s,%s,14,01,50,ff,00,f8,03,01,3b,07,03,04,06,%s,%s"
|
||||||
const wind_direction_command = "%s,3,126208,%s,%s,14,01,41,ff,00,f8,03,01,3b,07,03,04,04,%s,%s"
|
const wind_direction_command = "%s,3,126208,%s,%s,14,01,41,ff,00,f8,03,01,3b,07,03,04,04,%s,%s"
|
||||||
const raymarine_ttw_Mode = "%s,3,126208,%s,%s,17,01,63,ff,00,f8,04,01,3b,07,03,04,04,81,01,05,ff,ff"
|
const raymarine_ttw_Mode = "%s,3,126208,%s,%s,17,01,63,ff,00,f8,04,01,3b,07,03,04,04,81,01,05,ff,ff"
|
||||||
@ -176,6 +303,31 @@ function setState(app, deviceid, command_json)
|
|||||||
return [util.format(state_commands[state], (new Date()).toISOString(), default_src, deviceid)]
|
return [util.format(state_commands[state], (new Date()).toISOString(), default_src, deviceid)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tackTo(app, deviceid, command_json)
|
||||||
|
{
|
||||||
|
var tackTo = command_json["value"]
|
||||||
|
app.debug("tackTo: " + tackTo)
|
||||||
|
if (tackTo === "port")
|
||||||
|
{
|
||||||
|
return [util.format(key_command, (new Date()).toISOString(), default_src, everyone_dst, keys_code["-1-10"])]
|
||||||
|
}
|
||||||
|
else if (tackTo === "starboard")
|
||||||
|
{
|
||||||
|
return [util.format(key_command, (new Date()).toISOString(), default_src, everyone_dst, keys_code["+1+10"])]
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.debug("tackTo: unknown " + tackTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeHeadingByKey(app, deviceid, command_json)
|
||||||
|
{
|
||||||
|
var key = command_json["value"]
|
||||||
|
app.debug("changeHeadingByKey: " + key)
|
||||||
|
return [util.format(key_command, (new Date()).toISOString(), default_src, everyone_dst, keys_code[key])]
|
||||||
|
}
|
||||||
|
|
||||||
function advanceWaypoint(app, deviceid, command_json)
|
function advanceWaypoint(app, deviceid, command_json)
|
||||||
{
|
{
|
||||||
return [util.format(raymarine_ttw_Mode, (new Date()).toISOString(),
|
return [util.format(raymarine_ttw_Mode, (new Date()).toISOString(),
|
||||||
@ -186,6 +338,17 @@ function advanceWaypoint(app, deviceid, command_json)
|
|||||||
|
|
||||||
function silenceAlarm(app, deviceid, command_json)
|
function silenceAlarm(app, deviceid, command_json)
|
||||||
{
|
{
|
||||||
|
if (typeof command_json.value.signalkPath !== 'undefined') {
|
||||||
|
var path = command_json.value.signalkPath.split('.')
|
||||||
|
if (path.length > 1) {
|
||||||
|
var groupId = raymarineAlarmGroupCodes[path[path.length - 2]] || -1
|
||||||
|
var alarmId = alarmsId[path[path.length - 1]] || -1
|
||||||
|
if ((groupId !== -1) && (alarmId !== -1)) {
|
||||||
|
command_json.value.groupId = groupId
|
||||||
|
command_json.value.alarmId = alarmId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return [ util.format(raymarine_silence, (new Date()).toISOString(),
|
return [ util.format(raymarine_silence, (new Date()).toISOString(),
|
||||||
default_src, padd(command_json.value.alarmId.toString(16),2),
|
default_src, padd(command_json.value.alarmId.toString(16),2),
|
||||||
padd(command_json.value.groupId.toString(16),2)) ]
|
padd(command_json.value.groupId.toString(16),2)) ]
|
||||||
@ -212,6 +375,14 @@ function sendCommand(app, deviceid, command_json)
|
|||||||
{
|
{
|
||||||
n2k_msgs = silenceAlarm(app, deviceid, command_json)
|
n2k_msgs = silenceAlarm(app, deviceid, command_json)
|
||||||
}
|
}
|
||||||
|
else if ( action == "tackTo" )
|
||||||
|
{
|
||||||
|
n2k_msgs = tackTo(app, deviceid, command_json)
|
||||||
|
}
|
||||||
|
else if ( action == "changeHeadingByKey" )
|
||||||
|
{
|
||||||
|
n2k_msgs = changeHeadingByKey(app, deviceid, command_json)
|
||||||
|
}
|
||||||
if ( n2k_msgs )
|
if ( n2k_msgs )
|
||||||
{
|
{
|
||||||
app.debug("n2k_msg: " + n2k_msgs)
|
app.debug("n2k_msg: " + n2k_msgs)
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
"test": "$NODE $npm_package_main"
|
"test": "$NODE $npm_package_main"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"signalk-node-server-plugin"
|
"signalk-node-server-plugin",
|
||||||
|
"signalk-webapp"
|
||||||
],
|
],
|
||||||
"author": "scott@scottbender.net",
|
"author": "scott@scottbender.net",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
445
public/css/rayremote.css
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
body,
|
||||||
|
html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main {
|
||||||
|
margin: auto;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgRemoteBig {
|
||||||
|
margin: auto;
|
||||||
|
width: 238px;
|
||||||
|
height: 612px;
|
||||||
|
position: relative;
|
||||||
|
background-image: url("../img/bgRemoteBig.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnCircleWithDotRed {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
background-image: url("../img/btnCircleWithDotRed.png");
|
||||||
|
}
|
||||||
|
.btnCircleWithDotRed:active {
|
||||||
|
background-position-x: -70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnCircleRed {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
background-image: url("../img/btnCircleRed.png");
|
||||||
|
}
|
||||||
|
.btnCircleRed:active {
|
||||||
|
background-position-x: -70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnLeftBlack {
|
||||||
|
width: 50px;
|
||||||
|
height: 54px;
|
||||||
|
background-image: url("../img/btnLeftBlack.png");
|
||||||
|
background-size: 100px;
|
||||||
|
}
|
||||||
|
.btnLeftBlack:active {
|
||||||
|
background-position-x: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnRightBlack {
|
||||||
|
width: 50px;
|
||||||
|
height: 54px;
|
||||||
|
background-image: url("../img/btnRightBlack.png");
|
||||||
|
background-size: 100px;
|
||||||
|
}
|
||||||
|
.btnRightBlack:active {
|
||||||
|
background-position-x: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnTopBlack {
|
||||||
|
width: 54px;
|
||||||
|
height: 50px;
|
||||||
|
left: 90px;
|
||||||
|
top: 280px;
|
||||||
|
background-image: url("../img/btnTopBlack.png");
|
||||||
|
background-size: 55px;
|
||||||
|
}
|
||||||
|
.btnTopBlack:active {
|
||||||
|
background-position-y: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnBottomBlack {
|
||||||
|
width: 54px;
|
||||||
|
height: 50px;
|
||||||
|
left: 90px;
|
||||||
|
top: 378px;
|
||||||
|
background-image: url("../img/btnBottomBlack.png");
|
||||||
|
background-size: 55px;
|
||||||
|
}
|
||||||
|
.btnBottomBlack:active {
|
||||||
|
background-position-y: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnTopSmallBlack {
|
||||||
|
width: 52px;
|
||||||
|
height: 38px;
|
||||||
|
background-image: url("../img/btnTopSmallBlack.png");
|
||||||
|
background-size: 104px;
|
||||||
|
}
|
||||||
|
.btnTopSmallBlack:active {
|
||||||
|
background-position-x: -52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnBottomSmallBlack {
|
||||||
|
width: 52px;
|
||||||
|
height: 38px;
|
||||||
|
background-image: url("../img/btnBottomSmallBlack.png");
|
||||||
|
background-size: 104px;
|
||||||
|
}
|
||||||
|
.btnBottomSmallBlack:active {
|
||||||
|
background-position-x: -52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.defaultTextFontKey {
|
||||||
|
width: 100%;
|
||||||
|
color: white;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.defaultKey {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logoSignalK {
|
||||||
|
top: 54px;
|
||||||
|
left: 44px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
position: absolute;
|
||||||
|
background-image: url("../img/signalK_16x16.png");
|
||||||
|
background-size: 16px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logoText {
|
||||||
|
top: 54px;
|
||||||
|
left: 66px;
|
||||||
|
width: 100px;
|
||||||
|
height: 18px;
|
||||||
|
position: absolute;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#topBarIcon {
|
||||||
|
top: 94px;
|
||||||
|
left: 38px;
|
||||||
|
width: 155px;
|
||||||
|
height: 18px;
|
||||||
|
position: absolute;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position-x: right;
|
||||||
|
background-color: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottomBarIcon {
|
||||||
|
top: 190px;
|
||||||
|
left: 38px;
|
||||||
|
width: 155px;
|
||||||
|
height: 18px;
|
||||||
|
position: absolute;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position-x: right;
|
||||||
|
background-color: initial;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
color: red;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon18x18 {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
position: absolute;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#powerOnIcon {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAAeklEQVR4AdXQsQ2CUBgE4K/RxM64BNqxicQlYAMdQaZgAV0B/9AxDzUPW8ibgO/aKy5n7x46vRBKpRB6ncpKazGKTWkwWbQAheQld/KVFEBtdgSxClzMaqCRnJGXDmYNcJN8nOSekivA22IybIaHcT0cKp1fdsHdvv0B0+E2g9R6Y0QAAAAASUVORK5CYII=);
|
||||||
|
left: 140px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#powerOffIcon {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAAuklEQVR4Aa3NgUZDYRzG4ac0WkJCNxAS6ICQIgQQu4MQCgyAgAiKAAhhN1AGA8M2u5ddwWzGnPONPz7APtgPwON97bRDHw4UurYyKLM7yzLb8y1lVpmYGOnpIPcjmYnTjKbmki+AK8mvffdx2gLQ9q9xAXTVjsFMMtRSGYFTtRfgVeME4mYR7BkcqXWBS40/bcCtVTB41zgH+JTMTWOp8iROPVh7Q66jZyyQKp+e2dqNZTBl1qfMHu24DRTyPk1WGp22AAAAAElFTkSuQmCC);
|
||||||
|
left: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sendIcon {
|
||||||
|
left: 120px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAyklEQVQoz8XOIU4DARCF4Y+iIO02IeAxCERFRRWFW+DgAAjugK8gNXCFUlQNWHQNBDD1yGZTgmw3g9jNssmGSphR8+bN/I8/rc2aMnDkaf3RQAhX6ywtUyFMtX6z7IFDB5WpBvp0Xk5nvurQxEwIC89eLIQwk1Qtu2i7lYmiV260i00BmuuBpr5LF/qaoGeeQxPvwsrYsUb5u+HEnUx4y6FbxgUk9eDa0KO0UEa2f1J1TSzLRCEsTXTz5UYl/o5THfsyH17dS/1PfQMplUPMhfqpHwAAAABJRU5ErkJggg==);
|
||||||
|
transform:scaleY(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#receiveIcon {
|
||||||
|
left: 100px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAyklEQVQoz8XOIU4DARCF4Y+iIO02IeAxCERFRRWFW+DgAAjugK8gNXCFUlQNWHQNBDD1yGZTgmw3g9jNssmGSphR8+bN/I8/rc2aMnDkaf3RQAhX6ywtUyFMtX6z7IFDB5WpBvp0Xk5nvurQxEwIC89eLIQwk1Qtu2i7lYmiV260i00BmuuBpr5LF/qaoGeeQxPvwsrYsUb5u+HEnUx4y6FbxgUk9eDa0KO0UEa2f1J1TSzLRCEsTXTz5UYl/o5THfsyH17dS/1PfQMplUPMhfqpHwAAAABJRU5ErkJggg==);
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorIcon {
|
||||||
|
left: 80px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAAwElEQVR4AZXRtXUDMBQAwOvDjaEMwwBhWiE0RKiy9wiWYc4W3iSoGcKRS71n9qmU9FH7+izbtWVZr5pyHsR0PMiqMOjdp2MDYMCxT++GSfKCYILEhOBVHoAbf+YA+/YBc/5cA/T4dwagpATg1L8eYFU0XfPRlGgDKIi6az7qFhWAoqgTlTXRKSoC61I6gJRuPRV+XjPSWSqcK/9mVD6a8e+qcphjSMYEQZZkyJvKtTwbUiHvsdmCgV7Ldu1a1qtdZTafSCFQvo25AAAAAElFTkSuQmCC);
|
||||||
|
}
|
||||||
|
|
||||||
|
#notificationCounter {
|
||||||
|
left: 0px;
|
||||||
|
width: 35px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAAhElEQVR4Aa3MoQ3CUABF0UNwCAbAMAMTYJkDyRxI5kCyDYoNKqqbmkdFQ2j6+wXhXPvy/OY0VGftNbRWdRFxUbHVimhtLbrJ2I2yvV7GentFDxEQ8VBwlMkojqasPGejp5WJs8xGcfZlo5FCjY2PqxSf4mq00y2OOjvgLpXucBD1Dv7mDVf3cw8sx/MwAAAAAElFTkSuQmCC);
|
||||||
|
}
|
||||||
|
|
||||||
|
#notificationCounterText {
|
||||||
|
padding-right: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#countDownCounter {
|
||||||
|
left: 36px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headingValue {
|
||||||
|
top: 116px;
|
||||||
|
left: 38px;
|
||||||
|
width: 155px;
|
||||||
|
height: 34px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pilotStatus {
|
||||||
|
top: 150px;
|
||||||
|
left: 38px;
|
||||||
|
width: 155px;
|
||||||
|
height: 34px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageScreen {
|
||||||
|
position: absolute;
|
||||||
|
top: 112px;
|
||||||
|
left: 38px;
|
||||||
|
width: 158px;
|
||||||
|
height: 100px;
|
||||||
|
z-index: 1;
|
||||||
|
visibility: hidden;
|
||||||
|
background-color: #adc1b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#silenceScreen {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tackScreen {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#silenceScreenScroll{
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
height: 20px;
|
||||||
|
width: 150px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAIElEQVR4AWMYGDAKJoAxgn2ACDyBmgaNem3Ua4TBKAAA2Kk2AbLe+1QAAAAASUVORK5CYII=);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#silenceScreenMute {
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
height: 20px;
|
||||||
|
width: 150px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAAvElEQVR4Aa3OAUcDARzG4adUpSAQECYRoEAium8QBH2D1AkcAZxKRkjQ56hhCIQADOwLDIANwGzGf5zDbHcD+70AD14rruHBbBeuLPQopIAmcuHfgbkyIcONwL4XQz2H1WzND+DMwK+ZTiUSrYJtI5UgFS4B+BPKyfCkb8+Wic9qFHKb2q7R1a5DIbPhCB3fdei5YOwaeZs/3iwRHwVb9+XEQu8lUjJQzdhxa7yckQstr8Kd2s4dg3sNK20KQZNRkQgu8yAAAAAASUVORK5CYII=);
|
||||||
|
}
|
||||||
|
|
||||||
|
#silenceScreenText {
|
||||||
|
padding-top: 4px;
|
||||||
|
text-align: center;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyPlusOne {
|
||||||
|
top: 296px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
#keyPlusOneText {
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyMinusOne {
|
||||||
|
top: 296px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
#keyMinusOneText {
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyPlusTen {
|
||||||
|
top: 296px;
|
||||||
|
right: 68px;
|
||||||
|
}
|
||||||
|
#keyPlusTenText {
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyMinusTen {
|
||||||
|
top: 296px;
|
||||||
|
left: 68px;
|
||||||
|
}
|
||||||
|
#keyMinusTenText {
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyMute {
|
||||||
|
top: 362px;
|
||||||
|
left: 28px;
|
||||||
|
}
|
||||||
|
#keyMuteText {
|
||||||
|
top: 10px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
left: 16px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAAy0lEQVR4AayRtUICUBRA7a7Jye5RF7u+wM4vsF2s0e7O37BzkZqZ+AF2WGk4XLqZeO/dPq/TUtuoZSki7mIgFloFlv3+icgOoKE8GlsH1sVOgOgy9jGjpzIuRjqv/rgNI3/hQCuD0t+8WK7Eywx6NfSGIAX+5t90CwOF5ODgNj4EO2TzxbDkdXwlgmCdLOolr+UlEbTn37QAK0fRBz8BAImvEYwMnmiJfdRzHxTAYoAQJjqPWWxJMf+3vHEAzCf+8A4avXYR9xSpm5QAaOvUDhZuhCsAAAAASUVORK5CYII=);
|
||||||
|
background-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyScroll {
|
||||||
|
top: 362px;
|
||||||
|
right: 28px;
|
||||||
|
}
|
||||||
|
#keyScrollText {
|
||||||
|
top: 10px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
left: 16px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAI0lEQVR4AWMYADAK/v//PwGEkdgHiMATqGnQqNdGvUYYjAIAXvgfJmV31egAAAAASUVORK5CYII=);
|
||||||
|
background-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyPower {
|
||||||
|
top: 500px;
|
||||||
|
right: 28px;
|
||||||
|
}
|
||||||
|
#keyPowerText {
|
||||||
|
top: 10px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
left: 16px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAAw0lEQVR4AbTQgQbCQBzH8QI2Vhh7iQEJAoLWqxSwdxhApb1FgLG9wNkDbPQGA4gNU2zgGznudleADu7/v/vc/fnN/rdISX9fLuUuELJamCTkwVZHRDT4Oplzp2c9QRt6bjraAbE1LgZWCl154VjI4clFoZxalhLJuiZXqKD6FgEVhUInRlwrFJeRs2oj4GihA7DXD0o6QiO5jnL6KqBhIMH7dB4JAw2B+blPBrQIBC2Q4b+HPf7s/1f8XwuEFcAoogkAANy1lXzi6lAZAAAAAElFTkSuQmCC);
|
||||||
|
background-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyStandby {
|
||||||
|
top: 486px;
|
||||||
|
left: 84px;
|
||||||
|
}
|
||||||
|
#keyStandbyText {
|
||||||
|
top: 28px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyAuto {
|
||||||
|
top: 418px;
|
||||||
|
left: 26px;
|
||||||
|
}
|
||||||
|
#keyAutoText {
|
||||||
|
top: 28px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyWind {
|
||||||
|
top: 356px;
|
||||||
|
left: 84px;
|
||||||
|
}
|
||||||
|
#keyWindText {
|
||||||
|
top: 28px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyTrack {
|
||||||
|
top: 418px;
|
||||||
|
right: 26px;
|
||||||
|
}
|
||||||
|
#keyTrackText {
|
||||||
|
top: 28px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tackLabel {
|
||||||
|
top: 250px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyTackPort {
|
||||||
|
top: 230px;
|
||||||
|
left: 38px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#keyTackPortText {
|
||||||
|
top: 16px;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAd0lEQVRIx73WSw6AIAwE0I5H9P53qTsTFYR+ZliXvJTPACw43N3NzABgt/bIANFapCYtunjXgg1sdVIFlkgH8It0AVOkExgi3cAHYQAPhAXcCBMIx0p26JZLtvGyIyy7jLJYkQWkLOplj5bs+a1C0Vg5SbX1r9EF/au4qJQe+D0AAAAASUVORK5CYII=);
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyTackStarbord {
|
||||||
|
top: 230px;
|
||||||
|
right: 38px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#keyTackStarbordText {
|
||||||
|
top: 16px;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAg0lEQVRIx81WQQ7AIAwCn+j//+IuW6IeZqmyjDOIqZYW7QYCULg9Sn9AVKQacRaRZNRgxR1M3EbMihUud8RRLndvGeHyRDlW3GN1f+NS6YmsEdXmyxgx0+WqUcEH+Ee57A9v/8L2ZrTHij0g7VFvH1r28es2GLYVAFVIiirlinMVenABXi3An6ky178AAAAASUVORK5CYII=);
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyHelp {
|
||||||
|
top: 500px;
|
||||||
|
left: 28px;
|
||||||
|
}
|
||||||
|
#keyHelpText {
|
||||||
|
top: 10px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
left: 16px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAA+0lEQVR4AZSOMQhBYRSF7QkUmJRSeiWDwYPBaBa70WBjtY8WM2Fgt5ed7GXkSVklC5/bfa8/ZHFP/fc7p9Pf9f09hKjRFdUI/S4kWPA+S+LflQxH7oxIq0sL3SXJfP7iiCyhKB1RVMjSLGlKzHlQlZ3jDCBvTlxF0rlXIciTidKGIzHRga36MU+CbqkB2EoD6rqHXHXbQMst9YDA24Up9qyUAkDPDfuA31QsLuwIK/uBvhs3AduUptyIeFwEml+Hq8tTMDwxh4uZiSl7vGbtUUnSmaLaOA4nsspt2rqzOKLXxJHDXBlrtKjA5BGmrUCPYNxJJRcIQUmFVAAAZtDpcTPc14kAAAAASUVORK5CYII=);
|
||||||
|
background-size: 20px;
|
||||||
|
}
|
BIN
public/img/bgRemoteBig.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
public/img/btnBottomBlack.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
public/img/btnBottomSmallBlack.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/img/btnCircleRed.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
public/img/btnCircleWithDotRed.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
public/img/btnLeftBlack.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/img/btnRightBlack.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/img/btnTopBlack.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
public/img/btnTopSmallBlack.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/img/signalK_16x16.png
Normal file
After Width: | Height: | Size: 496 B |
98
public/index.html
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="initial-scale=1">
|
||||||
|
<script src="./js/scale2fit.js"></script>
|
||||||
|
<script src="./js/rayremote.js"></script>
|
||||||
|
<title>SignalK RayRemote</title>
|
||||||
|
<link href="./css/rayremote.css" rel="stylesheet">
|
||||||
|
<script>
|
||||||
|
(function(window) {
|
||||||
|
function main() {
|
||||||
|
const margin = 20;
|
||||||
|
requestAnimationFrame(function fitToParentOnResize() {
|
||||||
|
fitToParent(document.getElementById('remoteBig'), margin);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener("DOMContentLoaded", main);
|
||||||
|
window.addEventListener("resize", main);
|
||||||
|
})(this);
|
||||||
|
// iOS CSS :active
|
||||||
|
document.addEventListener("touchstart", function() {},false);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body onload="startUpRayRemote()">
|
||||||
|
<div id="main">
|
||||||
|
<div class="bgRemoteBig" id="remoteBig">
|
||||||
|
<div id="logoSignalK"></div>
|
||||||
|
<div id="logoText">Ray Remote</div>
|
||||||
|
<div id="topBarIcon">
|
||||||
|
<div class="icon18x18" id="notificationCounter">
|
||||||
|
<div id="notificationCounterText">88</div>
|
||||||
|
</div>
|
||||||
|
<div class="icon18x18" id="countDownCounter">8</div>
|
||||||
|
<div class="icon18x18" id="errorIcon"></div>
|
||||||
|
<div class="icon18x18" id="receiveIcon"></div>
|
||||||
|
<div class="icon18x18" id="sendIcon"></div>
|
||||||
|
<div class="icon18x18" id="powerOffIcon"></div>
|
||||||
|
<div class="icon18x18" id="powerOnIcon"></div>
|
||||||
|
</div>
|
||||||
|
<div id="bottomBarIcon"> Loading Ray Remote...</div>
|
||||||
|
<div id="headingValue"></div>
|
||||||
|
<div id="pilotStatus"></div>
|
||||||
|
<div class="messageScreen" id="silenceScreen">
|
||||||
|
<div id="silenceScreenText"></div>
|
||||||
|
<div id="silenceScreenMute"> Mute alarm press:</div>
|
||||||
|
<div id="silenceScreenScroll"> Next alarm press:</div>
|
||||||
|
</div>
|
||||||
|
<div class="messageScreen" id="tackScreen"></div>
|
||||||
|
<div class="defaultKey btnLeftBlack" id="keyTackPort" onClick="buildAndSendCommand('tackToPort');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyTackPortText"></div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnRightBlack" id="keyTackStarbord" onClick="buildAndSendCommand('tackToStarboard');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyTackStarbordText"></div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultTextFontKey" id="tackLabel">: TACK :</div>
|
||||||
|
<div class="defaultKey btnBottomSmallBlack" id="keyMute" onClick="sendSilence();" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyMuteText"></div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnBottomSmallBlack" id="keyScroll" onClick="notificationScroll();" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyScrollText"></div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnLeftBlack" id="keyMinusOne" onClick="buildAndSendCommand('-1');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyMinusOneText">-1</div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnLeftBlack" id="keyMinusTen" onClick="buildAndSendCommand('-10');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyMinusTenText">-10</div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnRightBlack" id="keyPlusTen" onClick="buildAndSendCommand('+10');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyPlusTenText">+10</div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnRightBlack" id="keyPlusOne" onClick="buildAndSendCommand('+1');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyPlusOneText">+1</div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnCircleRed" id="keyAuto" onClick="buildAndSendCommand('auto');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyAutoText">AUTO</div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnCircleRed" id="keyWind" onClick="buildAndSendCommand('wind');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyWindText">WIND</div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnCircleRed" id="keyTrack" onClick="buildAndSendCommand('route');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyTrackText">TRACK</div>
|
||||||
|
</div>
|
||||||
|
<div class="defaultKey btnCircleWithDotRed" id="keyStandby" onClick="buildAndSendCommand('standby');" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyStandbyText">STANDBY</div>
|
||||||
|
</div>
|
||||||
|
<!--
|
||||||
|
<div class="defaultKey btnBottomSmallBlack" id="keyHelp" onClick="displayHelp();" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyHelpText"></div>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
<div class="defaultKey btnBottomSmallBlack" id="keyPower" onClick="wsOpenClose();" onTouchend="touchEnd(event)">
|
||||||
|
<div class="defaultTextFontKey" id="keyPowerText"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
433
public/js/rayremote.js
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Christian MOTELET <cmotelet@motelet.com>
|
||||||
|
*
|
||||||
|
* 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 commands = {
|
||||||
|
"auto": '{"action":"setState","value":"auto"}',
|
||||||
|
"wind": '{"action":"setState","value":"wind"}',
|
||||||
|
"route": '{"action":"setState","value":"route"}',
|
||||||
|
"standby": '{"action":"setState","value":"standby"}',
|
||||||
|
"+1": '{"action":"changeHeadingByKey","value":"+1"}',
|
||||||
|
"+10": '{"action":"changeHeadingByKey","value":"+10"}',
|
||||||
|
"-1": '{"action":"changeHeadingByKey","value":"-1"}',
|
||||||
|
"-10": '{"action":"changeHeadingByKey","value":"-10"}',
|
||||||
|
"tackToPort": '{"action":"tackTo","value":"port"}',
|
||||||
|
"tackToStarboard": '{"action":"tackTo","value":"starboard"}'
|
||||||
|
}
|
||||||
|
|
||||||
|
var notificationsArray = {};
|
||||||
|
|
||||||
|
var touchEnd = function(event) {
|
||||||
|
event.currentTarget.onclick();
|
||||||
|
event.preventDefault(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var ws = null;
|
||||||
|
var handlePilotStatusTimeout = null;
|
||||||
|
var handleHeadindValueTimeout = null;
|
||||||
|
var handleReceiveTimeout = null;
|
||||||
|
var handleSilenceScreenTimeout = null;
|
||||||
|
var handleConfirmTackTimeout = null;
|
||||||
|
var handleCountDownCounterTimeout = null;
|
||||||
|
var connected = false;
|
||||||
|
var reconnect = true;
|
||||||
|
const timeoutReconnect = 2000;
|
||||||
|
const timeoutValue = 2000;
|
||||||
|
const timeoutBlink = 500;
|
||||||
|
//const noDataMessage = 'NO DATA';
|
||||||
|
const noDataMessage = '-- -- -- --';
|
||||||
|
var pilotStatusDiv = undefined;
|
||||||
|
var headingValueDiv = undefined;
|
||||||
|
var receiveIconDiv = undefined;
|
||||||
|
var sendIconDiv = undefined;
|
||||||
|
var errorIconDiv = undefined;
|
||||||
|
var countDownCounterDiv = undefined;
|
||||||
|
var powerOnIconDiv = undefined;
|
||||||
|
var powerOffIconDiv = undefined;
|
||||||
|
var bottomBarIconDiv = undefined;
|
||||||
|
var notificationCounterDiv = undefined;
|
||||||
|
var notificationCounterTextDiv = undefined;
|
||||||
|
var silenceScreenDiv = undefined;
|
||||||
|
var silenceScreenText = undefined;
|
||||||
|
var tackScreenDiv = undefined;
|
||||||
|
var skPathToAck = '';
|
||||||
|
var tackConfirmed = false;
|
||||||
|
var countDownValue = 0;
|
||||||
|
|
||||||
|
var startUpRayRemote = function() {
|
||||||
|
pilotStatusDiv = document.getElementById('pilotStatus');
|
||||||
|
headingValueDiv = document.getElementById('headingValue');
|
||||||
|
receiveIconDiv = document.getElementById('receiveIcon');
|
||||||
|
sendIconDiv = document.getElementById('sendIcon');
|
||||||
|
errorIconDiv = document.getElementById('errorIcon');
|
||||||
|
powerOnIconDiv = document.getElementById('powerOnIcon');
|
||||||
|
powerOffIconDiv = document.getElementById('powerOffIcon');
|
||||||
|
bottomBarIconDiv = document.getElementById('bottomBarIcon');
|
||||||
|
notificationCounterDiv = document.getElementById('notificationCounter');
|
||||||
|
notificationCounterTextDiv = document.getElementById('notificationCounterText');
|
||||||
|
silenceScreenDiv = document.getElementById('silenceScreen');
|
||||||
|
silenceScreenTextDiv = document.getElementById('silenceScreenText');
|
||||||
|
tackScreenDiv = document.getElementById('tackScreen');
|
||||||
|
countDownCounterDiv = document.getElementById('countDownCounter');
|
||||||
|
setPilotStatus(noDataMessage);
|
||||||
|
setHeadindValue(noDataMessage);
|
||||||
|
// demo(); return;
|
||||||
|
setTimeout(() => {
|
||||||
|
receiveIconDiv.style.visibility = 'hidden';
|
||||||
|
sendIconDiv.style.visibility = 'hidden';
|
||||||
|
errorIconDiv.style.visibility = 'hidden';
|
||||||
|
bottomBarIconDiv.style.visibility = 'hidden';
|
||||||
|
notificationCounterDiv.style.visibility = 'hidden';
|
||||||
|
countDownCounterDiv.innerHTML = '';
|
||||||
|
wsConnect();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
var demo = function () {
|
||||||
|
setHeadindValue(100);
|
||||||
|
setPilotStatus('WIND');
|
||||||
|
setNotificationMessage({"path":"notifications.autopilot.PilotWarningWindShift","value":{"state":"alarm","message":"Pilot Warning Wind Shift"}});
|
||||||
|
powerOffIconDiv.style.visibility = 'hidden';
|
||||||
|
powerOnIconDiv.style.visibility = 'visible';
|
||||||
|
countDownCounterDiv.innerHTML = '5';
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildAndSendCommand = function(cmd) {
|
||||||
|
var cmdJson = commands[cmd];
|
||||||
|
if (((cmd === 'tackToPort')||(cmd === 'tackToStarboard')) && (tackConfirmed === false)) {
|
||||||
|
confirmTack(cmd);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof cmdJson !== 'undefined') {
|
||||||
|
if ((cmd === 'tackToPort')||(cmd === 'tackToStarboard')) {
|
||||||
|
countDownValue = 0;
|
||||||
|
updateCountDownCounter();
|
||||||
|
sendCommand(commands['auto']);
|
||||||
|
}
|
||||||
|
sendCommand(cmdJson);
|
||||||
|
} else {
|
||||||
|
alert('Unknown command !')
|
||||||
|
}
|
||||||
|
if (tackConfirmed) {
|
||||||
|
clearTimeout(handleConfirmTackTimeout);
|
||||||
|
tackScreenDiv.style.visibility = 'hidden';
|
||||||
|
tackScreenDiv.innerHTML = '';
|
||||||
|
tackConfirmed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendCommand = function(cmdJson) {
|
||||||
|
errorIconDiv.style.visibility = 'hidden';
|
||||||
|
sendIconDiv.style.visibility = 'visible';
|
||||||
|
window.fetch('/plugins/raymarineautopilot/command', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: cmdJson,
|
||||||
|
}).then(function(response) {
|
||||||
|
setTimeout(() => {sendIconDiv.style.visibility = 'hidden';}, timeoutBlink);
|
||||||
|
if (response.status !== 200) {
|
||||||
|
errorIconDiv.style.visibility = 'visible';
|
||||||
|
if (response.status === 401) {
|
||||||
|
alert('You must be authenticated to send commands !')
|
||||||
|
} else {
|
||||||
|
errorIconDiv.style.visibility = 'visible';
|
||||||
|
alert('[' + response.status + ']' + response.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(status) {
|
||||||
|
sendIconDiv.style.visibility = 'hidden';
|
||||||
|
errorIconDiv.style.visibility = 'visible';
|
||||||
|
alert(status.message)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
reconnect = true;
|
||||||
|
wsConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
var notificationToValue = function (skPathToAck) {
|
||||||
|
var message = notificationsArray[skPathToAck];
|
||||||
|
if (typeof message === 'undefined') {
|
||||||
|
message = 'No current alarm...';
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendSilence = function() {
|
||||||
|
if (silenceScreenDiv.style.visibility !== 'visible') {
|
||||||
|
silenceScreenDiv.style.visibility = 'visible';
|
||||||
|
autoHhideSilenceScreen();
|
||||||
|
if ((Object.keys(notificationsArray).length > 0) && (skPathToAck === '')) {
|
||||||
|
skPathToAck = Object.keys(notificationsArray)[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (skPathToAck !== '') {
|
||||||
|
sendCommand('{"action":"silenceAlarm","value":{"signalkPath":"' + skPathToAck + '"}}');
|
||||||
|
}
|
||||||
|
countDownValue = 0;
|
||||||
|
updateCountDownCounter();
|
||||||
|
silenceScreenDiv.style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
silenceScreenTextDiv.innerHTML = notificationToValue(skPathToAck);
|
||||||
|
}
|
||||||
|
|
||||||
|
var notificationScroll = function() {
|
||||||
|
autoHhideSilenceScreen();
|
||||||
|
if (silenceScreenDiv.style.visibility !== 'visible') {
|
||||||
|
silenceScreenDiv.style.visibility = 'visible';
|
||||||
|
if ((Object.keys(notificationsArray).length > 0) && (skPathToAck === '')) {
|
||||||
|
skPathToAck = Object.keys(notificationsArray)[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
skPathToAck = getNextNotification(skPathToAck);
|
||||||
|
}
|
||||||
|
silenceScreenTextDiv.innerHTML = notificationToValue(skPathToAck);
|
||||||
|
}
|
||||||
|
|
||||||
|
var autoHhideSilenceScreen = function() {
|
||||||
|
countDownValue = 5;
|
||||||
|
updateCountDownCounter();
|
||||||
|
clearTimeout(handleSilenceScreenTimeout);
|
||||||
|
handleSilenceScreenTimeout = setTimeout(() => {
|
||||||
|
silenceScreenDiv.style.visibility = 'hidden';
|
||||||
|
countDownValue = 0;
|
||||||
|
updateCountDownCounter();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
var getNextNotification = function(skPath) {
|
||||||
|
var notificationsKeys = Object.keys(notificationsArray);
|
||||||
|
var newSkPathToAck = '';
|
||||||
|
var index;
|
||||||
|
if (notificationsKeys.length > 0) {
|
||||||
|
if (typeof skPath !== 'undefined') {
|
||||||
|
index = notificationsKeys.indexOf(skPath) + 1;
|
||||||
|
} else {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
if (notificationsKeys.length <= index) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
newSkPathToAck = notificationsKeys[index];
|
||||||
|
}
|
||||||
|
return newSkPathToAck;
|
||||||
|
}
|
||||||
|
|
||||||
|
var confirmTack = function(cmd) {
|
||||||
|
var message = 'Repeat same key<br>to confirm<br>tack to ';
|
||||||
|
tackConfirmed = true;
|
||||||
|
if (cmd === 'tackToPort') {
|
||||||
|
message += 'port';
|
||||||
|
} else if (cmd === 'tackToStarboard') {
|
||||||
|
message += 'starboard';
|
||||||
|
} else {
|
||||||
|
tackConfirmed = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
countDownValue = 5;
|
||||||
|
updateCountDownCounter();
|
||||||
|
tackScreenDiv.innerHTML = '<p>' + message + '</p>';
|
||||||
|
tackScreenDiv.style.visibility = 'visible';
|
||||||
|
clearTimeout(handleConfirmTackTimeout);
|
||||||
|
handleConfirmTackTimeout = setTimeout(() => {
|
||||||
|
tackScreenDiv.style.visibility = 'hidden';
|
||||||
|
tackScreenDiv.innerHTML = '';
|
||||||
|
tackConfirmed = false;
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var wsConnect = function() {
|
||||||
|
if (ws === null) {
|
||||||
|
try {
|
||||||
|
reconnect = true;
|
||||||
|
ws = new WebSocket((window.location.protocol === 'https:' ? 'wss' : 'ws') + "://" + window.location.host + "/signalk/v1/stream?subscribe=none");
|
||||||
|
|
||||||
|
ws.onopen = function() {
|
||||||
|
connected = true;
|
||||||
|
powerOffIconDiv.style.visibility = 'hidden';
|
||||||
|
powerOnIconDiv.style.visibility = 'visible';
|
||||||
|
errorIconDiv.style.visibility = 'hidden';
|
||||||
|
var subscriptionObject = {
|
||||||
|
"context": "vessels.self",
|
||||||
|
"subscribe": [
|
||||||
|
{
|
||||||
|
"path": "steering.autopilot.state",
|
||||||
|
"format": "delta",
|
||||||
|
"minPeriod": 900
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "navigation.headingMagnetic",
|
||||||
|
"format": "delta",
|
||||||
|
"minPeriod": 900
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "notifications.autopilot.*",
|
||||||
|
"format": "delta",
|
||||||
|
"minPeriod": 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
var subscriptionMessage = JSON.stringify(subscriptionObject);
|
||||||
|
ws.send(subscriptionMessage);
|
||||||
|
handlePilotStatusTimeout = setTimeout(() => {setPilotStatus(noDataMessage)}, timeoutValue);
|
||||||
|
handleHeadindValueTimeout = setTimeout(() => {setHeadindValue(noDataMessage)}, timeoutValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onclose = function() {
|
||||||
|
cleanOnClosed();
|
||||||
|
if (reconnect === true) {
|
||||||
|
setTimeout(() => {wsConnect()}, timeoutReconnect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onerror = function() {
|
||||||
|
console.log("ws error");
|
||||||
|
cleanOnClosed();
|
||||||
|
errorIconDiv.style.visibility = 'visible';
|
||||||
|
if (reconnect === true) {
|
||||||
|
setTimeout(() => {wsConnect()}, timeoutReconnect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
receiveIconDiv.style.visibility = 'visible';
|
||||||
|
clearTimeout(handleReceiveTimeout);
|
||||||
|
handleReceiveTimeout = setTimeout(() => {receiveIconDiv.style.visibility = 'hidden';}, timeoutBlink);
|
||||||
|
var jsonData = JSON.parse(event.data)
|
||||||
|
dispatchMessages(jsonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (exception) {
|
||||||
|
console.error(exception);
|
||||||
|
cleanOnClosed();
|
||||||
|
errorIconDiv.style.visibility = 'visible';
|
||||||
|
setTimeout(() => {wsConnect()}, timeoutReconnect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dispatchMessages = function(jsonData) {
|
||||||
|
if (typeof jsonData.updates === 'object') {
|
||||||
|
jsonData.updates.forEach((update) => {
|
||||||
|
if (typeof update.values === 'object') {
|
||||||
|
update.values.forEach((value) => {
|
||||||
|
if (value.path === "steering.autopilot.state") {
|
||||||
|
clearTimeout(handlePilotStatusTimeout);
|
||||||
|
handlePilotStatusTimeout = setTimeout(() => {setPilotStatus(noDataMessage)}, timeoutValue);
|
||||||
|
setPilotStatus(value.value);
|
||||||
|
} else if (value.path === "navigation.headingMagnetic") {
|
||||||
|
clearTimeout(handleHeadindValueTimeout);
|
||||||
|
handleHeadindValueTimeout = setTimeout(() => {setHeadindValue(noDataMessage)}, timeoutValue);
|
||||||
|
setHeadindValue(Math.round(value.value * (180/Math.PI)));
|
||||||
|
} else if (value.path.startsWith("notifications.autopilot")) {
|
||||||
|
setNotificationMessage(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var setHeadindValue = function(value) {
|
||||||
|
if (value !== '') {
|
||||||
|
value = ((typeof value === 'undefined') || isNaN(value)) ? noDataMessage : 'Mag:' + value + '°';
|
||||||
|
}
|
||||||
|
headingValueDiv.innerHTML = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var setPilotStatus = function(value) {
|
||||||
|
if (typeof value === 'undefined') {
|
||||||
|
value = noDataMessage;
|
||||||
|
}
|
||||||
|
pilotStatusDiv.innerHTML = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var setNotificationMessage = function(value) {
|
||||||
|
if (typeof value.path !== 'undefined') {
|
||||||
|
value.path = value.path.replace('notifications.', '');
|
||||||
|
if (typeof value.value !== 'undefined') {
|
||||||
|
if (value.value.state === 'normal') {
|
||||||
|
delete notificationsArray[value.path]
|
||||||
|
} else {
|
||||||
|
notificationsArray[value.path] = value.value.message.replace('Pilot', '');
|
||||||
|
bottomBarIconDiv.style.visibility = 'visible';
|
||||||
|
bottomBarIconDiv.innerHTML = notificationsArray[value.path];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var alarmsCount = Object.keys(notificationsArray).length;
|
||||||
|
if (alarmsCount > 0) {
|
||||||
|
notificationCounterTextDiv.innerHTML = alarmsCount;
|
||||||
|
notificationCounterDiv.style.visibility = 'visible';
|
||||||
|
} else {
|
||||||
|
notificationCounterTextDiv.innerHTML = '';
|
||||||
|
notificationCounterDiv.style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayHelp = function() {
|
||||||
|
bottomBarIconDiv.style.visibility = 'visible';
|
||||||
|
bottomBarIconDiv.innerHTML = ' Not yet implemented...'
|
||||||
|
setTimeout(() => {bottomBarIconDiv.style.visibility = 'hidden';}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
var wsOpenClose = function() {
|
||||||
|
if (connected === false) {
|
||||||
|
wsConnect();
|
||||||
|
} else {
|
||||||
|
reconnect = false;
|
||||||
|
if (ws !== null) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
cleanOnClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanOnClosed = function() {
|
||||||
|
ws = null;
|
||||||
|
connected = false;
|
||||||
|
receiveIconDiv.style.visibility = 'hidden';
|
||||||
|
sendIconDiv.style.visibility = 'hidden';
|
||||||
|
errorIconDiv.style.visibility = 'hidden';
|
||||||
|
bottomBarIconDiv.style.visibility = 'hidden';
|
||||||
|
notificationCounterDiv.style.visibility = 'hidden';
|
||||||
|
powerOffIconDiv.style.visibility = 'visible';
|
||||||
|
powerOnIconDiv.style.visibility = 'hidden';
|
||||||
|
notificationCounterDiv.style.visibility = 'hidden';
|
||||||
|
silenceScreenDiv.style.visibility = 'hidden';
|
||||||
|
notificationCounterTextDiv.innerHTML = '';
|
||||||
|
notificationsArray = {};
|
||||||
|
skPathToAck = '';
|
||||||
|
tackConfirmed = false;
|
||||||
|
clearTimeout(handleHeadindValueTimeout);
|
||||||
|
clearTimeout(handlePilotStatusTimeout);
|
||||||
|
setPilotStatus('');
|
||||||
|
setHeadindValue('');
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateCountDownCounter = function() {
|
||||||
|
if (countDownValue > 0) {
|
||||||
|
clearTimeout(handleCountDownCounterTimeout);
|
||||||
|
countDownCounterDiv.innerHTML = countDownValue;
|
||||||
|
countDownValue -= 1;
|
||||||
|
handleCountDownCounterTimeout = setTimeout(() => {
|
||||||
|
updateCountDownCounter();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
clearTimeout(handleCountDownCounterTimeout);
|
||||||
|
countDownCounterDiv.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
27
public/js/scale2fit.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
(function(global) {
|
||||||
|
/*
|
||||||
|
Simple functions to scale content to fit it's parent
|
||||||
|
|
||||||
|
Author: Liudmil Mitev
|
||||||
|
License: WTFPL
|
||||||
|
Demo: https://jsfiddle.net/oxzxyxqn/7/
|
||||||
|
|
||||||
|
*/
|
||||||
|
function scaleAmountNeededToFit(el, margin = 0) {
|
||||||
|
const parentSize = {
|
||||||
|
width: el.parentElement.clientWidth - margin * 2,
|
||||||
|
height: el.parentElement.clientHeight - margin * 2
|
||||||
|
};
|
||||||
|
|
||||||
|
return Math.min(parentSize.width / el.clientWidth,
|
||||||
|
parentSize.height / el.clientHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitToParent(element, margin) {
|
||||||
|
const scale = scaleAmountNeededToFit(element, margin);
|
||||||
|
element.style.transformOrigin = "0 0";
|
||||||
|
element.style.transform = `translate(${margin}px, ${margin}px) scale(${scale})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
global.fitToParent = fitToParent;
|
||||||
|
})(this);
|
BIN
small-rayremote.png
Normal file
After Width: | Height: | Size: 39 KiB |