Merge branch 'dev' into 3D
This commit is contained in:
commit
a420a7630d
22 changed files with 609 additions and 455 deletions
|
@ -733,7 +733,11 @@ var canvas_MCDU_base = {
|
|||
var p = logs[i].state;
|
||||
if (p == 4) p = 3; # RETURN-IN
|
||||
me[rowsC[p]].setText(logs[i].time);
|
||||
me[rowsR[p]].setText(sprintf("%3.1f ",logs[i].fob));
|
||||
if (acconfig_weight_kgs.getValue() == 1) {
|
||||
me[rowsR[p]].setText(sprintf("%3.1f ",logs[i].fob * LBS2KGS));
|
||||
} else {
|
||||
me[rowsR[p]].setText(sprintf("%3.1f ",logs[i].fob));
|
||||
}
|
||||
}
|
||||
}
|
||||
var logpage = mcdu.FlightLogDatabase.getPage(logid);
|
||||
|
@ -797,7 +801,11 @@ var canvas_MCDU_base = {
|
|||
me["Simple_R3S"].setText(sprintf("%-10s",(doorL4_pos.getValue() > 0.1) ? "OPEN" : "CLOSED"));
|
||||
me["Simple_R3"].setText(sprintf("%-10s",(doorR4_pos.getValue() > 0.1) ? "OPEN" : "CLOSED"));
|
||||
me["Simple_R4S"].setText(sprintf("%-10s",sprintf("%03.3f",pts.Velocities.groundspeedKt.getValue())));
|
||||
me["Simple_R4"].setText(sprintf("%-10s",sprintf("%03.1f",fmgc.FMGCInternal.fob)));
|
||||
if (acconfig_weight_kgs.getValue() == 1) {
|
||||
me["Simple_R4"].setText(sprintf("%-10s",sprintf("%03.1f",fmgc.FMGCInternal.fob * LBS2KGS)));
|
||||
} else {
|
||||
me["Simple_R4"].setText(sprintf("%-10s",sprintf("%03.1f",fmgc.FMGCInternal.fob)));
|
||||
}
|
||||
} else if (page == "AOCCONFIG") {
|
||||
if (!pageSwitch[i].getBoolValue()) {
|
||||
me.defaultHideWithCenter();
|
||||
|
|
|
@ -637,11 +637,9 @@ canvas.NavDisplay.update = func() # FIXME: This stuff is still too aircraft spec
|
|||
me.symbols.dmeRDist.setText("");
|
||||
}
|
||||
|
||||
# Hide heading bug 10 secs after change
|
||||
# Hide heading bug 45 secs after change
|
||||
var vhdg_bug = getprop("/it-autoflight/input/hdg") or 0;
|
||||
var hdg_bug_active = getprop("/it-autoflight/custom/show-hdg");
|
||||
if (hdg_bug_active == nil)
|
||||
hdg_bug_active = 1;
|
||||
var hdg_bug_active = getprop("/it-autoflight/custom/show-hdg") or 1;
|
||||
|
||||
if((me.in_mode("toggle_display_mode", ["MAP"]) and me.get_switch("toggle_display_type") == "CRT")
|
||||
or (me.get_switch("toggle_track_heading") and me.get_switch("toggle_display_type") == "LCD"))
|
||||
|
|
|
@ -79,6 +79,15 @@ var init = func {
|
|||
}
|
||||
|
||||
var draw = func{
|
||||
if (me.model.wp.hidden == 1 or me.model.name == "(DECEL)" or me.model.name == "(T/C)" or me.model.name == "(T/D)" or me.model.name == "(LIM)") {
|
||||
me.wp_sym.hide();
|
||||
me.text_wps.hide();
|
||||
if (me.text_alt != nil) {
|
||||
me.text_alt.hide();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var wp_group = me.element;
|
||||
var alt = me.model.alt;
|
||||
var i = me.model.idx;
|
||||
|
|
|
@ -60,7 +60,7 @@ canvas.NDStyles["Airbus"] = {
|
|||
lat_ctrl: "/it-autoflight/output/lat",
|
||||
managed_val: 1,
|
||||
ver_ctrl: "/it-autoflight/output/vert",
|
||||
spd_ctrl: "/flight-management/control/spd-ctrl",
|
||||
spd_ctrl: "/it-autoflight/input/spd-managed",
|
||||
current_wp: "/FMGC/flightplan[2]/current-wp",
|
||||
ap1: "/it-autoflight/output/ap1",
|
||||
ap2: "/it-autoflight/output/ap2",
|
||||
|
@ -595,18 +595,18 @@ canvas.NDStyles["Airbus"] = {
|
|||
isMapStructure: 1,
|
||||
update_on: ["toggle_display_mode","toggle_range"],
|
||||
predicate: func(nd, layer) {
|
||||
var visible = nd.in_mode("toggle_display_mode", ["MAP", "PLAN"]) and (nd.adirs_property.getValue() == 1 or (adirs_3.getValue() == 1 and att_switch.getValue() == nd.attitude_heading_setting));
|
||||
var visible = nd.in_mode("toggle_display_mode", ["MAP", "PLAN"]) and (nd.adirs_property.getValue() == 1 or (adirs_3.getValue() == 1 and att_switch.getValue() == nd.attitude_heading_setting)) and getprop("/instrumentation/nd/symbols/decel/show");
|
||||
layer.group.setVisible( visible );
|
||||
if (visible) {
|
||||
layer.update();
|
||||
}
|
||||
},
|
||||
options: {
|
||||
# Overridable options:
|
||||
# decel_node: node containing latitude-deg and longitude-deg used to mark the deceleration point
|
||||
# managed_speed_node: boolean property indicating that the aircraft is flying in managed speed mode
|
||||
decel_node: "/instrumentation/nd/symbols/decel",
|
||||
listen: [
|
||||
"fplan_active",
|
||||
"current_wp",
|
||||
"wp_count",
|
||||
"spd_ctrl",
|
||||
"ver_ctrl",
|
||||
"athr"
|
||||
|
@ -746,14 +746,15 @@ canvas.NDStyles["Airbus"] = {
|
|||
},
|
||||
},
|
||||
{
|
||||
id: "taOnly", # the SVG ID
|
||||
impl: { # implementation hash
|
||||
init: func(nd, symbol), # for updateCenter stuff, called during initialization in the ctor
|
||||
predicate: func(nd) getprop("/instrumentation/tcas/inputs/mode") == 2, # the condition
|
||||
is_true: func(nd) nd.symbols.taOnly.show(), # if true, run this
|
||||
is_false: func(nd) nd.symbols.taOnly.hide(), # if false, run this
|
||||
}, # end of taOnly behavior/callbacks
|
||||
}, # end of taOnly
|
||||
# TODO: taOnly doesn"t need to use getprop polling in update(), use a listener instead!
|
||||
id: "taOnly",
|
||||
impl: {
|
||||
init: func(nd, symbol),
|
||||
predicate: func(nd) getprop("/instrumentation/tcas/inputs/mode") == 2,
|
||||
is_true: func(nd) nd.symbols.taOnly.show(),
|
||||
is_false: func(nd) nd.symbols.taOnly.hide(),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tas",
|
||||
impl: {
|
||||
|
@ -761,7 +762,6 @@ canvas.NDStyles["Airbus"] = {
|
|||
predicate: func(nd) getprop("/instrumentation/airspeed-indicator/true-speed-kt") >= 60,
|
||||
is_true: func(nd) {
|
||||
nd.symbols.tas.setText(sprintf("%3.0f",getprop("/instrumentation/airspeed-indicator/true-speed-kt")));
|
||||
nd.symbols.tas.show();
|
||||
},
|
||||
is_false: func(nd){
|
||||
nd.symbols.tas.setText("---");
|
||||
|
@ -1324,7 +1324,7 @@ canvas.NDStyles["Airbus"] = {
|
|||
id:"curTrkPtr",
|
||||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.in_mode("toggle_display_mode", ["APP","VOR","MAP"]) and !nd.get_switch("toggle_centered")
|
||||
predicate: func(nd) (nd.get_switch("toggle_display_mode") == "MAP" and !nd.get_switch("toggle_centered")
|
||||
and (nd.adirs_property.getValue() == 1 or (adirs_3.getValue() == 1 and att_switch.getValue() == nd.attitude_heading_setting))
|
||||
and abs(nd.aircraft_source.get_trk_mag() - nd.aircraft_source.get_hdg_mag()) <= 42),
|
||||
is_true: func(nd) {
|
||||
|
@ -1368,7 +1368,7 @@ canvas.NDStyles["Airbus"] = {
|
|||
impl: {
|
||||
init: func(nd,symbol),
|
||||
predicate: func(nd) (nd.get_switch("toggle_display_mode") == "MAP" and
|
||||
nd.get_switch("toggle_centered") and (nd.change_phase != 1) and
|
||||
nd.get_switch("toggle_centered") and (nd.change_phase != 1) and
|
||||
getprop(nd.options.defaults.lat_ctrl) == 0 and abs(nd.aircraft_source.get_trk_mag() - nd.aircraft_source.get_hdg_mag()) <= 42 and
|
||||
(nd.adirs_property.getValue() == 1 or (adirs_3.getValue() == 1 and att_switch.getValue() == nd.attitude_heading_setting))),
|
||||
is_true: func(nd) {
|
||||
|
@ -1531,6 +1531,8 @@ canvas.NDStyles["Airbus"] = {
|
|||
predicate: func(nd) (getprop("/instrumentation/airspeed-indicator/true-speed-kt") >= 100),
|
||||
is_true: func(nd) {
|
||||
var windDir = pts.Instrumentation.PFD.windDirection.getValue() or 0;
|
||||
if(nd.get_switch("toggle_true_north"))
|
||||
windDir = windDir + getprop("/environment/magnetic-variation-deg");
|
||||
nd.symbols.wind.setText(sprintf("%03.0f / %02.0f",windDir,pts.Instrumentation.PFD.windSpeed.getValue() or 0));
|
||||
},
|
||||
is_false: func(nd) {
|
||||
|
@ -1545,7 +1547,18 @@ canvas.NDStyles["Airbus"] = {
|
|||
predicate: func(nd) (!(nd.in_mode("toggle_display_mode", ["PLAN"]) and (nd.get_switch("toggle_display_type") == "LCD")) and (pts.Instrumentation.PFD.windSpeed.getValue() or 0) >= 2 and getprop("/instrumentation/airspeed-indicator/true-speed-kt") >= 100),
|
||||
is_true: func(nd) {
|
||||
nd.symbols.windArrow.show();
|
||||
var windArrowRot = (pts.Instrumentation.PFD.windDirection.getValue() or 0) - nd.aircraft_source.get_hdg_tru();
|
||||
var windArrowRot = pts.Instrumentation.PFD.windDirection.getValue() or 0;
|
||||
if(nd.in_mode("toggle_display_mode", ["MAP","PLAN"])) {
|
||||
if(nd.get_switch("toggle_true_north"))
|
||||
windArrowRot = windArrowRot - nd.aircraft_source.get_trk_tru();
|
||||
else
|
||||
windArrowRot = windArrowRot - nd.aircraft_source.get_trk_mag();
|
||||
} else {
|
||||
if(nd.get_switch("toggle_true_north"))
|
||||
windArrowRot = windArrowRot - nd.aircraft_source.get_hdg_tru();
|
||||
else
|
||||
windArrowRot = windArrowRot - nd.aircraft_source.get_hdg_mag();
|
||||
}
|
||||
nd.symbols.windArrow.setRotation(windArrowRot*D2R);
|
||||
},
|
||||
is_false: func(nd) nd.symbols.windArrow.hide(),
|
||||
|
|
|
@ -713,7 +713,7 @@ var canvas_pfd = {
|
|||
me.tgt_kts = notification.targetKts;
|
||||
|
||||
if (notification.managedSpd) {
|
||||
if (notification.decel) {
|
||||
if (fmgc.FMGCInternal.decel) {
|
||||
me.tgt_ias = fmgc.FMGCInternal.vappSpeedSet ? fmgc.FMGCInternal.vapp_appr : fmgc.FMGCInternal.vapp;
|
||||
me.tgt_kts = fmgc.FMGCInternal.vappSpeedSet ? fmgc.FMGCInternal.vapp_appr : fmgc.FMGCInternal.vapp;
|
||||
} else if (fmgc.FMGCInternal.phase == 6) {
|
||||
|
@ -2055,7 +2055,6 @@ var input = {
|
|||
detent2: "/fdm/jsbsim/fadec/control-2/detent",
|
||||
|
||||
decision: "/instrumentation/mk-viii/inputs/arinc429/decision-height",
|
||||
decel: "/FMGC/internal/decel",
|
||||
radio: "/FMGC/internal/radio",
|
||||
baro: "/FMGC/internal/baro",
|
||||
|
||||
|
|
|
@ -24,10 +24,7 @@ var altitude = 0;
|
|||
var flap = 0;
|
||||
var flaps = 0;
|
||||
var ktsmach = 0;
|
||||
var kts_sel = 0;
|
||||
var mach_sel = 0;
|
||||
var srsSPD = 0;
|
||||
var decel = 0;
|
||||
var mng_alt_spd = 0;
|
||||
var mng_alt_mach = 0;
|
||||
var altsel = 0;
|
||||
|
@ -72,6 +69,9 @@ var FMGCinit = func {
|
|||
}
|
||||
|
||||
var FMGCInternal = {
|
||||
# phase logic
|
||||
phase: 0,
|
||||
decel: 0,
|
||||
minspeed: 0,
|
||||
maxspeed: 0,
|
||||
clbSpdLim: 250,
|
||||
|
@ -255,7 +255,6 @@ var postInit = func() {
|
|||
|
||||
var FMGCNodes = {
|
||||
costIndex: props.globals.initNode("/FMGC/internal/cost-index", 0, "DOUBLE"),
|
||||
decel: props.globals.initNode("/FMGC/internal/decel", 0, "BOOL"),
|
||||
clean: props.globals.getNode("/FMGC/internal/clean"),
|
||||
flap2: props.globals.getNode("/FMGC/internal/flap-2"),
|
||||
flap3: props.globals.getNode("/FMGC/internal/flap-3"),
|
||||
|
@ -322,9 +321,15 @@ var updateARPT = func {
|
|||
}
|
||||
}
|
||||
|
||||
var updateArptLatLon = func {
|
||||
var apt = nil;
|
||||
var dms = nil;
|
||||
var degrees = nil;
|
||||
var minutes = nil;
|
||||
var sign = nil;
|
||||
var updateArptLatLon = func() {
|
||||
#ref lat
|
||||
dms = getprop("/FMGC/flightplan[2]/wp[0]/lat");
|
||||
apt = airportinfo(FMGCInternal.depApt);
|
||||
dms = apt.lat;
|
||||
degrees = int(dms);
|
||||
minutes = sprintf("%.1f",abs((dms - degrees) * 60));
|
||||
sign = degrees >= 0 ? "N" : "S";
|
||||
|
@ -332,7 +337,7 @@ var updateArptLatLon = func {
|
|||
setprop("/FMGC/internal/align-ref-lat-minutes", minutes);
|
||||
setprop("/FMGC/internal/align-ref-lat-sign", sign);
|
||||
#ref long
|
||||
dms = getprop("/FMGC/flightplan[2]/wp[0]/lon");
|
||||
dms = apt.lon;
|
||||
degrees = int(dms);
|
||||
minutes = sprintf("%.1f",abs((dms - degrees) * 60));
|
||||
sign = degrees >= 0 ? "E" : "W";
|
||||
|
@ -631,7 +636,7 @@ var masterFMGC = maketimer(0.2, func {
|
|||
}
|
||||
}
|
||||
} elsif (FMGCInternal.phase == 4) {
|
||||
if (FMGCNodes.decel.getValue()) {
|
||||
if (FMGCInternal.decel) {
|
||||
newphase = 5;
|
||||
}
|
||||
} elsif (FMGCInternal.phase == 5) {
|
||||
|
@ -648,9 +653,9 @@ var masterFMGC = maketimer(0.2, func {
|
|||
|
||||
if (flightPlanController.num[2].getValue() > 0 and getprop("/FMGC/flightplan[2]/active") == 1 and
|
||||
flightPlanController.arrivalDist <= 15 and (Modes.PFD.FMA.rollMode == "NAV" or Modes.PFD.FMA.rollMode == "LOC" or Modes.PFD.FMA.rollMode == "LOC*") and pts.Position.gearAglFt.getValue() < 9500) { #todo decel pseudo waypoint
|
||||
FMGCNodes.decel.setValue(1);
|
||||
} elsif (FMGCNodes.decel.getValue() and (FMGCInternal.phase == 0 or FMGCInternal.phase == 6)) {
|
||||
FMGCNodes.decel.setValue(0);
|
||||
FMGCInternal.decel = 1;
|
||||
} elsif (FMGCInternal.decel and (FMGCInternal.phase == 0 or FMGCInternal.phase == 6)) {
|
||||
FMGCInternal.decel = 0;
|
||||
}
|
||||
|
||||
tempOverspeed = systems.ADIRS.overspeedVFE.getValue();
|
||||
|
@ -933,7 +938,6 @@ var ManagedSPD = maketimer(0.25, func {
|
|||
if (FMGCInternal.crzSet and FMGCInternal.costIndexSet) {
|
||||
if (Custom.Input.spdManaged.getBoolValue()) {
|
||||
altitude = pts.Instrumentation.Altimeter.indicatedFt.getValue();
|
||||
decel = FMGCNodes.decel.getValue();
|
||||
ktsmach = Input.ktsMach.getValue();
|
||||
srsSPD = srsSpeedNode.getValue();
|
||||
|
||||
|
@ -953,12 +957,12 @@ var ManagedSPD = maketimer(0.25, func {
|
|||
} elsif ((FMGCInternal.phase == 2 or FMGCInternal.phase == 3) and altitude <= FMGCInternal.clbSpdLimAlt) {
|
||||
# Speed is maximum of greendot / climb speed limit
|
||||
FMGCInternal.mngKtsMach = 0;
|
||||
FMGCInternal.mngSpdCmd = decel ? FMGCInternal.minspeed : math.clamp(FMGCInternal.clbSpdLim, FMGCInternal.clean, 999);
|
||||
FMGCInternal.mngSpdCmd = FMGCInternal.decel ? FMGCInternal.minspeed : math.clamp(FMGCInternal.clbSpdLim, FMGCInternal.clean, 999);
|
||||
} elsif ((FMGCInternal.phase == 2 or FMGCInternal.phase == 3) and altitude > (FMGCInternal.clbSpdLimAlt + 20)) {
|
||||
FMGCInternal.mngKtsMach = FMGCInternal.machSwitchover ? 1 : 0;
|
||||
FMGCInternal.mngSpdCmd = FMGCInternal.machSwitchover ? mng_alt_mach : mng_alt_spd;
|
||||
} elsif ((FMGCInternal.phase >= 4 and FMGCInternal.phase <= 6) and altitude > (FMGCInternal.desSpdLimAlt + 20)) {
|
||||
if (decel) {
|
||||
if (FMGCInternal.decel) {
|
||||
FMGCInternal.mngKtsMach = 0;
|
||||
FMGCInternal.mngSpdCmd = FMGCInternal.minspeed;
|
||||
} else {
|
||||
|
@ -968,7 +972,7 @@ var ManagedSPD = maketimer(0.25, func {
|
|||
} elsif ((FMGCInternal.phase >= 4 and FMGCInternal.phase <= 6) and altitude <= FMGCInternal.desSpdLimAlt) {
|
||||
FMGCInternal.mngKtsMach = 0;
|
||||
# Speed is maximum of greendot / descent speed limit
|
||||
FMGCInternal.mngSpdCmd = decel ? FMGCInternal.minspeed : math.clamp(FMGCInternal.desSpdLim, FMGCInternal.clean, 999);
|
||||
FMGCInternal.mngSpdCmd = FMGCInternal.decel ? FMGCInternal.minspeed : math.clamp(FMGCInternal.desSpdLim, FMGCInternal.clean, 999);
|
||||
}
|
||||
|
||||
# Clamp to minspeed, maxspeed
|
||||
|
|
|
@ -290,6 +290,31 @@ var staticText = {
|
|||
},
|
||||
};
|
||||
|
||||
var pseudoItem = {
|
||||
new: func(computer, text) {
|
||||
var pI = {parents:[pseudoItem]};
|
||||
pI.computer = computer;
|
||||
pI.text = text;
|
||||
pI.colour = colour;
|
||||
return pI;
|
||||
},
|
||||
updateLeftText: func() {
|
||||
return [me.text, nil, me.colour];
|
||||
},
|
||||
updateCenterText: func() {
|
||||
return ["----", nil, "wht"];
|
||||
},
|
||||
updateRightText: func() {
|
||||
return ["---/------", " --NM ", "wht"];
|
||||
},
|
||||
pushButtonLeft: func() {
|
||||
mcdu_message(me.computer, "NOT ALLOWED");
|
||||
},
|
||||
pushButtonRight: func() {
|
||||
mcdu_message(me.computer, "NOT ALLOWED");
|
||||
},
|
||||
};
|
||||
|
||||
var fplnPage = { # this one is only created once, and then updated - remember this
|
||||
L1: [nil, nil, "ack"], # content, title, colour
|
||||
L2: [nil, nil, "ack"],
|
||||
|
@ -346,6 +371,8 @@ var fplnPage = { # this one is only created once, and then updated - remember th
|
|||
return "----END OF ALTN F-PLN---";
|
||||
} else if (type == "noAltnFpln") {
|
||||
return "------NO ALTN F-PLN-----";
|
||||
} else if (type == "decel") {
|
||||
return "(DECEL)";
|
||||
} else if (type == "empty") {
|
||||
return "";
|
||||
}
|
||||
|
@ -510,6 +537,12 @@ var fplnPage = { # this one is only created once, and then updated - remember th
|
|||
} else {
|
||||
if (size(me.outputList) >= index and !mcdu_scratchpad.scratchpads[me.computer].showTypeIMsg and !mcdu_scratchpad.scratchpads[me.computer].showTypeIIMsg) {
|
||||
if (size(mcdu_scratchpad.scratchpads[me.computer].scratchpad) > 0) {
|
||||
if (mcdu_scratchpad.scratchpads[me.computer].scratchpad == "CLR") {
|
||||
if (me.outputList[index - 1 + me.scroll].wp.wp_name == "(DECEL)") {
|
||||
mcdu_message(me.computer, "NOT ALLOWED");
|
||||
return;
|
||||
}
|
||||
}
|
||||
var returny = fmgc.flightPlanController.scratchpad(mcdu_scratchpad.scratchpads[me.computer].scratchpad, (index - 1 + me.scroll), me.computer);
|
||||
if (returny == 3) {
|
||||
mcdu_message(me.computer, "DIR TO IN PROGRESS");
|
||||
|
|
|
@ -5,7 +5,11 @@ var OOOIReport = {
|
|||
new: func(state,time=0,fob="") {
|
||||
var report = {parents:[OOOIReport]};
|
||||
report.state = state;
|
||||
report.fob = (fob != "") ? fob : fmgc.FMGCInternal.fob;
|
||||
if (acconfig_weight_kgs.getValue() == 1) {
|
||||
report.fob = (fob != "") ? fob : fmgc.FMGCInternal.fob * LBS2KGS;
|
||||
} else {
|
||||
report.fob = (fob != "") ? fob : fmgc.FMGCInternal.fob;
|
||||
}
|
||||
if (time != 0) {
|
||||
report.time = formatSecToHHMM(time);
|
||||
report.elapsed = time;
|
||||
|
@ -195,4 +199,4 @@ var engine_one_chk_OOOI = setlistener("/engines/engine[0]/state", func {
|
|||
removelistener(engine_one_chk_OOOI);
|
||||
waitingOOOIChange.start();
|
||||
}
|
||||
},0,0);
|
||||
},0,0);
|
||||
|
|
|
@ -3,6 +3,29 @@
|
|||
# Copyright (c) 2022 Josh Davidson (Octal450)
|
||||
# Copyright (c) 2020 Matthew Maring (mattmaring)
|
||||
|
||||
var resetFlightplan = func(i) {
|
||||
fmgc.FMGCInternal.depApt = "";
|
||||
fmgc.FMGCInternal.arrApt = "";
|
||||
fmgc.FMGCInternal.toFromSet = 0;
|
||||
fmgc.FMGCNodes.toFromSet.setValue(0);
|
||||
fmgc.windController.resetDesWinds();
|
||||
setprop("/FMGC/internal/align-ref-lat", 0);
|
||||
setprop("/FMGC/internal/align-ref-long", 0);
|
||||
setprop("/FMGC/internal/align-ref-lat-edit", 0);
|
||||
setprop("/FMGC/internal/align-ref-long-edit", 0);
|
||||
if (fmgc.FMGCInternal.blockConfirmed) {
|
||||
fmgc.FMGCInternal.fuelCalculating = 0;
|
||||
fmgc.fuelCalculating.setValue(0);
|
||||
fmgc.FMGCInternal.fuelCalculating = 1;
|
||||
fmgc.fuelCalculating.setValue(1);
|
||||
}
|
||||
fmgc.flightPlanController.reset(2);
|
||||
fmgc.flightPlanController.init();
|
||||
Simbrief.SimbriefParser.inhibit = 0;
|
||||
fmgc.updateARPT();
|
||||
mcdu_scratchpad.scratchpads[i].empty();
|
||||
}
|
||||
|
||||
var initInputA = func(key, i) {
|
||||
var scratchpad = mcdu_scratchpad.scratchpads[i].scratchpad;
|
||||
if (key == "L1") { #clear coRoute if set
|
||||
|
@ -220,26 +243,7 @@ var initInputA = func(key, i) {
|
|||
mcdu_message(i, "NOT ALLOWED");
|
||||
}
|
||||
else if (scratchpad == "CLR") {
|
||||
fmgc.FMGCInternal.depApt = "";
|
||||
fmgc.FMGCInternal.arrApt = "";
|
||||
fmgc.FMGCInternal.toFromSet = 0;
|
||||
fmgc.FMGCNodes.toFromSet.setValue(0);
|
||||
fmgc.windController.resetDesWinds();
|
||||
setprop("/FMGC/internal/align-ref-lat", 0);
|
||||
setprop("/FMGC/internal/align-ref-long", 0);
|
||||
setprop("/FMGC/internal/align-ref-lat-edit", 0);
|
||||
setprop("/FMGC/internal/align-ref-long-edit", 0);
|
||||
if (fmgc.FMGCInternal.blockConfirmed) {
|
||||
fmgc.FMGCInternal.fuelCalculating = 0;
|
||||
fmgc.fuelCalculating.setValue(0);
|
||||
fmgc.FMGCInternal.fuelCalculating = 1;
|
||||
fmgc.fuelCalculating.setValue(1);
|
||||
}
|
||||
fmgc.flightPlanController.reset(2);
|
||||
fmgc.flightPlanController.init();
|
||||
Simbrief.SimbriefParser.inhibit = 0;
|
||||
fmgc.updateARPT();
|
||||
mcdu_scratchpad.scratchpads[i].empty();
|
||||
resetFlightplan(i);
|
||||
#} else if (scratchpad == "") {
|
||||
#fmgc.FMGCInternal.altSelected = 0;
|
||||
#setprop("MCDU[" ~ i ~ "]/page", "ROUTESELECTION");
|
||||
|
@ -251,18 +255,15 @@ var initInputA = func(key, i) {
|
|||
var froms = size(fromto[0]);
|
||||
var tos = size(fromto[1]);
|
||||
if (froms == 4 and tos == 4) {
|
||||
#route
|
||||
if (fmgc.FMGCInternal.toFromSet == 1 and fmgc.FMGCInternal.arrApt != fromto[1]) {
|
||||
fmgc.windController.resetDesWinds();
|
||||
}
|
||||
resetFlightplan(i);
|
||||
fmgc.FMGCInternal.depApt = fromto[0];
|
||||
fmgc.FMGCInternal.arrApt = fromto[1];
|
||||
atsu.ATISInstances[0].newStation(fromto[0]);
|
||||
atsu.ATISInstances[1].newStation(fromto[1]);
|
||||
fmgc.FMGCInternal.toFromSet = 1;
|
||||
fmgc.FMGCNodes.toFromSet.setValue(1);
|
||||
#scratchpad
|
||||
mcdu_scratchpad.scratchpads[i].empty();
|
||||
|
||||
fmgc.flightPlanController.updateAirports(fromto[0], fromto[1], 2);
|
||||
fmgc.FMGCInternal.altSelected = 0;
|
||||
fmgc.updateARPT();
|
||||
|
|
|
@ -153,7 +153,6 @@ var MCDU_reset = func(i) {
|
|||
fmgc.FMGCInternal.vrset = 0;
|
||||
fmgc.FMGCInternal.v2 = 0;
|
||||
fmgc.FMGCInternal.v2set = 0;
|
||||
|
||||
setprop("/FMGC/internal/accel-agl-ft", 1500); #eventually set to 1500 above runway
|
||||
setprop("/MCDUC/thracc-set", 0);
|
||||
setprop("/FMGC/internal/to-flap", 0);
|
||||
|
@ -1566,6 +1565,8 @@ var button = func(btn, i, event = "") {
|
|||
} else { # up with buttonCLRDown[i]>4
|
||||
buttonCLRDown[i] = 0;
|
||||
}
|
||||
} else if (btn == "LONGCLR") {
|
||||
mcdu_scratchpad.scratchpads[i].empty();
|
||||
} else if (btn == "DOT") {
|
||||
mcdu_scratchpad.scratchpads[i].addChar(".");
|
||||
} else if (btn == "PLUSMINUS") {
|
||||
|
|
|
@ -38,7 +38,7 @@ var perfCLBInput = func(key, i) {
|
|||
if (getprop("/FMGC/internal/activate-twice") == 0) {
|
||||
setprop("/FMGC/internal/activate-twice", 1);
|
||||
fmgc.FMGCInternal.phase = 5;
|
||||
setprop("/FMGC/internal/decel", 1);
|
||||
fmgc.FMGCInternal.decel = 1;
|
||||
setprop("MCDU[" ~ i ~ "]/page", "PERFAPPR");
|
||||
} else {
|
||||
mcdu_message(i, "NOT ALLOWED");
|
||||
|
|
|
@ -32,7 +32,7 @@ var perfCRZInput = func(key, i) {
|
|||
if (getprop("/FMGC/internal/activate-twice") == 0) {
|
||||
setprop("/FMGC/internal/activate-twice", 1);
|
||||
fmgc.FMGCInternal.phase = 5;
|
||||
setprop("/FMGC/internal/decel", 1);
|
||||
fmgc.FMGCInternal.decel = 1;
|
||||
setprop("MCDU[" ~ i ~ "]/page", "PERFAPPR");
|
||||
} else {
|
||||
mcdu_message(i, "NOT ALLOWED");
|
||||
|
|
|
@ -32,7 +32,7 @@ var perfDESInput = func(key, i) {
|
|||
if (getprop("/FMGC/internal/activate-twice") == 0) {
|
||||
setprop("/FMGC/internal/activate-twice", 1);
|
||||
fmgc.FMGCInternal.phase = 5;
|
||||
setprop("/FMGC/internal/decel", 1);
|
||||
fmgc.FMGCInternal.decel = 1;
|
||||
setprop("MCDU[" ~ i ~ "]/page", "PERFAPPR");
|
||||
} else {
|
||||
mcdu_message(i, "NOT ALLOWED");
|
||||
|
|
|
@ -52,7 +52,7 @@ var progCLBInput = func(key, i) {
|
|||
systems.PNEU.pressMode.setValue("CR");
|
||||
setprop("/FMGC/internal/activate-once", 0);
|
||||
setprop("/FMGC/internal/activate-twice", 0);
|
||||
setprop("/FMGC/internal/decel", 0);
|
||||
fmgc.FMGCInternal.decel = 0;
|
||||
}
|
||||
mcdu_scratchpad.scratchpads[i].empty();
|
||||
} else if (int(scratchpad) != nil) {
|
||||
|
@ -66,7 +66,7 @@ var progCLBInput = func(key, i) {
|
|||
systems.PNEU.pressMode.setValue("CR");
|
||||
setprop("/FMGC/internal/activate-once", 0);
|
||||
setprop("/FMGC/internal/activate-twice", 0);
|
||||
setprop("/FMGC/internal/decel", 0);
|
||||
fmgc.FMGCInternal.decel = 0;
|
||||
}
|
||||
} else {
|
||||
mcdu_message(i, "NOT ALLOWED");
|
||||
|
@ -88,7 +88,7 @@ var progCRZInput = func(key, i) {
|
|||
systems.PNEU.pressMode.setValue("CR");
|
||||
setprop("/FMGC/internal/activate-once", 0);
|
||||
setprop("/FMGC/internal/activate-twice", 0);
|
||||
setprop("/FMGC/internal/decel", 0);
|
||||
fmgc.FMGCInternal.decel = 0;
|
||||
}
|
||||
mcdu_scratchpad.scratchpads[i].empty();
|
||||
} else if (int(scratchpad) != nil) {
|
||||
|
@ -102,7 +102,7 @@ var progCRZInput = func(key, i) {
|
|||
systems.PNEU.pressMode.setValue("CR");
|
||||
setprop("/FMGC/internal/activate-once", 0);
|
||||
setprop("/FMGC/internal/activate-twice", 0);
|
||||
setprop("/FMGC/internal/decel", 0);
|
||||
fmgc.FMGCInternal.decel = 0;
|
||||
}
|
||||
} else {
|
||||
mcdu_message(i, "NOT ALLOWED");
|
||||
|
@ -124,7 +124,7 @@ var progDESInput = func(key, i) {
|
|||
systems.PNEU.pressMode.setValue("CR");
|
||||
setprop("/FMGC/internal/activate-once", 0);
|
||||
setprop("/FMGC/internal/activate-twice", 0);
|
||||
setprop("/FMGC/internal/decel", 0);
|
||||
fmgc.FMGCInternal.decel = 0;
|
||||
}
|
||||
mcdu_scratchpad.scratchpads[i].empty();
|
||||
} else if (int(scratchpad) != nil) {
|
||||
|
@ -138,7 +138,7 @@ var progDESInput = func(key, i) {
|
|||
systems.PNEU.pressMode.setValue("CR");
|
||||
setprop("/FMGC/internal/activate-once", 0);
|
||||
setprop("/FMGC/internal/activate-twice", 0);
|
||||
setprop("/FMGC/internal/decel", 0);
|
||||
fmgc.FMGCInternal.decel = 0;
|
||||
}
|
||||
} else {
|
||||
mcdu_message(i, "NOT ALLOWED");
|
||||
|
|
BIN
WebPanel/WebPanel1/img/icon-192.png
Normal file
BIN
WebPanel/WebPanel1/img/icon-192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 181 KiB |
|
@ -1,6 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" translate="no">
|
||||
|
||||
<head>
|
||||
<title>A320 MCDU</title>
|
||||
<meta charset="utf-8">
|
||||
|
@ -8,113 +7,6 @@
|
|||
<link rel="manifest" href="mcdu_manifest.json" />
|
||||
<meta name="apple-mobile-web-app-status-bar" content="#db4938" />
|
||||
<meta name="theme-color" content="#db4938" />
|
||||
<script>
|
||||
let screen;
|
||||
let screen_src;
|
||||
let blank_src;
|
||||
let loading = 0;
|
||||
let scheduled_load = 0;
|
||||
function refresh_screen(force) {
|
||||
if (loading && !force) {
|
||||
scheduled_load = 1;
|
||||
}
|
||||
else {
|
||||
loading = 1;
|
||||
screen.src = screen_src + '&random=' + (new Date).getTime()
|
||||
}
|
||||
}
|
||||
function press_button(type, text) {
|
||||
let request = new XMLHttpRequest;
|
||||
request.open(
|
||||
"POST",
|
||||
window.location.protocol + "//" + window.location.host + "/run.cgi?value=nasal"
|
||||
);
|
||||
request.setRequestHeader("Content-Type", "application/json");
|
||||
let body = JSON.stringify({
|
||||
"name": "",
|
||||
"children": [
|
||||
{
|
||||
"name": "script",
|
||||
"index": 0,
|
||||
"value": "mcdu." + type + "(\"" + text + "\", 0);"
|
||||
}
|
||||
]
|
||||
});
|
||||
request.send(body);
|
||||
request.addEventListener('load', function () {
|
||||
refresh_screen();
|
||||
}, true);
|
||||
}
|
||||
|
||||
var preventzoomaction = function(e) { //https://exceptionshub.com/disable-double-tap-zoom-option-in-browser-on-touch-devices.html
|
||||
var t2 = e.timeStamp;
|
||||
var t1 = e.currentTarget.dataset.lastTouch || t2;
|
||||
var dt = t2 - t1;
|
||||
var fingers = e.touches.length;
|
||||
e.currentTarget.dataset.lastTouch = t2;
|
||||
|
||||
if (!dt || dt > 500 || fingers > 1) return; // not double-tap
|
||||
|
||||
e.preventDefault();
|
||||
e.target.click();
|
||||
};
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
|
||||
let tds = document.querySelectorAll('.input td');
|
||||
for (const td of tds) {
|
||||
td.addEventListener('click', function () {
|
||||
if (td.className == "disabled") return;
|
||||
press_button("button", td.textContent);
|
||||
}, true);
|
||||
td.addEventListener('touchstart', preventzoomaction, true);
|
||||
}
|
||||
tds = document.querySelectorAll('.enter td');
|
||||
for (const td of tds) {
|
||||
td.addEventListener('click', function () {
|
||||
press_button(td.getAttribute("button-side") == "l" ? "lskbutton" : "rskbutton", td.getAttribute("button-id"));
|
||||
}, true);
|
||||
td.addEventListener('touchstart', preventzoomaction, true);
|
||||
}
|
||||
tds = document.querySelectorAll('.menu td');
|
||||
for (const td of tds) {
|
||||
td.addEventListener('click', function () {
|
||||
press_button("pagebutton", td.className);
|
||||
}, true);
|
||||
td.addEventListener('touchstart', preventzoomaction, true);
|
||||
}
|
||||
tds = document.querySelectorAll('.arrows td');
|
||||
for (const td of tds) {
|
||||
td.addEventListener('click', function () {
|
||||
press_button(td.className == 'airport' ? "pagebutton" : "arrowbutton", td.className);
|
||||
}, true);
|
||||
td.addEventListener('touchstart', preventzoomaction, true);
|
||||
}
|
||||
screen = document.querySelector('img');
|
||||
screen.addEventListener('load', function () {
|
||||
loading = 0;
|
||||
if (scheduled_load) {
|
||||
scheduled_load = 0;
|
||||
refresh_screen();
|
||||
}
|
||||
});
|
||||
blank_src = screen_src;
|
||||
screen_src = "/screenshot?canvasindex=13&type=png";
|
||||
screen.addEventListener('error', function () {
|
||||
loading = 0;
|
||||
if (scheduled_load) {
|
||||
refresh_screen();
|
||||
}
|
||||
});
|
||||
screen.addEventListener('abort', function () {
|
||||
loading = 0;
|
||||
if (scheduled_load) {
|
||||
refresh_screen();
|
||||
}
|
||||
});
|
||||
setInterval(function () { refresh_screen(true); }, 1000);
|
||||
}, true);
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-size: 5rem;
|
||||
|
@ -201,33 +93,32 @@
|
|||
<table class="enter" style="width: 100%">
|
||||
<tr>
|
||||
<td><br></td>
|
||||
<td rowspan="8" style="vertical-align: top"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAANgAQMAAACIgIgUAAAAA1BMVEUAAACnej3aAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAgklEQVR42u3BAQEAAACCIP+vbkhAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7wazbwABRxu5owAAAABJRU5ErkJggg=="
|
||||
style="width: 100%" /></td>
|
||||
<td rowspan="8" style="vertical-align: top"><img data-element="lcdimage" src="" style="width: 100%" /></td>
|
||||
<td><br></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td button-id="1" button-side="l">—</td>
|
||||
<td button-id="1" button-side="r">—</td>
|
||||
<td data-button="lskbutton:1">—</td>
|
||||
<td data-button="rskbutton:1">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td button-id="2" button-side="l">—</td>
|
||||
<td button-id="2" button-side="r">—</td>
|
||||
<td data-button="lskbutton:2">—</td>
|
||||
<td data-button="rskbutton:2">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td button-id="3" button-side="l">—</td>
|
||||
<td button-id="3" button-side="r">—</td>
|
||||
<td data-button="lskbutton:3">—</td>
|
||||
<td data-button="rskbutton:3">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td button-id="4" button-side="l">—</td>
|
||||
<td button-id="4" button-side="r">—</td>
|
||||
<td data-button="lskbutton:4">—</td>
|
||||
<td data-button="rskbutton:4">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td button-id="5" button-side="l">—</td>
|
||||
<td button-id="5" button-side="r">—</td>
|
||||
<td data-button="lskbutton:5">—</td>
|
||||
<td data-button="rskbutton:5">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td button-id="6" button-side="l">—</td>
|
||||
<td button-id="6" button-side="r">—</td>
|
||||
<td data-button="lskbutton:6">—</td>
|
||||
<td data-button="rskbutton:6">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><br></td>
|
||||
|
@ -236,107 +127,110 @@
|
|||
</table>
|
||||
<table class="menu" style="width: 100%">
|
||||
<tr>
|
||||
<td class="dirto">DIR</td>
|
||||
<td class="prog">PROG</td>
|
||||
<td class="perf">PERF</td>
|
||||
<td class="init">INIT</td>
|
||||
<td class="data">DATA</td>
|
||||
<td data-button="pagebutton:dirto" class="dirto">DIR</td>
|
||||
<td data-button="pagebutton:prog" class="prog">PROG</td>
|
||||
<td data-button="pagebutton:perf" class="perf">PERF</td>
|
||||
<td data-button="pagebutton:init" class="init">INIT</td>
|
||||
<td data-button="pagebutton:data" class="data">DATA</td>
|
||||
<td></td>
|
||||
<td>BRT</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="f-pln">F-PLN</td>
|
||||
<td class="radnav">RAD<br>NAV</td>
|
||||
<td class="fuel-pred">FUEL<br>PRED</td>
|
||||
<td>SEC<br>F-PLN</td>
|
||||
<td class="atc">ATC<br>COMM</td>
|
||||
<td class="mcdu">MCDU<br>MENU</td>
|
||||
<td data-button="pagebutton:f-pln" class="f-pln">F-PLN</td>
|
||||
<td data-button="pagebutton:radnav" class="radnav">RAD<br>NAV</td>
|
||||
<td data-button="pagebutton:fuel-pred" class="fuel-pred">FUEL<br>PRED</td>
|
||||
<td data-button="" >SEC<br>F-PLN</td>
|
||||
<td data-button="pagebutton:atc" class="atc">ATC<br>COMM</td>
|
||||
<td data-button="pagebutton:mcdu" class="mcdu">MCDU<br>MENU</td>
|
||||
<td>DIM</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="input" style="float: right; width: 62.5%">
|
||||
<tr>
|
||||
<td>A</td>
|
||||
<td>B</td>
|
||||
<td>C</td>
|
||||
<td>D</td>
|
||||
<td>E</td>
|
||||
<td data-button="button:A">A</td>
|
||||
<td data-button="button:B">B</td>
|
||||
<td data-button="button:C">C</td>
|
||||
<td data-button="button:D">D</td>
|
||||
<td data-button="button:E">E</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>F</td>
|
||||
<td>G</td>
|
||||
<td>H</td>
|
||||
<td>I</td>
|
||||
<td>J</td>
|
||||
<td data-button="button:F">F</td>
|
||||
<td data-button="button:G">G</td>
|
||||
<td data-button="button:H">H</td>
|
||||
<td data-button="button:I">I</td>
|
||||
<td data-button="button:J">J</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>K</td>
|
||||
<td>L</td>
|
||||
<td>M</td>
|
||||
<td>N</td>
|
||||
<td>O</td>
|
||||
<td data-button="button:K">K</td>
|
||||
<td data-button="button:L">L</td>
|
||||
<td data-button="button:M">M</td>
|
||||
<td data-button="button:N">N</td>
|
||||
<td data-button="button:O">O</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>P</td>
|
||||
<td>Q</td>
|
||||
<td>R</td>
|
||||
<td>S</td>
|
||||
<td>T</td>
|
||||
<td data-button="button:P">P</td>
|
||||
<td data-button="button:Q">Q</td>
|
||||
<td data-button="button:R">R</td>
|
||||
<td data-button="button:S">S</td>
|
||||
<td data-button="button:T">T</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>U</td>
|
||||
<td>V</td>
|
||||
<td>W</td>
|
||||
<td>X</td>
|
||||
<td>Y</td>
|
||||
<td data-button="button:U">U</td>
|
||||
<td data-button="button:V">V</td>
|
||||
<td data-button="button:W">W</td>
|
||||
<td data-button="button:X">X</td>
|
||||
<td data-button="button:Y">Y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Z</td>
|
||||
<td>/</td>
|
||||
<td style="font-size: 50%">SP</td>
|
||||
<td style="font-size: 33%" class="disabled">OVFY<br>△</td>
|
||||
<td style="font-size: 33%">CLR</td>
|
||||
<td data-button="button:Z">Z</td>
|
||||
<td data-button="button:SLASH">/</td>
|
||||
<td style="font-size: 50%" data-button="button:SP">SP</td>
|
||||
<td style="font-size: 33%" data-button="button:OVFY">OVFY<br>△</td>
|
||||
<td style="font-size: 33%" data-button="button:CLR">CLR</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="arrows" style="width: 29.5%">
|
||||
<tr>
|
||||
<td class="airport">AIR<br>PORT</td>
|
||||
<td data-button="pagebutton:airport" class="airport">AIR<br>PORT</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">←</td>
|
||||
<td class="up">↑</td>
|
||||
<td data-button="arrowbutton:left" class="left">←</td>
|
||||
<td data-button="arrowbutton:up" class="up">↑</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="right">→</td>
|
||||
<td class="down">↓</td>
|
||||
<td data-button="arrowbutton:right" class="right">→</td>
|
||||
<td data-button="arrowbutton:down" class="down">↓</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="input" style="width: 30%">
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td data-button="button:1">1</td>
|
||||
<td data-button="button:2">2</td>
|
||||
<td data-button="button:3">3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>6</td>
|
||||
<td data-button="button:4">4</td>
|
||||
<td data-button="button:5">5</td>
|
||||
<td data-button="button:6">6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>8</td>
|
||||
<td>9</td>
|
||||
<td data-button="button:7">7</td>
|
||||
<td data-button="button:8">8</td>
|
||||
<td data-button="button:9">9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>.</td>
|
||||
<td>0</td>
|
||||
<td data-button="button:DOT">.</td>
|
||||
<td data-button="button:0">0</td>
|
||||
<td
|
||||
style="font-size: 3vw; width: 33.333333333333333333333333333333333333333333333333333333333333333333333%; /* :) */" class="disabled">
|
||||
style="font-size: 3vw; width: 33.34%;" data-button="button:PLUSMINUS">
|
||||
+/-
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<script src="../js/mcdu.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
19
WebPanel/WebPanel1/mcdu_manifest.json
Normal file
19
WebPanel/WebPanel1/mcdu_manifest.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"short_name": "A320MCDU",
|
||||
"name": "A320 Remote MCDU for FG",
|
||||
"icons": [
|
||||
{
|
||||
"src": "img/icon-192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
],
|
||||
"start_url": "/aircraft-dir/WebPanel/WebPanel1/?source=home",
|
||||
"background_color": "#000",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#db4938",
|
||||
"shortcuts": [ ],
|
||||
"description": "Airbus 320 Remote MCDU for Flightgear",
|
||||
"screenshots": [ ]
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en" translate="no">
|
||||
<head>
|
||||
<title>A320 MCDU</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar" content="#333333" />
|
||||
|
@ -83,9 +84,9 @@
|
|||
<div data-button="button:9" class="numberbutton number-col3"></div>
|
||||
</div>
|
||||
<div class="number-row4">
|
||||
<div data-button="button:." class="numberbutton number-col1"></div>
|
||||
<div data-button="button:DOT" class="numberbutton number-col1"></div>
|
||||
<div data-button="button:0" class="numberbutton number-col2"></div>
|
||||
<div data-button="button:-" class="numberbutton number-col3"></div>
|
||||
<div data-button="button:PLUSMINUS" class="numberbutton number-col3"></div>
|
||||
</div>
|
||||
|
||||
<div class="alphabutton-row1">
|
||||
|
@ -130,7 +131,7 @@
|
|||
|
||||
<div class="alphabutton-row6">
|
||||
<div data-button="button:Z" class="alphabutton alphabutton-col1"></div>
|
||||
<div data-button="button:/" class="alphabutton alphabutton-col2"></div>
|
||||
<div data-button="button:SLASH" class="alphabutton alphabutton-col2"></div>
|
||||
<div data-button="button:SP" class="alphabutton alphabutton-col3"></div>
|
||||
<div data-button="button:OVFY" class="alphabutton alphabutton-col4"></div>
|
||||
<div data-button="button:CLR" class="alphabutton alphabutton-col5"></div>
|
||||
|
@ -140,7 +141,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/mcdu.js">
|
||||
<script src="../js/mcdu.js">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,185 +1,185 @@
|
|||
const MCDU = (function () {
|
||||
const screenImageBaseUrl = '/screenshot?canvasindex=13&type=jpg';
|
||||
const refreshInterval = 2000;
|
||||
|
||||
const body = document.body;
|
||||
let currentCacheBust = 0;
|
||||
let lastSentText = '';
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
toggleUsedUniverse
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
function init()
|
||||
{
|
||||
body.dataset.lastTouch = 0;
|
||||
body.addEventListener('touchstart', preventZoomAction, { passive: false });
|
||||
|
||||
registerButtons();
|
||||
registerKeyboardInput();
|
||||
setInterval(refreshScreen, refreshInterval);
|
||||
refreshScreen();
|
||||
}
|
||||
|
||||
function refreshScreen() {
|
||||
loadScreenImage(screenImageBaseUrl)
|
||||
.then(setScreenSrc)
|
||||
.catch(setScreenSrc);
|
||||
}
|
||||
|
||||
function setScreenSrc(url) {
|
||||
url = typeof url === 'string' ? url : '';
|
||||
showScreenImageLoadState(url !== '');
|
||||
document.querySelectorAll('[data-element="lcdimage"]').forEach((imageElement) => {
|
||||
imageElement.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function loadScreenImage(baseUrl) {
|
||||
currentCacheBust = new Date().getTime();
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = baseUrl + '?cacheBust=' + currentCacheBust;
|
||||
const img = new Image;
|
||||
|
||||
img.addEventListener('error', reject);
|
||||
|
||||
img.addEventListener('load', (event) => {
|
||||
showScreenImageLoadState(true);
|
||||
resolve(url);
|
||||
});
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function showScreenImageLoadState(isOK) {
|
||||
if (!isOK) {
|
||||
console.log('fail');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleUsedUniverse() {
|
||||
body.setAttribute('data-used-universe', body.getAttribute('data-used-universe') === '1' ? '0' : '1');
|
||||
}
|
||||
|
||||
function registerButtons() {
|
||||
document.querySelectorAll('[data-button]').forEach((buttonElement) => {
|
||||
const buttonFunction = getButtonFunction(buttonElement);
|
||||
if (!(typeof buttonFunction === 'function')) {
|
||||
return;
|
||||
}
|
||||
buttonElement.addEventListener('click', buttonFunction);
|
||||
buttonElement.addEventListener('touchstart', preventZoomAction, true);
|
||||
});
|
||||
}
|
||||
|
||||
function registerKeyboardInput() {
|
||||
const keyTranslation = {
|
||||
BACKSPACE: 'CLR'
|
||||
};
|
||||
body.addEventListener('keyup', (event) => {
|
||||
const key = event.key.toUpperCase();
|
||||
if (key.match(/^[A-Z0-9/\-+.\ ]$/)) {
|
||||
if (key === '+' || key === '-') {
|
||||
return sendPlusMinusKey();
|
||||
}
|
||||
return sendButtonpress('button', key);
|
||||
}
|
||||
|
||||
const translatedKey = keyTranslation[key];
|
||||
if (translatedKey) {
|
||||
return sendButtonpress('button', translatedKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getButtonFunction(buttonElement) {
|
||||
const buttonActions = buttonElement.getAttribute('data-button').split(':');
|
||||
const actionKey = buttonActions[0];
|
||||
const actionValue = buttonActions[1];
|
||||
|
||||
if(!actionKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionKey === 'toggleUsedUniverse') {
|
||||
return toggleUsedUniverse;
|
||||
}
|
||||
|
||||
if (actionKey === 'button' && actionValue === '-') {
|
||||
return sendPlusMinusKey;
|
||||
}
|
||||
|
||||
return function () {
|
||||
sendButtonpress(actionKey, actionValue);
|
||||
};
|
||||
}
|
||||
|
||||
function sendPlusMinusKey() {
|
||||
if (lastSentText === '-') {
|
||||
sendButtonpress('button', 'CLR')
|
||||
.then(() => {
|
||||
sendButtonpress('button', '+');
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastSentText === '+') {
|
||||
sendButtonpress('button', 'CLR')
|
||||
.then(() => {
|
||||
sendButtonpress('button', '-');
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
sendButtonpress('button', '-');
|
||||
}
|
||||
|
||||
function sendButtonpress(type, text) {
|
||||
// console.log({ type, text });
|
||||
let request = new XMLHttpRequest;
|
||||
request.open("POST", "/run.cgi?value=nasal");
|
||||
request.setRequestHeader("Content-Type", "application/json");
|
||||
let body = JSON.stringify({
|
||||
"name": "",
|
||||
"children": [
|
||||
{
|
||||
"name": "script",
|
||||
"index": 0,
|
||||
"value": "mcdu." + type + "(\"" + text + "\", 0);"
|
||||
}
|
||||
]
|
||||
});
|
||||
request.send(body);
|
||||
return new Promise((resolve) => {
|
||||
request.addEventListener('load', () => {
|
||||
lastSentText = text;
|
||||
refreshScreen();
|
||||
resolve();
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
//https://exceptionshub.com/disable-double-tap-zoom-option-in-browser-on-touch-devices.html
|
||||
function preventZoomAction(event) {
|
||||
const t2 = event.timeStamp;
|
||||
const touchedElement = event.currentTarget;
|
||||
const t1 = touchedElement.dataset.lastTouch || t2;
|
||||
const dt = t2 - t1;
|
||||
const fingers = event.touches.length;
|
||||
touchedElement.dataset.lastTouch = t2;
|
||||
|
||||
if (!dt || dt > 500 || fingers > 1) {
|
||||
// no double-tap
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.target.click();
|
||||
}
|
||||
})();
|
||||
const MCDU = (function () {
|
||||
const screenImageBaseUrl = '/screenshot?canvasindex=13&type=jpg';
|
||||
const refreshInterval = 2000;
|
||||
|
||||
const body = document.body;
|
||||
let currentCacheBust = 0;
|
||||
let lastSentText = '';
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
toggleUsedUniverse
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
function init()
|
||||
{
|
||||
body.dataset.lastTouch = 0;
|
||||
body.addEventListener('touchstart', preventZoomAction, { passive: false });
|
||||
|
||||
registerButtons();
|
||||
registerKeyboardInput();
|
||||
setInterval(refreshScreen, refreshInterval);
|
||||
refreshScreen();
|
||||
}
|
||||
|
||||
function refreshScreen() {
|
||||
loadScreenImage(screenImageBaseUrl)
|
||||
.then(setScreenSrc)
|
||||
.catch(setScreenSrc);
|
||||
}
|
||||
|
||||
function setScreenSrc(url) {
|
||||
url = typeof url === 'string' ? url : '';
|
||||
showScreenImageLoadState(url !== '');
|
||||
document.querySelectorAll('[data-element="lcdimage"]').forEach((imageElement) => {
|
||||
imageElement.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function loadScreenImage(baseUrl) {
|
||||
currentCacheBust = new Date().getTime();
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = baseUrl + '?cacheBust=' + currentCacheBust;
|
||||
const img = new Image;
|
||||
|
||||
img.addEventListener('error', reject);
|
||||
|
||||
img.addEventListener('load', (event) => {
|
||||
showScreenImageLoadState(true);
|
||||
resolve(url);
|
||||
});
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function showScreenImageLoadState(isOK) {
|
||||
if (!isOK) {
|
||||
console.log('fail');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleUsedUniverse() {
|
||||
body.setAttribute('data-used-universe', body.getAttribute('data-used-universe') === '1' ? '0' : '1');
|
||||
}
|
||||
|
||||
function registerButtons() {
|
||||
document.querySelectorAll('[data-button]').forEach((buttonElement) => {
|
||||
const buttonFunction = getButtonFunction(buttonElement);
|
||||
if (!(typeof buttonFunction === 'function')) {
|
||||
return;
|
||||
}
|
||||
buttonElement.addEventListener('click', buttonFunction);
|
||||
buttonElement.addEventListener('touchstart', preventZoomAction, true);
|
||||
});
|
||||
}
|
||||
|
||||
function registerKeyboardInput() {
|
||||
const keyTranslation = {
|
||||
BACKSPACE: 'CLR'
|
||||
};
|
||||
body.addEventListener('keyup', (event) => {
|
||||
const key = event.key.toUpperCase();
|
||||
if (key.match(/^[A-Z0-9/\-+.\ ]$/)) {
|
||||
if (key === '+' || key === '-') {
|
||||
return sendPlusMinusKey();
|
||||
}
|
||||
return sendButtonpress('button', key);
|
||||
}
|
||||
|
||||
const translatedKey = keyTranslation[key];
|
||||
if (translatedKey) {
|
||||
return sendButtonpress('button', translatedKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getButtonFunction(buttonElement) {
|
||||
const buttonActions = buttonElement.getAttribute('data-button').split(':');
|
||||
const actionKey = buttonActions[0];
|
||||
const actionValue = buttonActions[1];
|
||||
|
||||
if(!actionKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionKey === 'toggleUsedUniverse') {
|
||||
return toggleUsedUniverse;
|
||||
}
|
||||
|
||||
if (actionKey === 'button' && actionValue === '-') {
|
||||
return sendPlusMinusKey;
|
||||
}
|
||||
|
||||
return function () {
|
||||
sendButtonpress(actionKey, actionValue);
|
||||
};
|
||||
}
|
||||
|
||||
function sendPlusMinusKey() {
|
||||
if (lastSentText === '-') {
|
||||
sendButtonpress('button', 'CLR')
|
||||
.then(() => {
|
||||
sendButtonpress('button', '+');
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastSentText === '+') {
|
||||
sendButtonpress('button', 'CLR')
|
||||
.then(() => {
|
||||
sendButtonpress('button', '-');
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
sendButtonpress('button', '-');
|
||||
}
|
||||
|
||||
function sendButtonpress(type, text) {
|
||||
// console.log({ type, text });
|
||||
let request = new XMLHttpRequest;
|
||||
request.open("POST", "/run.cgi?value=nasal");
|
||||
request.setRequestHeader("Content-Type", "application/json");
|
||||
let body = JSON.stringify({
|
||||
"name": "",
|
||||
"children": [
|
||||
{
|
||||
"name": "script",
|
||||
"index": 0,
|
||||
"value": "mcdu." + type + "(\"" + text + "\", 0);"
|
||||
}
|
||||
]
|
||||
});
|
||||
request.send(body);
|
||||
return new Promise((resolve) => {
|
||||
request.addEventListener('load', () => {
|
||||
lastSentText = text;
|
||||
refreshScreen();
|
||||
resolve();
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
//https://exceptionshub.com/disable-double-tap-zoom-option-in-browser-on-touch-devices.html
|
||||
function preventZoomAction(event) {
|
||||
const t2 = event.timeStamp;
|
||||
const touchedElement = event.currentTarget;
|
||||
const t1 = touchedElement.dataset.lastTouch || t2;
|
||||
const dt = t2 - t1;
|
||||
const fingers = event.touches.length;
|
||||
touchedElement.dataset.lastTouch = t2;
|
||||
|
||||
if (!dt || dt > 500 || fingers > 1) {
|
||||
// no double-tap
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.target.click();
|
||||
}
|
||||
})();
|
166
WebPanel/js/mcdu.js
Normal file
166
WebPanel/js/mcdu.js
Normal file
|
@ -0,0 +1,166 @@
|
|||
const MCDU = (function () {
|
||||
const screenImageBaseUrl = '/screenshot?canvasindex=13&type=jpg';
|
||||
const refreshInterval = 2000;
|
||||
|
||||
const body = document.body;
|
||||
let currentCacheBust = 0;
|
||||
let lastSentText = '';
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
toggleUsedUniverse
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
function init() {
|
||||
body.dataset.lastTouch = 0;
|
||||
body.addEventListener('touchstart', preventZoomAction, { passive: false });
|
||||
|
||||
registerButtons();
|
||||
registerKeyboardInput();
|
||||
setInterval(refreshScreen, refreshInterval);
|
||||
refreshScreen();
|
||||
}
|
||||
|
||||
function refreshScreen() {
|
||||
loadScreenImage(screenImageBaseUrl)
|
||||
.then(setScreenSrc)
|
||||
.catch(setScreenSrc);
|
||||
}
|
||||
|
||||
function setScreenSrc(url) {
|
||||
url = typeof url === 'string' ? url : '';
|
||||
showScreenImageLoadState(url !== '');
|
||||
document.querySelectorAll('[data-element="lcdimage"]').forEach((imageElement) => {
|
||||
imageElement.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function loadScreenImage(baseUrl) {
|
||||
currentCacheBust = new Date().getTime();
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = baseUrl + '?cacheBust=' + currentCacheBust;
|
||||
const img = new Image;
|
||||
|
||||
img.addEventListener('error', reject);
|
||||
|
||||
img.addEventListener('load', (event) => {
|
||||
showScreenImageLoadState(true);
|
||||
resolve(url);
|
||||
});
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function showScreenImageLoadState(isOK) {
|
||||
if (!isOK) {
|
||||
console.log('fail');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleUsedUniverse() {
|
||||
body.setAttribute('data-used-universe', body.getAttribute('data-used-universe') === '1' ? '0' : '1');
|
||||
}
|
||||
|
||||
function registerButtons() {
|
||||
document.querySelectorAll('[data-button]').forEach((buttonElement) => {
|
||||
const buttonFunction = getButtonFunction(buttonElement);
|
||||
if (!(typeof buttonFunction === 'function')) {
|
||||
return;
|
||||
}
|
||||
buttonElement.addEventListener('click', buttonFunction);
|
||||
buttonElement.addEventListener('touchstart', preventZoomAction, true);
|
||||
});
|
||||
let btn = document.querySelector("[data-button=\"button:CLR\"]");
|
||||
if (btn) btn.addEventListener("contextmenu",function(e){
|
||||
e.preventDefault();
|
||||
sendButtonpress('button', 'LONGCLR');
|
||||
});
|
||||
}
|
||||
|
||||
function registerKeyboardInput() {
|
||||
const keyTranslation = {
|
||||
BACKSPACE: 'CLR',
|
||||
DELETE: 'LONGCLR',
|
||||
'+': 'PLUSMINUS',
|
||||
'-': 'PLUSMINUS',
|
||||
'/': 'SLASH',
|
||||
'.': 'DOT',
|
||||
'^': 'OVFY',
|
||||
' ': 'SP'
|
||||
};
|
||||
body.addEventListener('keyup', (event) => {
|
||||
const key = event.key.toUpperCase();
|
||||
if (key.match(/^[A-Z0-9]$/)) {
|
||||
return sendButtonpress('button', key);
|
||||
}
|
||||
const translatedKey = keyTranslation[key]||false;
|
||||
if (translatedKey) return sendButtonpress('button', translatedKey);
|
||||
});
|
||||
}
|
||||
|
||||
function getButtonFunction(buttonElement) {
|
||||
const buttonActions = buttonElement.getAttribute('data-button').split(':');
|
||||
const actionKey = buttonActions[0];
|
||||
const actionValue = buttonActions[1];
|
||||
|
||||
if(!actionKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionKey === 'toggleUsedUniverse') {
|
||||
return toggleUsedUniverse;
|
||||
}
|
||||
|
||||
return function () {
|
||||
sendButtonpress(actionKey, actionValue);
|
||||
};
|
||||
}
|
||||
|
||||
function sendButtonpress(type, text) {
|
||||
//console.log({ type, text });
|
||||
let request = new XMLHttpRequest;
|
||||
request.open("POST", "/run.cgi?value=nasal");
|
||||
request.setRequestHeader("Content-Type", "application/json");
|
||||
let body = JSON.stringify({
|
||||
"name": "",
|
||||
"children": [
|
||||
{
|
||||
"name": "script",
|
||||
"index": 0,
|
||||
"value": "mcdu." + type + "(\"" + text + "\", 0);"
|
||||
}
|
||||
]
|
||||
});
|
||||
request.send(body);
|
||||
return new Promise((resolve) => {
|
||||
request.addEventListener('load', () => {
|
||||
lastSentText = text;
|
||||
refreshScreen();
|
||||
resolve();
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
//https://exceptionshub.com/disable-double-tap-zoom-option-in-browser-on-touch-devices.html
|
||||
function preventZoomAction(event) {
|
||||
const t2 = event.timeStamp;
|
||||
const touchedElement = event.currentTarget;
|
||||
const t1 = touchedElement.dataset.lastTouch || t2;
|
||||
const dt = t2 - t1;
|
||||
const fingers = event.touches.length;
|
||||
touchedElement.dataset.lastTouch = t2;
|
||||
|
||||
if (!dt || dt > 500 || fingers > 1) {
|
||||
// no double-tap
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.target.click();
|
||||
}
|
||||
})();
|
|
@ -17,12 +17,14 @@
|
|||
background-color: #005670;
|
||||
text-align: center;
|
||||
color: #e4e4e4;
|
||||
font-size: 16px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
color: #8db9ca;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
@ -55,8 +57,9 @@
|
|||
.choice {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: calc(49% - 2vw);
|
||||
padding: 1vh 1vw 75% 1vw;
|
||||
width: calc(50% - 40px);
|
||||
padding: 15px;
|
||||
padding-bottom: 75%;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid transparent;
|
||||
|
@ -69,11 +72,12 @@
|
|||
|
||||
.choice img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: 1rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
max-height: 100%;
|
||||
max-height: auto;
|
||||
max-width: 100%;
|
||||
border-radius: 8px;
|
||||
box-shadow: 4px 4px 6px #00000063;
|
||||
}
|
||||
|
@ -99,9 +103,9 @@
|
|||
<div class="chooser">
|
||||
<a href="WebPanel1/index.html" class="choice choice--1">
|
||||
<span class="button">
|
||||
abstract
|
||||
basic
|
||||
</span>
|
||||
<img src="WebPanel1/screenshot.jpg">
|
||||
<img src="WebPanel1/img/screenshot.jpg">
|
||||
</a>
|
||||
<a href="WebPanel2/index.html" class="choice choice--2">
|
||||
<span class="button">
|
||||
|
|
Loading…
Add table
Reference in a new issue