diff --git a/.gitignore b/.gitignore index 76d34f10..7628b7e4 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ alog.txt __pycache__/ *.py[cod] *$py.class + +# node +static/node_modules/ diff --git a/Actions/actions.py b/Actions/actions.py index f094f16d..562d62a6 100644 --- a/Actions/actions.py +++ b/Actions/actions.py @@ -306,7 +306,7 @@ def shutdown(self): self.data.ui_queue1.put("WebMCP", "shutdown", "") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def turnOff(self): @@ -364,7 +364,7 @@ def defineHome(self, posX="", posY=""): self.data.ui_queue1.put("Action", "homePositionMessage", position) return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def home(self): @@ -394,7 +394,7 @@ def home(self): self.data.gcode_queue.put("G00 Z0 ") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def resetChainLengths(self): @@ -412,7 +412,7 @@ def resetChainLengths(self): self.data.gcode_queue.put("G21 ") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def defineZ0(self): @@ -424,7 +424,7 @@ def defineZ0(self): self.data.gcode_queue.put("G10 Z0 ") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def stopZ(self): @@ -439,7 +439,7 @@ def stopZ(self): self.data.gcode_queue.queue.clear() return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def setMinZ(self): @@ -451,7 +451,7 @@ def setMinZ(self): self.data.gcode_queue.put("B18") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def setMaxZ(self): @@ -463,7 +463,7 @@ def setMaxZ(self): self.data.gcode_queue.put("B17") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def clearZ(self): @@ -475,7 +475,7 @@ def clearZ(self): self.data.gcode_queue.put("B19") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def getZlimits(self): @@ -487,7 +487,7 @@ def getZlimits(self): self.data.gcode_queue.put("B20") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def startRun(self): @@ -518,7 +518,7 @@ def startRun(self): return False except Exception as e: # something goes wrong, stop uploading. - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") self.data.uploadFlag = 0 self.data.gcodeIndex = 0 return False @@ -529,7 +529,7 @@ def stopRun(self): :return: ''' try: - self.data.console_queue.put("stopping run") + self.data.console_queue.put(f"{__name__}: stopping run") # this is necessary because of the way PID data is being processed. Otherwise could potentially get stuck # in PID test self.data.inPIDPositionTest = False @@ -541,7 +541,7 @@ def stopRun(self): with self.data.gcode_queue.mutex: self.data.gcode_queue.queue.clear() # TODO: app.onUploadFlagChange(self.stopRun, 0) edit: not sure this is needed anymore - self.data.console_queue.put("Gcode stopped") + self.data.console_queue.put(f"{__name__}: Gcode stopped") self.sendGCodePositionUpdate(self.data.gcodeIndex) # notify UI client to clear any alarm that's active because a stop has been process. self.data.ui_queue1.put("Action", "clearAlarm", "") @@ -551,7 +551,7 @@ def stopRun(self): self.data.gpioActions.causeAction("PauseLED", "off") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def moveToDefault(self): @@ -577,7 +577,7 @@ def moveToDefault(self): return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def testMotors(self): @@ -589,7 +589,7 @@ def testMotors(self): self.data.gcode_queue.put("B04 ") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def wipeEEPROM(self, extent): @@ -622,7 +622,7 @@ def wipeEEPROM(self, extent): #self.timer.start() return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def pauseRun(self): @@ -633,7 +633,7 @@ def pauseRun(self): try: if self.data.uploadFlag == 1: self.data.uploadFlag = 2 - self.data.console_queue.put("Run Paused") + self.data.console_queue.put(f"{__name__}: Run Paused") self.data.ui_queue1.put("Action", "setAsResume", "") # The idea was to be able to make sure the machine returns to # the correct z-height after a pause in the event the user raised/lowered the bit. @@ -646,7 +646,7 @@ def pauseRun(self): self.data.gpioActions.causeAction("PauseLED", "on") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def resumeRun(self): @@ -713,7 +713,7 @@ def resumeRun(self): self.data.gpioActions.causeAction("PauseLED", "off") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def returnToCenter(self): @@ -734,7 +734,7 @@ def returnToCenter(self): self.data.gcode_queue.put("G00 X0.0 Y0.0 ") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def clearGCode(self): @@ -746,7 +746,7 @@ def clearGCode(self): self.data.gcodeFile.clearGcodeFile() return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def moveGcodeZ(self, moves): @@ -770,7 +770,7 @@ def moveGcodeZ(self, moves): else: return False except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def moveGcodeIndex(self, dist, index=False): @@ -804,12 +804,12 @@ def moveGcodeIndex(self, dist, index=False): retval = self.sendGCodePositionUpdate(recalculate=True) return retval except Exception as e: - self.data.console_queue.put(str(e)) - self.data.console_queue.put("Unable to update position for new gcode line") + self.data.console_queue.put(f"{__name__}: {e}") + self.data.console_queue.put(f"{__name__}: Unable to update position for new gcode line") return False return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def move(self, direction, distToMove): @@ -852,7 +852,7 @@ def move(self, direction, distToMove): self.data.config.setValue("Computed Settings", "distToMove", distToMove) return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def moveZ(self, direction, distToMoveZ): @@ -893,7 +893,7 @@ def moveZ(self, direction, distToMoveZ): self.data.gcode_queue.put("G20 ") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False @@ -916,7 +916,7 @@ def touchZ(self): self.data.measureRequest = self.defineZ0() return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def updateSetting(self, setting, value, fromGcode = False): @@ -928,7 +928,7 @@ def updateSetting(self, setting, value, fromGcode = False): :return: ''' try: - self.data.console_queue.put("at update setting from gcode("+str(fromGcode)+"): "+setting+" with value: "+str(value)) + self.data.console_queue.put(f"{__name__}: '{setting}': '{value}' - (from gcode? {fromGcode})") # if front page button has been pressed or serialPortThread is going to send gcode with a G21 or G20.. if setting == "toInches" or setting == "toMM": # this shouldn't be reached any more after I reordered the processActions function @@ -1030,7 +1030,7 @@ def updateSetting(self, setting, value, fromGcode = False): self.data.ui_queue1.put("Action", "distToMoveUpdateZ", "") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def rotateSprocket(self, sprocket, time): @@ -1048,7 +1048,7 @@ def rotateSprocket(self, sprocket, time): self.data.gcode_queue.put("B11 "+sprocket+" S-100 T"+str(abs(time))) return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False @@ -1086,7 +1086,7 @@ def setSprockets(self, sprocket, degrees): self.data.gcode_queue.put("G90 ") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def setSprocketAutomatic(self): @@ -1099,7 +1099,7 @@ def setSprocketAutomatic(self): self.data.gcode_queue.put("B10 L") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def getLeftChainLength(self, dist): @@ -1154,7 +1154,7 @@ def setSprocketsZero(self): self.data.gcode_queue.put("B06 L0 R0 ") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def setSprocketsDefault(self): @@ -1168,7 +1168,7 @@ def setSprocketsDefault(self): self.data.gcode_queue.put("B08 ") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def updatePorts(self): @@ -1176,7 +1176,7 @@ def updatePorts(self): Updates the list of ports found on the computer. :return: ''' - self.data.console_queue.put("at Update Ports") + self.data.console_queue.put(f"{__name__}: at Update Ports") portsList = [] try: if sys.platform.startswith("linux") or sys.platform.startswith("cygwin"): @@ -1199,7 +1199,7 @@ def updatePorts(self): self.data.ui_queue1.put("Action", "updatePorts", "") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def acceptTriangularKinematicsResults(self): @@ -1212,7 +1212,7 @@ def acceptTriangularKinematicsResults(self): self.data.triangularCalibration.acceptTriangularKinematicsResults() return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False @@ -1237,7 +1237,7 @@ def calibrate(self, result): cut34YoffsetEst, ) except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def holeyCalibrate(self, result): @@ -1262,7 +1262,7 @@ def holeyCalibrate(self, result): calibrationError, ) except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False @@ -1279,7 +1279,7 @@ def sendGCode(self, gcode): self.data.gcode_queue.put(line) return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def macro(self, number): @@ -1302,7 +1302,7 @@ def macro(self, number): return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False ''' @@ -1314,7 +1314,7 @@ def macro(self, number): self.data.gcode_queue.put(macro) return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def testImage(self): @@ -1327,7 +1327,7 @@ def testImage(self): self.data.opticalCalibration.testImage() return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def adjustCenter(self, dist): @@ -1343,7 +1343,7 @@ def adjustCenter(self, dist): return False return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def processSettingRequest(self, section, setting): @@ -1381,7 +1381,7 @@ def processSettingRequest(self, section, setting): data = {"curveX": xCurve, "curveY": yCurve} self.data.ui_queue1.put("Action", "updateOpticalCalibrationCurve", data) except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") elif setting == "calibrationError": # send calibration error matrix try: @@ -1391,7 +1391,7 @@ def processSettingRequest(self, section, setting): data = {"errorX": errorX, "errorY": errorY} self.data.ui_queue1.put("Action", "updateOpticalCalibrationError", data) except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") elif setting == "pauseButtonSetting": # send current pause button state try: @@ -1400,7 +1400,7 @@ def processSettingRequest(self, section, setting): else: return setting, "Resume" except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") else: # send whatever is being request if setting == "units": @@ -1595,7 +1595,7 @@ def moveTo(self, posX, posY): return True return False except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def processGCode(self): @@ -1810,7 +1810,7 @@ def adjustChain(self, chain): try: for x in range(6): self.data.ui_queue1.put("Action", "updateTimer", chain+":"+str(5-x)) - self.data.console_queue.put("Action:updateTimer_" + chain + ":" + str(5 - x)) + self.data.console_queue.put(f"{__name__}: Action:updateTimer_{chain}:{5 - x}") time.sleep(1) if chain == "left": self.data.gcode_queue.put("B02 L1 R0 ") @@ -1822,7 +1822,7 @@ def adjustChain(self, chain): self.data.gcode_queue.put("B10 R") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def issueStopCommand(self, distance): @@ -1832,7 +1832,7 @@ def issueStopCommand(self, distance): self.data.gcode_queue.queue.clear() return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def toggleCamera(self): @@ -1850,7 +1850,7 @@ def toggleCamera(self): self.data.camera.stop() return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def cameraStatus(self): @@ -1869,7 +1869,7 @@ def cameraStatus(self): self.data.ui_queue1.put("Action", "updateCamera", "on") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def queryCamera(self): @@ -1882,7 +1882,7 @@ def queryCamera(self): self.data.camera.getSettings() return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def velocityPIDTest(self, parameters): @@ -1904,7 +1904,7 @@ def velocityPIDTest(self, parameters): self.data.gcode_queue.put(gcodeString) return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def positionPIDTest(self, parameters): @@ -1928,7 +1928,7 @@ def positionPIDTest(self, parameters): self.data.gcode_queue.put(gcodeString) return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def velocityPIDTestRun(self, command, msg): @@ -1958,7 +1958,7 @@ def velocityPIDTestRun(self, command, msg): self.data.PIDVelocityTestData = [] print("PID velocity test started") except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def positionPIDTestRun(self, command, msg): @@ -1982,7 +1982,7 @@ def positionPIDTestRun(self, command, msg): self.data.PIDPositionTestData = [] print("PID position test started") except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def updateGCode(self, gcode): @@ -2004,7 +2004,7 @@ def updateGCode(self, gcode): self.data.ui_queue1.put("Action", "gcodeUpdate", "") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def downloadDiagnostics(self): @@ -2021,7 +2021,7 @@ def downloadDiagnostics(self): zipObj.close() return filename except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def clearLogs(self): @@ -2029,7 +2029,7 @@ def clearLogs(self): retval = self.data.logger.deleteLogFiles() return retval except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False ''' def checkForLatestPyRelease(self): @@ -2164,7 +2164,7 @@ def backupWebControl(self): #zipObj.close() return filename except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False @@ -2178,7 +2178,7 @@ def restoreWebControl(self, fileName): self.data.gcode_queue.put("$$") return retval except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False def setFakeServo(self, value): @@ -2189,5 +2189,5 @@ def setFakeServo(self, value): self.data.gcode_queue.put("B99 OFF") return True except Exception as e: - self.data.console_queue.put(str(e)) + self.data.console_queue.put(f"{__name__}: {e}") return False diff --git a/App/__init__.py b/App/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/App/data.py b/App/data.py new file mode 100644 index 00000000..e5400204 --- /dev/null +++ b/App/data.py @@ -0,0 +1,37 @@ +import sys + +from DataStructures.data import Data + + +def init_data(app): + """ + Initialize the Flask app's data member + """ + app.data = Data() + app.data.config.computeSettings(None, None, None, True) + app.data.config.parseFirmwareVersions() + app.data.units = app.data.config.getValue("Computed Settings", "units") + app.data.tolerance = app.data.config.getValue("Computed Settings", "tolerance") + app.data.distToMove = app.data.config.getValue("Computed Settings", "distToMove") + app.data.distToMoveZ = app.data.config.getValue("Computed Settings", "distToMoveZ") + app.data.unitsZ = app.data.config.getValue("Computed Settings", "unitsZ") + app.data.comport = app.data.config.getValue("Maslow Settings", "COMport") + app.data.gcodeShift = [ + float(app.data.config.getValue("Advanced Settings", "homeX")), + float(app.data.config.getValue("Advanced Settings", "homeY")), + ] + + version = sys.version_info + + if version[:2] > (3, 5): + app.data.pythonVersion35 = False + app.data.console_queue.put(f"{__name__}: Using routines for Python > 3.5") + else: + app.data.pythonVersion35 = True + app.data.console_queue.put(f"{__name__}: Using routines for Python == 3.5") + + app.data.firstRun = False + # app.previousPosX = 0.0 + # app.previousPosY = 0.0 + + return app diff --git a/App/route.py b/App/route.py new file mode 100644 index 00000000..48d91893 --- /dev/null +++ b/App/route.py @@ -0,0 +1,568 @@ +from werkzeug.utils import secure_filename + +from flask import ( + jsonify, + render_template, + request, + send_file, + send_from_directory, +) +from flask_mobility.decorators import mobile_template + + +def init_route(app): + route_type = "route" + app.data.console_queue.put(f"{__name__}: Initializing {route_type} handling for the app") + + @app.route("/") + @mobile_template("{mobile/}") + def index(template): + app.data.logger.resetIdler() + macro1Title = (app.data.config.getValue("Maslow Settings", "macro1_title"))[:6] + macro2Title = (app.data.config.getValue("Maslow Settings", "macro2_title"))[:6] + if template == "mobile/": + return render_template( + "frontpage3d_mobile.html", + modalStyle="modal-lg", + macro1_title=macro1Title, + macro2_title=macro2Title, + ) + else: + return render_template( + "frontpage3d.html", + modalStyle="mw-100 w-75", + macro1_title=macro1Title, + macro2_title=macro2Title, + ) + + + @app.route("/controls") + @mobile_template("/controls/{mobile/}") + def controls(template): + app.data.logger.resetIdler() + macro1Title = (app.data.config.getValue("Maslow Settings", "macro1_title"))[:6] + macro2Title = (app.data.config.getValue("Maslow Settings", "macro2_title"))[:6] + if template == "/controls/mobile/": + return render_template( + "frontpage3d_mobilecontrols.html", + modalStyle="modal-lg", + isControls=True, + macro1_title=macro1Title, + macro2_title=macro2Title, + ) + else: + return render_template( + "frontpage3d.html", + modalStyle="mw-100 w-75", + macro1_title=macro1Title, + macro2_title=macro2Title, + ) + + + @app.route("/text") + @mobile_template("/text/{mobile/}") + def text(template): + app.data.logger.resetIdler() + macro1Title = (app.data.config.getValue("Maslow Settings", "macro1_title"))[:6] + macro2Title = (app.data.config.getValue("Maslow Settings", "macro2_title"))[:6] + if template == "/text/mobile": + return render_template( + "frontpageText_mobile.html", + modalStyle="modal-lg", + isControls=True, + macro1_title=macro1Title, + macro2_title=macro2Title, + ) + else: + return render_template( + "frontpageText.html", + modalStyle="mw-100 w-75", + macro1_title=macro1Title, + macro2_title=macro2Title, + ) + + + @app.route("/logs") + @mobile_template("/logs/{mobile/}") + def logs(template): + app.data.console_queue.put(f"{__name__}: here") + app.data.logger.resetIdler() + if template == "/logs/mobile/": + return render_template("logs.html") + else: + return render_template("logs.html") + + + @app.route("/maslowSettings", methods=["POST"]) + def maslowSettings(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + app.data.config.updateSettings("Maslow Settings", result) + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + + + @app.route("/advancedSettings", methods=["POST"]) + def advancedSettings(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + app.data.config.updateSettings("Advanced Settings", result) + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + + + @app.route("/webControlSettings", methods=["POST"]) + def webControlSettings(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + app.data.config.updateSettings("WebControl Settings", result) + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + + + @app.route("/cameraSettings", methods=["POST"]) + def cameraSettings(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + app.data.config.updateSettings("Camera Settings", result) + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + + + @app.route("/gpioSettings", methods=["POST"]) + def gpioSettings(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + app.data.config.updateSettings("GPIO Settings", result) + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + + + @app.route("/uploadGCode", methods=["POST"]) + def uploadGCode(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + directory = result["selectedDirectory"] + # app.data.console_queue.put(f"{__name__}: {directory}") + f = request.files.getlist("file[]") + app.data.console_queue.put(f"{__name__}: {f}") + home = app.data.config.getHome() + app.data.config.setValue( + "Computed Settings", "lastSelectedDirectory", directory + ) + + if len(f) > 0: + firstFile = f[0] + for file in f: + app.data.gcodeFile.filename = ( + home + + "/.WebControl/gcode/" + + directory + + "/" + + secure_filename(file.filename) + ) + file.save(app.data.gcodeFile.filename) + app.data.gcodeFile.filename = ( + home + + "/.WebControl/gcode/" + + directory + + "/" + + secure_filename(firstFile.filename) + ) + returnVal = app.data.gcodeFile.loadUpdateFile() + if returnVal: + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/openGCode", methods=["POST"]) + def openGCode(): + app.data.logger.resetIdler() + if request.method == "POST": + f = request.form["selectedGCode"] + app.data.console_queue.put(f"{__name__}:openGCode selectedGcode={f}") + tDir = f.split("/") + app.data.config.setValue("Computed Settings", "lastSelectedDirectory", tDir[0]) + home = app.data.config.getHome() + app.data.gcodeFile.filename = home + "/.WebControl/gcode/" + f + app.data.config.setValue("Maslow Settings", "openFile", tDir[1]) + returnVal = app.data.gcodeFile.loadUpdateFile() + if returnVal: + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/saveGCode", methods=["POST"]) + def saveGCode(): + app.data.logger.resetIdler() + if request.method == "POST": + app.data.console_queue.put(f"{__name__}: {request.form}") + f = request.form["fileName"] + d = request.form["selectedDirectory"] + app.data.console_queue.put(f"{__name__}:saveGCode selectedGcode={f}") + app.data.config.setValue("Computed Settings", "lastSelectedDirectory", d) + home = app.data.config.getHome() + returnVal = app.data.gcodeFile.saveFile(f, home + "/.WebControl/gcode/" + d) + """ + tDir = f.split("/") + app.data.config.setValue("Computed Settings","lastSelectedDirectory",tDir[0]) + home = app.data.config.getHome() + app.data.gcodeFile.filename = home+"/.WebControl/gcode/" + f + app.data.config.setValue("Maslow Settings", "openFile", tDir[1]) + returnVal = app.data.gcodeFile.loadUpdateFile() + """ + if returnVal: + app.data.config.setValue("Maslow Settings", "openFile", f) + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/openBoard", methods=["POST"]) + def openBoard(): + app.data.logger.resetIdler() + if request.method == "POST": + f = request.form["selectedBoard"] + app.data.console_queue.put(f"selectedBoard={f}") + tDir = f.split("/") + app.data.config.setValue( + "Computed Settings", "lastSelectedBoardDirectory", tDir[0] + ) + home = app.data.config.getHome() + app.data.gcodeFile.filename = home + "/.WebControl/boards/" + f + app.data.config.setValue("Maslow Settings", "openBoardFile", tDir[1]) + returnVal = app.data.boardManager.loadBoard(home + "/.WebControl/boards/" + f) + if returnVal: + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/saveBoard", methods=["POST"]) + def saveBoard(): + app.data.logger.resetIdler() + if request.method == "POST": + app.data.console_queue.put(f"{__name__}: {request.form}") + f = request.form["fileName"] + d = request.form["selectedDirectory"] + app.data.console_queue.put(f"selectedBoard={f}") + app.data.config.setValue("Computed Settings", "lastSelectedBoardDirectory", d) + home = app.data.config.getHome() + returnVal = app.data.boardManager.saveBoard( + f, home + "/.WebControl/boards/" + d + ) + app.data.config.setValue("Maslow Settings", "openBoardFile", f) + if returnVal: + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/importFile", methods=["POST"]) + def importFile(): + app.data.logger.resetIdler() + if request.method == "POST": + f = request.files["file"] + home = app.data.config.getHome() + secureFilename = home + "/.WebControl/imports/" + secure_filename(f.filename) + f.save(secureFilename) + returnVal = app.data.importFile.importGCini(secureFilename) + if returnVal: + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/importFileWCJSON", methods=["POST"]) + def importFileJSON(): + app.data.logger.resetIdler() + if request.method == "POST": + f = request.files["file"] + home = app.data.config.getHome() + secureFilename = home + "/.WebControl/imports/" + secure_filename(f.filename) + f.save(secureFilename) + returnVal = app.data.importFile.importWebControlJSON(secureFilename) + if returnVal: + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/importRestoreWebControl", methods=["POST"]) + def importRestoreWebControl(): + app.data.logger.resetIdler() + if request.method == "POST": + f = request.files["file"] + home = app.data.config.getHome() + secureFilename = home + "/" + secure_filename(f.filename) + f.save(secureFilename) + returnVal = app.data.actions.restoreWebControl(secureFilename) + if returnVal: + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/sendGCode", methods=["POST"]) + def sendGcode(): + app.data.logger.resetIdler() + # app.data.console_queue.put(f"{__name__}: {request.form)#['gcodeInput']}") + if request.method == "POST": + returnVal = app.data.actions.sendGCode(request.form["gcode"].rstrip()) + if returnVal: + message = {"status": 200} + resp = jsonify("success") + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify("failed") + resp.status_code = 500 + return resp + + + @app.route("/triangularCalibration", methods=["POST"]) + def triangularCalibration(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + ( + motorYoffsetEst, + rotationRadiusEst, + chainSagCorrectionEst, + cut34YoffsetEst, + ) = app.data.actions.calibrate(result) + # app.data.console_queue.put(f"{__name__}: {returnVal}") + if motorYoffsetEst: + message = { + "status": 200, + "data": { + "motorYoffset": motorYoffsetEst, + "rotationRadius": rotationRadiusEst, + "chainSagCorrection": chainSagCorrectionEst, + "calibrationError": cut34YoffsetEst, + }, + } + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/holeyCalibration", methods=["POST"]) + def holeyCalibration(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + ( + motorYoffsetEst, + distanceBetweenMotors, + leftChainTolerance, + rightChainTolerance, + calibrationError, + ) = app.data.actions.holeyCalibrate(result) + # app.data.console_queue.put(f"{__name__}: {returnVal}") + if motorYoffsetEst: + message = { + "status": 200, + "data": { + "motorYoffset": motorYoffsetEst, + "distanceBetweenMotors": distanceBetweenMotors, + "leftChainTolerance": leftChainTolerance, + "rightChainTolerance": rightChainTolerance, + "calibrationError": calibrationError, + }, + } + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/opticalCalibration", methods=["POST"]) + def opticalCalibration(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify(message) + resp.status_code = 500 + return resp + + + @app.route("/quickConfigure", methods=["POST"]) + def quickConfigure(): + app.data.logger.resetIdler() + if request.method == "POST": + result = request.form + app.data.config.updateQuickConfigure(result) + message = {"status": 200} + resp = jsonify(message) + resp.status_code = 200 + return resp + + + @app.route("/editGCode", methods=["POST"]) + def editGCode(): + app.data.logger.resetIdler() + # app.data.console_queue.put(f"{__name__}: {request.form['gcode']}") + if request.method == "POST": + returnVal = app.data.actions.updateGCode(request.form["gcode"].rstrip()) + if returnVal: + message = {"status": 200} + resp = jsonify("success") + resp.status_code = 200 + return resp + else: + message = {"status": 500} + resp = jsonify("failed") + resp.status_code = 500 + return resp + + + @app.route("/downloadDiagnostics", methods=["GET"]) + def downloadDiagnostics(): + app.data.logger.resetIdler() + if request.method == "GET": + returnVal = app.data.actions.downloadDiagnostics() + if returnVal != False: + app.data.console_queue.put(f"{__name__}: {returnVal}") + return send_file(returnVal, as_attachment=True) + else: + resp = jsonify("failed") + resp.status_code = 500 + return resp + + + @app.route("/backupWebControl", methods=["GET"]) + def backupWebControl(): + app.data.logger.resetIdler() + if request.method == "GET": + returnVal = app.data.actions.backupWebControl() + if returnVal != False: + app.data.console_queue.put(f"{__name__}: {returnVal}") + return send_file(returnVal) + else: + resp = jsonify("failed") + resp.status_code = 500 + return resp + + + @app.route("/editBoard", methods=["POST"]) + def editBoard(): + app.data.logger.resetIdler() + if request.method == "POST": + returnVal = app.data.boardManager.editBoard(request.form) + if returnVal: + resp = jsonify("success") + resp.status_code = 200 + return resp + else: + resp = jsonify("failed") + resp.status_code = 500 + return resp + + + @app.route("/trimBoard", methods=["POST"]) + def trimBoard(): + app.data.logger.resetIdler() + if request.method == "POST": + returnVal = app.data.boardManager.trimBoard(request.form) + if returnVal: + resp = jsonify("success") + resp.status_code = 200 + return resp + else: + resp = jsonify("failed") + resp.status_code = 500 + return resp + + + @app.route("/assets/") + def sendDocs(path): + app.data.console_queue.put(f"{__name__}: {path}") + return send_from_directory("docs/assets/", path) diff --git a/App/socket_catchall.py b/App/socket_catchall.py new file mode 100644 index 00000000..2b445150 --- /dev/null +++ b/App/socket_catchall.py @@ -0,0 +1,13 @@ +from app import socketio + + +def init_socket_catchall(app): + route_type = "socket" + app.data.console_queue.put(f"{__name__}: Initializing {route_type} handling for catch all") + + @socketio.on("*") + def catch_all(event, sid, data): + app.data.console_queue.put(f"{__name__}: Hit the socket catch_all") + app.data.console_queue.put(f"{__name__}: Event: {event}") + app.data.console_queue.put(f"{__name__}: SID: {sid}") + app.data.console_queue.put(f"{__name__}: Data: {data}") diff --git a/App/socket_maslowcnc.py b/App/socket_maslowcnc.py new file mode 100644 index 00000000..630697c2 --- /dev/null +++ b/App/socket_maslowcnc.py @@ -0,0 +1,167 @@ +from app import socketio + +from flask import current_app, json, request + + +def init_socket_maslowcnc(app): + route_type = "socket" + namespace = "/MaslowCNC" + app.data.console_queue.put(f"{__name__}: Initializing {route_type} handling for {namespace}") + + @socketio.on("my event", namespace=namespace) + def my_event(msg): + app.data.console_queue.put(f"{__name__}: {msg['data']}") + + @socketio.on("modalClosed", namespace=namespace) + def modalClosed(msg): + app.data.logger.resetIdler() + data = json.dumps({"title": msg["data"]}) + socketio.emit( + "message", + {"command": "closeModals", "data": data, "dataFormat": "json"}, + namespace=namespace, + ) + + @socketio.on("contentModalClosed", namespace=namespace) + def contentModalClosed(msg): + # Note, this shouldn't be called anymore + # todo: cleanup + app.data.logger.resetIdler() + data = json.dumps({"title": msg["data"]}) + app.data.console_queue.put(f"{__name__}: {data}") + # socketio.emit("message", {"command": "closeContentModals", "data": data, "dataFormat": "json"}, + # namespace=namespace, ) + + """ + todo: cleanup + not used + @socketio.on("actionModalClosed", namespace=namespace) + def actionModalClosed(msg): + app.data.logger.resetIdler() + data = json.dumps({"title": msg["data"]}) + socketio.emit("message", {"command": "closeActionModals", "data": data, "dataFormat": "json"}, + namespace=namespace, ) + """ + + @socketio.on("alertModalClosed", namespace=namespace) + def alertModalClosed(msg): + app.data.logger.resetIdler() + data = json.dumps({"title": msg["data"]}) + socketio.emit( + "message", + {"command": "closeAlertModals", "data": data, "dataFormat": "json"}, + namespace=namespace, + ) + + @socketio.on("requestPage", namespace=namespace) + def requestPage(msg): + app.data.console_queue.put(f"{__name__}: requestPage: {msg}") + app.data.logger.resetIdler() + app.data.console_queue.put(f"{__name__}: {request.sid}") + client = request.sid + try: + ( + page, + title, + isStatic, + modalSize, + modalType, + resume, + ) = app.webPageProcessor.createWebPage( + msg["data"]["page"], msg["data"]["isMobile"], msg["data"]["args"] + ) + # if msg["data"]["page"] != "help": + # client = "all" + data = json.dumps( + { + "title": title, + "message": page, + "isStatic": isStatic, + "modalSize": modalSize, + "modalType": modalType, + "resume": resume, + "client": client, + } + ) + socketio.emit( + "message", + {"command": "activateModal", "data": data, "dataFormat": "json"}, + namespace=namespace, + ) + except Exception as e: + app.data.console_queue.put(f"{__name__}: {e}") + + @socketio.on("connect", namespace=namespace) + def test_connect(msg): + app.data.console_queue.put(f"{__name__}: client connected to socket for namespace {namespace}") + app.data.console_queue.put(f"{__name__}: {request.sid}") + + if not app.data.connectionStatus: + app.data.console_queue.put(f"{__name__}: Attempting to re-establish connection to controller") + app.data.serialPort.openConnection() + + app.data.console_queue.put(f"{__name__}: Emit connect reply back to the client for namespace {namespace}") + socketio.emit("after connect", {"data": "Connected", "count": 0}, namespace=namespace) + address = app.data.hostAddress + data = json.dumps({"hostAddress": address}) + app.data.console_queue.put(f"{__name__}: {data}") + socketio.emit( + "message", + {"command": "hostAddress", "data": data, "dataFormat": "json"}, + namespace=namespace, + ) + if app.data.pyInstallUpdateAvailable: + app.data.ui_queue1.put("Action", "pyinstallUpdate", "on") + + @socketio.on("disconnect", namespace=namespace) + def test_disconnect(): + app.data.console_queue.put(f"{__name__}: Client disconnected") + + @socketio.on("action", namespace=namespace) + def command(msg): + app.data.logger.resetIdler() + retval = app.data.actions.processAction(msg) + if retval == "Shutdown": + app.data.console_queue.put(f"{__name__}: Shutting Down") + socketio.stop() + app.data.console_queue.put(f"{__name__}: Shutdown") + if retval == "TurnOffRPI": + app.data.console_queue.put(f"{__name__}: Turning off RPI") + os.system("sudo poweroff") + + @socketio.on("settingRequest", namespace=namespace) + def settingRequest(msg): + app.data.logger.resetIdler() + # didn't move to actions.. this request is just to send it computed values.. keeping it here makes it faster than putting it through the UIProcessor + setting, value = app.data.actions.processSettingRequest( + msg["data"]["section"], msg["data"]["setting"] + ) + if setting is not None: + data = json.dumps({"setting": setting, "value": value}) + socketio.emit( + "message", + {"command": "requestedSetting", "data": data, "dataFormat": "json"}, + namespace=namespace, + ) + + @socketio.on("updateSetting", namespace=namespace) + def updateSetting(msg): + app.data.console_queue.put(f"{__name__}: updateSetting: {msg}") + app.data.logger.resetIdler() + if not app.data.actions.updateSetting(msg["data"]["setting"], msg["data"]["value"]): + app.data.ui_queue1.put("Alert", "Alert", "Error updating setting") + + @socketio.on("checkForGCodeUpdate", namespace=namespace) + def checkForGCodeUpdate(msg): + app.data.logger.resetIdler() + # this currently doesn't check for updated gcode, it just resends it.. + ## the gcode file might change the active units so we need to inform the UI of the change. + app.data.ui_queue1.put("Action", "unitsUpdate", "") + app.data.ui_queue1.put("Action", "gcodeUpdate", "") + app.data.console_queue.put(f"{__name__}: updateSetting: {msg}") + + @socketio.on("checkForBoardUpdate", namespace=namespace) + def checkForBoardUpdate(msg): + app.data.logger.resetIdler() + # this currently doesn't check for updated board, it just resends it.. + app.data.ui_queue1.put("Action", "boardUpdate", "") diff --git a/App/socket_maslowcnclogs.py b/App/socket_maslowcnclogs.py new file mode 100644 index 00000000..84e40086 --- /dev/null +++ b/App/socket_maslowcnclogs.py @@ -0,0 +1,21 @@ +from app import socketio + +from flask import request + + +def init_socket_maslowcnclogs(app): + route_type = "socket" + namespace = "/MaslowCNCLogs" + app.data.console_queue.put(f"{__name__}: Initializing {route_type} handling for {namespace}") + + @socketio.on("connect", namespace=namespace) + def log_connect(): + app.data.console_queue.put(f"{__name__}: Client connected to socket for namespace {namespace}") + app.data.console_queue.put(f"{__name__}: {request.sid}") + socketio.emit( + "after connect", {"data": "Connected", "count": 0}, namespace=namespace + ) + + @socketio.on("disconnect", namespace=namespace) + def log_disconnect(): + app.data.console_queue.put(f"{__name__}: Client disconnected") diff --git a/App/socket_mcp.py b/App/socket_mcp.py new file mode 100644 index 00000000..0b47da97 --- /dev/null +++ b/App/socket_mcp.py @@ -0,0 +1,19 @@ +from app import socketio + +from flask import request + + +def init_socket_mcp(app): + route_type = "socket" + namespace = "/WebMCP" + app.data.console_queue.put(f"{__name__}: Initializing {route_type} handling for {namespace}") + + @socketio.on("checkInRequested", namespace=namespace) + def checkInRequested(): + socketio.emit("checkIn", namespace=namespace) + + @socketio.on("connect", namespace=namespace) + def watchdog_connect(): + app.data.console_queue.put("watchdog connected") + app.data.console_queue.put(request.sid) + socketio.emit("connect", namespace=namespace) diff --git a/Background/LogStreamer.py b/Background/LogStreamer.py index 5830123f..8f0af610 100644 --- a/Background/LogStreamer.py +++ b/Background/LogStreamer.py @@ -11,6 +11,7 @@ ''' class LogStreamer: app = None + namespace = "/MaslowCNCLogs" def start(self, _app): self.app = _app @@ -29,18 +30,18 @@ def start(self, _app): message = self.app.data.alog_streamer_queue.get() if message != "": socketio.emit("message", {"log": "alog", "data": message, "state": loggerState, - "dataFormat": "text"}, namespace="/MaslowCNCLogs", ) + "dataFormat": "text"}, namespace=self.namespace, ) timeSinceLastLoggerState = time.time() # process a line from the log queue if not self.app.data.log_streamer_queue.empty(): message = self.app.data.log_streamer_queue.get() if message != "": socketio.emit("message", {"log": "log", "data": message, "state": loggerState, - "dataFormat": "text"}, namespace="/MaslowCNCLogs", ) + "dataFormat": "text"}, namespace=self.namespace, ) timeSinceLastLoggerState = time.time() currentTime = time.time() if currentTime - timeSinceLastLoggerState > 5: socketio.emit("message", {"log": "state", "state": loggerState, "dataFormat":"text"}, - namespace="/MaslowCNCLogs", ) + namespace=self.namespace, ) timeSinceLastLoggerState = currentTime diff --git a/Background/UIProcessor.py b/Background/UIProcessor.py index b6468c02..a3c138ba 100644 --- a/Background/UIProcessor.py +++ b/Background/UIProcessor.py @@ -1,4 +1,4 @@ -from __main__ import socketio +from app import socketio import json import math @@ -17,18 +17,20 @@ ''' class UIProcessor: - app = None - lastCameraTime = 0 - lastHealthCheck = 0 - previousUploadFlag = None - previousCurrentTool = None - previousPositioningMode = None + def __init__(self): + self.app = None + self.lastCameraTime = 0 + self.lastHealthCheck = 0 + self.previousUploadFlag = None + self.previousCurrentTool = None + self.previousPositioningMode = None + self.namespace = "/MaslowCNC" def start(self, _app): - self.app = _app - self.app.data.console_queue.put("starting UI") + self.app.data.console_queue.put(f"{__name__}: starting") with self.app.app_context(): + self.app.data.console_queue.put(f"{__name__}: entering processing loop") while True: time.sleep(0.001) # send health message @@ -104,7 +106,7 @@ def start(self, _app): data = json.dumps({"setting": "pauseButtonSetting", "value": "Resume"}) socketio.emit("message", {"command": "requestedSetting", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) elif message[0:12] == "Tool Change:": # Tool change message detected. # not sure what manualzaxisadjust is.. ### @@ -364,7 +366,7 @@ def activateModal(self, title, message, modalType, resume="false", progress="fal {"title": title, "message": message, "resume": resume, "progress": progress, "modalSize": "small", "modalType": modalType}) socketio.emit("message", {"command": "activateModal", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", + namespace=self.namespace, ) def sendAlarm(self, message): @@ -375,7 +377,7 @@ def sendAlarm(self, message): ''' data = json.dumps({"message": message}) socketio.emit("message", {"command": "alarm", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", + namespace=self.namespace, ) def sendControllerMessage(self, message): @@ -386,7 +388,7 @@ def sendControllerMessage(self, message): :return: ''' socketio.emit("message", {"command": "controllerMessage", "data": json.dumps(message), "dataFormat": "json"}, - namespace="/MaslowCNC") + namespace=self.namespace) def sendWebMCPMessage(self, message): ''' @@ -405,7 +407,7 @@ def sendPositionMessage(self, position): :return: ''' socketio.emit("message", {"command": "positionMessage", "data": json.dumps(position), "dataFormat": "json"}, - namespace="/MaslowCNC") + namespace=self.namespace) def sendErrorValueMessage(self, position): ''' @@ -414,7 +416,7 @@ def sendErrorValueMessage(self, position): :return: ''' socketio.emit("message", {"command": "errorValueMessage", "data": json.dumps(position), "dataFormat": "json"}, - namespace="/MaslowCNC") + namespace=self.namespace) def sendCameraMessage(self, message, _data=""): ''' @@ -426,7 +428,7 @@ def sendCameraMessage(self, message, _data=""): ''' data = json.dumps({"command": message, "data": _data}) socketio.emit( - "message", {"command": "cameraMessage", "data": data, "dataFormat": "json"}, namespace="/MaslowCNC" + "message", {"command": "cameraMessage", "data": data, "dataFormat": "json"}, namespace=self.namespace ) def updatePIDData(self, message, _data=""): @@ -438,7 +440,7 @@ def updatePIDData(self, message, _data=""): ''' data = json.dumps({"command": message, "data": _data}) socketio.emit( - "message", {"command": "updatePIDData", "data": data, "dataFormat": "json"}, namespace="/MaslowCNC" + "message", {"command": "updatePIDData", "data": data, "dataFormat": "json"}, namespace=self.namespace ) def sendGcodeUpdate(self): @@ -452,19 +454,19 @@ def sendGcodeUpdate(self): # turn on spinner on UI clients socketio.emit("message", {"command": "showFPSpinner", "data": len(self.app.data.compressedGCode3D), "dataFormat": "int"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) # pause to let the spinner get turned on. time.sleep(0.25) # send the data. Once processed by the UI client, the client will turn off the spinner. socketio.emit("message", {"command": "gcodeUpdateCompressed", "data": self.app.data.compressedGCode3D, "dataFormat": "base64"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) self.app.data.console_queue.put("Sent Gcode compressed") else: #send "" if there is no compressed data (i.e., because there's no gcode to compress) socketio.emit("message", {"command": "gcodeUpdateCompressed", "data": "", "dataFormat": "base64"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) def sendBoardUpdate(self): ''' @@ -477,7 +479,7 @@ def sendBoardUpdate(self): self.app.data.console_queue.put("Sending Board Data") socketio.emit("message", {"command": "boardDataUpdate", "data": boardData, "dataFormat": "json"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) self.app.data.console_queue.put("Sent Board Data") cutData = self.app.data.boardManager.getCurrentBoard().getCompressedCutData() @@ -485,11 +487,11 @@ def sendBoardUpdate(self): self.app.data.console_queue.put("Sending Board Cut Data compressed") socketio.emit("message", {"command": "showFPSpinner", "data": 1, "dataFormat": "int"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) time.sleep(0.25) socketio.emit("message", {"command": "boardCutDataUpdateCompressed", "data": cutData, "dataFormat": "base64"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) self.app.data.console_queue.put("Sent Board Cut Data compressed") def unitsUpdate(self): @@ -502,7 +504,7 @@ def unitsUpdate(self): ) data = json.dumps({"setting": "units", "value": units}) socketio.emit("message", {"command": "requestedSetting", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) def distToMoveUpdate(self): ''' @@ -514,7 +516,7 @@ def distToMoveUpdate(self): ) data = json.dumps({"setting": "distToMove", "value": distToMove}) socketio.emit("message", {"command": "requestedSetting", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) def unitsUpdateZ(self): ''' @@ -526,7 +528,7 @@ def unitsUpdateZ(self): ) data = json.dumps({"setting": "unitsZ", "value": unitsZ}) socketio.emit("message", {"command": "requestedSetting", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) def distToMoveUpdateZ(self): ''' @@ -538,7 +540,7 @@ def distToMoveUpdateZ(self): ) data = json.dumps({"setting": "distToMoveZ", "value": distToMoveZ}) socketio.emit("message", {"command": "requestedSetting", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", ) + namespace=self.namespace, ) def processMessage(self, _message): ''' @@ -549,6 +551,7 @@ def processMessage(self, _message): ''' # parse the message to get to its components msg = json.loads(_message) + self.app.data.console_queue.put(f"{__name__}: processMessage command:{msg['command']}, message:{msg['message']}") if msg["command"] == "WebMCP": self.sendWebMCPMessage(msg["message"]) if msg["command"] == "SendAlarm": @@ -577,7 +580,7 @@ def processMessage(self, _message): elif msg["message"] == "clearAlarm": msg["data"] = json.dumps({"data": ""}) socketio.emit("message", {"command": msg["message"], "data": msg["data"], "dataFormat": "json"}, - namespace="/MaslowCNC") + namespace=self.namespace) # I'm pretty sure this can be cleaned up into just a continuation of elif's else: if msg["message"] == "setAsPause": @@ -595,12 +598,12 @@ def processMessage(self, _message): msg["message"] = "getZLimits" msg["data"] = json.dumps({"maxZlimit": max, "minZlimit": min}) socketio.emit("message", {"command": msg["message"], "data": msg["data"], "dataFormat": "json"}, - namespace="/MaslowCNC") + namespace=self.namespace) # I think I was working on clearing on an issue with the formatting of messages so I added this. I think the # only function that calls it is the serialPortThread when webcontrol connects to the arduino. elif msg["command"] == "TextMessage": socketio.emit("message", {"command": "controllerMessage", "data": msg["data"], "dataFormat": "json"}, - namespace="/MaslowCNC") + namespace=self.namespace) # Alerts activate the modal. If alerts are sent on top of each other, it can mess up the UI client display. elif msg["command"] == "Alert": self.activateModal(msg["message"], msg["data"], "alert") @@ -618,7 +621,7 @@ def sendCalibrationImage(self, message, _data): data = json.dumps({"command": message, "data": _data}) socketio.emit( - "message", {"command": "updateCalibrationImage", "data": data, "dataFormat": "json"}, namespace="/MaslowCNC" + "message", {"command": "updateCalibrationImage", "data": data, "dataFormat": "json"}, namespace=self.namespace ) def performHealthCheck(self): @@ -630,6 +633,7 @@ def performHealthCheck(self): ''' currentTime = time.time() if currentTime - self.lastHealthCheck > 5: + self.app.data.console_queue.put(f"{__name__}: performing health check") self.lastHealthCheck = currentTime load = max(psutil.cpu_percent(interval=None, percpu=True)) weAreBufferingLines = bool(int(self.app.data.config.getValue("Maslow Settings", "bufferOn"))) @@ -642,8 +646,7 @@ def performHealthCheck(self): "bufferSize": bufferSize, } socketio.emit("message", {"command": "healthMessage", "data": json.dumps(healthData), "dataFormat": "json"}, - namespace="/MaslowCNC") - self.performStatusCheck(True) + namespace=self.namespace) def performStatusCheck(self, healthCheckCalled=False): ''' @@ -673,7 +676,7 @@ def performStatusCheck(self, healthCheckCalled=False): } socketio.emit("message", {"command": "statusMessage", "data": json.dumps(statusData), "dataFormat": "json"}, - namespace="/MaslowCNC") + namespace=self.namespace) def isChainLengthZero(self, msg): #Message: Unable to find valid machine position for chain lengths 0.00, 0.00 Left Chain Length diff --git a/Background/WebMCPProcessor.py b/Background/WebMCPProcessor.py index 280ea23b..aa7cb1c8 100644 --- a/Background/WebMCPProcessor.py +++ b/Background/WebMCPProcessor.py @@ -34,7 +34,7 @@ def connect(self, _app): class ConsoleProcessor(MakesmithInitFuncs): def start(self): - print("Starting Console Queue Processor") + print(f"{__name__}: Starting Console Queue Processor") while True: time.sleep(0.001) while ( diff --git a/Background/webcamVideoStream.py b/Background/webcamVideoStream.py index 8b601dc1..92fc7858 100644 --- a/Background/webcamVideoStream.py +++ b/Background/webcamVideoStream.py @@ -31,7 +31,7 @@ def __init__(self, src=0): # be stopped #self.stopped = True #self.suspended = False - print("Camera initialized") + print(f"{__name__}: Camera initialized") def getSettings(self, src=0): cameraOff = False @@ -68,7 +68,7 @@ def getSettings(self, src=0): def start(self, src = 0): # start the thread to read frames from the video stream - print("Starting camera thread") + print(f"{__name__}: Starting camera thread") if self.stream is None: self.stream = cv2.VideoCapture(src) (self.grabbed, self.frame) = self.stream.read() @@ -81,11 +81,11 @@ def start(self, src = 0): self.th = threading.Thread(target=self.update) self.th.daemon = True self.th.start() - print("Camera thread started") + print(f"{__name__}: Camera thread started") #self.data.ui_queue.put("Action:updateCamera_on") self.data.ui_queue1.put("Action", "updateCamera", "on") else: - print("Camera already started") + print(f"{__name__}: Camera already started") return self def update(self): @@ -117,7 +117,7 @@ def update(self): def read(self): # return the frame most recently read - #print("Reading camera frame") + #print(f"{__name__}: Reading camera frame") if self.suspended: (self.grabbed, self.frame) = self.stream.read() self.suspended = False @@ -131,7 +131,7 @@ def read(self): def stop(self): # indicate that the thread should be stopped - print("Stopping camera") + print(f"{__name__}: Stopping camera") self.stopped = True def status(self): @@ -152,7 +152,7 @@ def changeSetting(self, key, value): self.setVideoSize() if key == 'cameraSleep' and value != self.cameraSleep: if value<1: - print("changing sleep interval") + print(f"{__name__}: changing sleep interval") self.cameraSleep = value if self.stream is not None: self.stopped = True diff --git a/Connection/nonVisibleWidgets.py b/Connection/nonVisibleWidgets.py index 21f02d6a..53f88dcc 100644 --- a/Connection/nonVisibleWidgets.py +++ b/Connection/nonVisibleWidgets.py @@ -95,13 +95,13 @@ def setUpData(self, data): if data.pyInstallPlatform == "linux": _platform = distro.linux_distribution()[0].lower() - print("##") - print(_platform) - print("##") + print(f"{__name__}: ##") + print(f"{__name__}: {_platform}") + print(f"{__name__}: ##") if _platform.find("raspbian") != -1: data.pyInstallPlatform = "rpi" - print("----") - print(data.pyInstallPlatform) + print(f"{__name__}: ----") + print(f"{__name__}: {data.pyInstallPlatform}") if getattr(sys, "frozen", False): if hasattr(sys, "_MEIPASS"): @@ -112,8 +112,8 @@ def setUpData(self, data): else: data.pyInstallType = "singledirectory" - print(data.pyInstallType) - print("----") + print(f"{__name__}: {data.pyInstallType}") + print(f"{__name__}: ----") self.serialPort.setUpData(data) self.gcodeFile.setUpData(data) diff --git a/Connection/serialPortThread.py b/Connection/serialPortThread.py index b1fed10d..2156030d 100644 --- a/Connection/serialPortThread.py +++ b/Connection/serialPortThread.py @@ -47,7 +47,7 @@ def _write(self, message, isQuickCommand=False): message = message + "\n" if len(message) > 1: - self.data.console_queue.put("Sending: " + str(message).rstrip('\n')) + self.data.console_queue.put(f"{__name__}: Sending: " + str(message).rstrip('\n')) self.bufferSpace = self.bufferSpace - len( message @@ -100,12 +100,12 @@ def _write(self, message, isQuickCommand=False): #print("Set positioning mode: " + str(positioningMode)) self.data.logger.writeToLog("Sent: " + str(message.decode())) except: - self.data.console_queue.put("write issue") + self.data.console_queue.put(f"{__name__}: write issue") self.data.logger.writeToLog("Send FAILED: " + str(message.decode())) self.lastWriteTime = time.time() else: - self.data.console_queue.put("Skipping: " + str(message).rstrip('\n')) + self.data.console_queue.put(f"{__name__}: Skipping: " + str(message).rstrip('\n')) def _getFirmwareVersion(self): ''' @@ -169,7 +169,7 @@ def sendNextLine(self): else: self.data.uploadFlag = 0 self.data.gcodeIndex = 0 - self.data.console_queue.put("Gcode Ended") + self.data.console_queue.put(f"{__name__}: Gcode Ended") def managePause(self, line): if line.find("M0 ") != -1 or line.find("M00") != -1 or line.find("M1 ") != -1 or line.find("M01") != -1: @@ -186,8 +186,8 @@ def manageToolChange(self, line): # if this is a the same tool as the controller is currently tracking, it will continue on. # first, determine the tool being called for... toolNumber = int(self.extractGcodeValue(line, 'T', 0)) - print(toolNumber) - print(self.data.currentTool) + self.data.console_queue.put(f"{__name__}: {toolNumber}") + self.data.console_queue.put(f"{__name__}: {self.data.currentTool}") # so, in the first case... if toolNumber != self.data.currentTool: # set uploadFlag to -1 to turn off sending more lines (after this one) @@ -219,9 +219,9 @@ def closeConnection(self): if self.serialInstance is not None: self.serialInstance.close() self.data.serialPort.serialPortRequest = "Closed" - print("connection closed at serialPortThread") + self.data.console_queue.put(f"{__name__}: connection closed at serialPortThread") else: - print("serial Instance is none??") + self.data.console_queue.put(f"{__name__}: serial Instance is none??") return @@ -235,16 +235,14 @@ def getmessage(self): # check for serial version being > 3 if float(serial.VERSION[0]) < 3: self.data.ui_queue1.put("Alert", "Incompability Detected", - "Pyserial version 3.x is needed, but version " - + serial.VERSION - + " is installed" + f"Pyserial version 3.x is needed, but version {serial.VERSION} is installed" ) self.weAreBufferingLines = bool(int(self.data.config.getValue("Maslow Settings", "bufferOn")) ) try: self.data.comport = self.data.config.getValue("Maslow Settings", "COMport") - connectMessage = "Trying to connect to controller on " +self.data.comport + connectMessage = f"{__name__}: Trying to connect to controller on COM port {self.data.comport}" self.data.console_queue.put(connectMessage) self.serialInstance = serial.Serial( self.data.comport, 57600, timeout=0.25 @@ -253,7 +251,7 @@ def getmessage(self): #self.data.console_queue.put(self.data.comport + " is unavailable or in use") pass else: - self.data.console_queue.put("\r\nConnected on port " + self.data.comport + "\r\n") + self.data.console_queue.put(f"\r\n{__name__}: Connected on port {self.data.comport}\r\n") self.data.ui_queue1.put("Action", "connectionStatus", {'status': 'connected', 'port': self.data.comport, 'fakeServoStatus': self.data.fakeServoStatus}) self.data.ui_queue1.put("TextMessage", "", "Connected on port " + self.data.comport) @@ -281,7 +279,7 @@ def getmessage(self): # check to see if the serail port needs to be closed (for firmware upgrade) if self.data.serialPort.serialPortRequest == "requestToClose": - print("processing request to close") + self.data.console_queue.put(f"{__name__}: processing request to close") self.closeConnection() # do not change status just yet... return @@ -354,7 +352,7 @@ def getmessage(self): if self.bufferSpace > len(line): self.sendNextLine() except IndexError: - self.data.console_queue.put("index error when reading gcode") + self.data.console_queue.put(f"{__name__}: index error when reading gcode") # we don't want the whole serial thread to close if the gcode can't be sent because of an # index error (file deleted...etc) else: @@ -365,7 +363,7 @@ def getmessage(self): # Check for serial connection loss # ------------------------------------------------------------------------------------- if time.time() - self.lastMessageTime > 5: - self.data.console_queue.put("Connection Timed Out") + self.data.console_queue.put(f"{__name__}: Connection Timed Out") if self.data.uploadFlag > 0: self.data.ui_queue1.put("Alert", "Connection Lost", "Message: USB connection lost. This has likely caused the machine to loose it's calibration, which can cause erratic behavior. It is recommended to stop the program, remove the sled, and perform the chain calibration process. Press Continue to override and proceed with the cut.") diff --git a/DataStructures/logger.py b/DataStructures/logger.py index 36d2b20c..96c80530 100644 --- a/DataStructures/logger.py +++ b/DataStructures/logger.py @@ -30,13 +30,13 @@ class Logger(MakesmithInitFuncs): loggingTimeout = -1 def __init__(self): - print("Initializing Logger") + print(f"{__name__}: Initializing") self.home = str(Path.home()) # clear the old log file if not os.path.isdir(self.home + "/.WebControl"): - print("creating " + self.home + "/.WebControl directory") + print(f"{__name__}: creating {self.home} /.WebControl directory") os.mkdir(self.home + "/.WebControl") - print(self.home+"/.WebControl/"+"log.txt") + print(f"{__name__}: {self.home}/.WebControl/log.txt") with open(self.home+"/.WebControl/"+"log.txt", "a") as logFile: logFile.truncate() diff --git a/File/gcodeFile.py b/File/gcodeFile.py index bfecc299..5810d96f 100644 --- a/File/gcodeFile.py +++ b/File/gcodeFile.py @@ -52,21 +52,21 @@ def serializeGCode3D(self): return sendStr def saveFile(self, fileName, directory): - if fileName is "": # Blank the g-code if we're loading "nothing" + if fileName == "": # Blank the g-code if we're loading "nothing" return False - if directory is "": + if directory == "": return False try: homeX = float(self.data.config.getValue("Advanced Settings", "homeX")) homeY = float(self.data.config.getValue("Advanced Settings", "homeY")) fileToWrite = directory + "/" + fileName gfile = open(fileToWrite, "w+") - print(fileToWrite) + print(f"{__name__}: writing file {fileToWrite}") for line in self.data.gcode: gfile.write(line+'\n') #gfile = open(directory+fileName, "w+") #gfile.writelines(map(lambda s: s+ '\n', self.data.gcode)) - print("Closing File") + print(f"{__name__}:Closing File") gfile.close() except Exception as e: self.data.console_queue.put(str(e)) @@ -76,17 +76,10 @@ def saveFile(self, fileName, directory): return False return True - - def loadUpdateFile(self, gcode=""): - print("At loadUpdateFile") - gcodeLoad = False - if gcode=="": - gcodeLoad = True - if self.data.units == "MM": - self.canvasScaleFactor = self.MILLIMETERS - else: - self.canvasScaleFactor = self.INCHES + print(f"{__name__}:loadUpdateFile called") + + self.canvasScaleFactor = self.MILLIMETERS if self.data.units == "MM" else self.INCHES if gcode == "": filename = self.data.gcodeFile.filename @@ -94,7 +87,7 @@ def loadUpdateFile(self, gcode=""): self.data.gcodeShift[1] = round(float(self.data.config.getValue("Advanced Settings", "homeY")),4) del self.line3D[:] - if filename is "": # Blank the g-code if we're loading "nothing" + if filename == "": # Blank the g-code if we're loading "nothing" self.data.gcode = "" return False @@ -106,6 +99,7 @@ def loadUpdateFile(self, gcode=""): self.data.ui_queue1.put("Alert", "Alert", "Cannot open gcode file.") self.data.gcodeFile.filename = "" return False + rawfilters = filterfile.read() filterfile.close() # closes the filter save file else: @@ -139,9 +133,7 @@ def loadUpdateFile(self, gcode=""): "\n", filtersparsed ) # splits the gcode into elements to be added to the list filtersparsed = [x.lstrip() for x in filtersparsed] # remove leading spaces - filtersparsed = [ - x + " " for x in filtersparsed - ] # adds a space to the end of each line + filtersparsed = [f"{x} " for x in filtersparsed] # adds a space to the end of each line filtersparsed = [x.replace("X ", "X") for x in filtersparsed] filtersparsed = [x.replace("Y ", "Y") for x in filtersparsed] filtersparsed = [x.replace("Z ", "Z") for x in filtersparsed] @@ -151,8 +143,6 @@ def loadUpdateFile(self, gcode=""): self.data.gcode = "[]" self.data.gcode = filtersparsed - - # Find gcode indicies of z moves self.data.zMoves = [0] zList = [] @@ -182,6 +172,7 @@ def loadUpdateFile(self, gcode=""): self.data.ui_queue1.put("Alert", "Alert", "Cannot open gcode file.") self.data.gcodeFile.filename = "" return False + self.updateGcode() self.data.gcodeFile.isChanged = True self.data.actions.sendGCodePositionUpdate(self.data.gcodeIndex, recalculate=True) @@ -210,19 +201,14 @@ def addPoint3D(self, x, y, z): def isNotReallyClose(self, x0, x1): - if abs(x0 - x1) > 0.0001: - return True - else: - return False + return abs(x0 - x1) > 0.0001 def drawLine(self, gCodeLine, command): """ - drawLine draws a line using the previous command as the start point and the xy coordinates from the current command as the end point. The line is styled based on the command to allow visually differentiating between normal and rapid moves. If the z-axis depth is changed a circle is placed at the location of the depth change to alert the user. - """ if True: @@ -241,17 +227,16 @@ def drawLine(self, gCodeLine, command): yTarget = float(y.groups()[0]) * self.canvasScaleFactor if self.absoluteFlag == 1: yTarget = self.yPosition + yTarget + z = re.search("Z(?=.)(([ ]*)?[+-]?([0-9]*)(\.([0-9]+))?)", gCodeLine) if z: zTarget = float(z.groups()[0]) * self.canvasScaleFactor - if self.isNotReallyClose(self.xPosition, xTarget) or self.isNotReallyClose( - self.yPosition, yTarget - ): - + notCloseX = self.isNotReallyClose(self.xPosition, xTarget) + notCloseY = self.isNotReallyClose(self.yPosition, yTarget) + if notCloseX or notCloseY: if command == "G00": # draw a dashed line - self.line3D.append(Line()) # points = (), width = 1, group = 'gcode') self.line3D[-1].type = "line" self.line3D[-1].command = None @@ -267,7 +252,6 @@ def drawLine(self, gCodeLine, command): self.addPoint3D(self.xPosition, self.yPosition, self.zPosition) self.line3D[-1].dashed = False - self.addPoint3D(xTarget, yTarget, zTarget) # If the zposition has changed, add indicators @@ -294,9 +278,7 @@ def drawLine(self, gCodeLine, command): def drawMCommand(self, mString): """ - drawToolChange draws a circle indicating a tool change. - """ #print(mString) code = self.getMCommand(mString) @@ -381,11 +363,9 @@ def getMCommand(self, mString): def drawArc(self, gCodeLine, command): """ - drawArc draws an arc using the previous command as the start point, the xy coordinates from the current command as the end point, and the ij coordinates from the current command as the circle center. Clockwise or counter-clockwise travel is based on the command. - """ if True: @@ -466,31 +446,24 @@ def drawArc(self, gCodeLine, command): def clearGcode(self): """ - clearGcode deletes the lines and arcs corresponding to gcode commands from the canvas. - """ + print(f"{__name__}:clearGcode") del self.line3D[:] def clearGcodeFile(self): """ - clearGcodeFile deletes the lines and arcs and the file - """ - del self.line3D[:] + self.clearGcode() self.data.gcode = [] self.updateGcode() self.data.gcodeFile.isChanged = True - - - def moveLine(self, gCodeLine): - originalLine = gCodeLine shiftX = self.data.gcodeShift[0] shiftY = self.data.gcodeShift[1] @@ -571,9 +544,7 @@ def moveLine(self, gCodeLine): def loadNextLine(self): """ - Load the next line of gcode - """ try: @@ -598,9 +569,7 @@ def loadNextLine(self): def updateOneLine(self, fullString): """ - Draw the next line on the gcode canvas - """ validPrefixList = [ @@ -615,22 +584,22 @@ def updateOneLine(self, fullString): "G17", ] - fullString = ( - fullString.upper() + " " - ) # ensures that there is a space at the end of the line - # find 'G' anywhere in string + # ensure there is a space at the end of the line, and that everything is UPPERCASE + fullString = f"{fullString.upper()} " + # find 'G' anywhere in string gString = fullString[fullString.find("G") : fullString.find("G") + 3] if gString in validPrefixList: self.prependString = gString - if ( - fullString.find("G") == -1 - ): # this adds the gcode operator if it is omitted by the program - fullString = self.prependString + " " + fullString + if fullString.find("G") == -1: + # this adds the gcode operator if it is omitted by the program + fullString = f"{self.prependString} {fullString}" gString = self.prependString + # self.data.console_queue.put(f"{__name__}:updateOneLine {fullString}") + # print gString if gString == "G00" or gString == "G0 ": self.drawLine(fullString, "G00") @@ -695,8 +664,8 @@ def callBackMechanism(self): f.write(tstr.encode()) self.data.compressedGCode3D = out.getvalue() - self.data.console_queue.put("uncompressed:"+str(len(tstr))) - self.data.console_queue.put("compressed3D:"+str(len(self.data.compressedGCode3D))) + self.data.console_queue.put(f"{__name__}:callBackMechanism uncompressed:{len(tstr)}") + self.data.console_queue.put(f"{__name__}:callBackMechanism compressed3D:{len(self.data.compressedGCode3D)}") def updateGcode(self): @@ -707,9 +676,9 @@ def updateGcode(self): # reset variables self.data.backgroundRedraw = False if (self.data.units=="INCHES"): - scaleFactor = 1.0; + scaleFactor = 1.0 else: - scaleFactor = 1/25.4; + scaleFactor = 1/25.4 #before, gcode shift = home X #old #self.xPosition = self.data.gcodeShift[0] * scaleFactor #old #self.yPosition = self.data.gcodeShift[1] * scaleFactor diff --git a/README.md b/README.md index 7de6d65d..1f3e5dee 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ Open your web browser to `localhost:5000` (or use the IP address of your device) * [Flask](http://flask.pocoo.org/) - The web framework used * [Flask-Socketio](https://github.com/miguelgrinberg/Flask-SocketIO) - Websocket integration for communications with browser clients -* [Bootstrap4](https://getbootstrap.com/) - Front-end component library +* [Bootstrap5](https://getbootstrap.com/) - Front-end component library * [Jinja2](http://jinja.pocoo.org/) - Template engine for web page generation * [Feather.js](https://feathericons.com/) - Only icon library I could find that had diagonal arrows.. works well to boot. * [OpenCV](https://github.com/skvark/opencv-python) - Library for computer vision to implement optical calibration @@ -148,42 +148,72 @@ To manage multiple python versions and virtual environments get `pyenv` (or [`py Here's a well-written [walkthrough](https://community.cisco.com/t5/developer-general-knowledge-base/pyenv-for-managing-python-versions-and-environments/ta-p/4696819) of setting up your system (macOS, Ubuntu/Debian, Fedora/CentOS/RHEL, Windows) for `pyenv` and then using `pyenv` to set up your virtual environment. Once you've prepared your system and installed `pyenv` -- get the latest version of Python that we know works with WebControl, currently that's `3.9.13`: - `pyenv install 3.9.13` +- get the latest version of Python that we know works with WebControl, currently that's `3.10.10` (Windows) or `3.10.5` (everything else): + `pyenv install 3.10.10` or `pyenv install 3.10.5` +- you can also list what versions are available with: + `pyenv install -l` + Note that many of the Python libraries used by WebControl may not have been recompiled to the latest version of Python, meaning you either have to have all the other tools (usually C language compilers for you system) to build these libraries youself, or choose a slightly older version of Python (which is what we're doing here). + +Then for regular `pyenv` (i.e. except `pyenv-win`) - create a virtual environment with it: - `pyenv virtualenv 3.9.13 webcontrol_3_9` - The `webcontrol_3_9` name is arbitrary + `pyenv virtualenv 3.10.5 webcontrol_3_10` + The `webcontrol_3_5` name is arbitrary - activate the virtual environment - this will help isolate what you do from the rest of your system: - `pyenv activate webcontrol_3_9` + `pyenv activate webcontrol_3_10` + +For Windows and `pyenv-win` it's a bit simpler +- use that version in your shell (assuming powershell here) + `pyenv shell 3.10.10` +- Done #### Prepare the Virtual Environment itself -This next stuff should only need to be done once in your virtual environment. +This next stuff should only need to be done once in your virtual environment. If you change Python versions you will need to do this again. - install some useful tools `pip install setuptools` `pip install pip-tools` `pip install black` - rebuild the list of requirements needed by WebControl (optional) + You should do this step if you're using a different version of Python from 3.10.5/3.10.10 shown above `rm requirements.txt` `pip-compile -r requirements.in --resolver=backtracking --verbose` - install the requirements `pip install -r requirements.txt` -And that's the virtual environment creation and set up done. From now on you'll only need to activate the virtual environment after any restart to get going. +And that's the virtual environment creation and set up done. From now on you'll only need to activate the virtual environment (for Windows use the `pyenv shell` command) after any restart to get going. #### Virtualenv on a Raspberry Pi When running on the Pi, you'll also need some extra dependencies and will need to build OpenCV from source. See the Dockerfile for details. (TODO: add instructions here) +### Get the Client Side Libraries Set Up + +We're using `npm` (Node Package Manager) to manage the third party JavaScript libraries in use for the client side code (the stuff that actually runs in your browser), `npm` comes with `node`. + +Note that we're not using `node` as the web server, we're only using it for `npm` to manage the JavaScript libraries. + +To manage `node` it is better to use a node version manager, such as [nvm](https://github.com/nvm-sh/nvm) (or [nvm-windows](https://github.com/coreybutler/nvm-windows) for Windows). + +Once `nvm` is installed. Run `nvm install lts` to install the latest 'long term support' version of `node`. Which will get you both `node` and `npm`. There are other `nvm` commands, such as `nvm current` to show which version of `node` you're currently using, and `nvm use lts` to switch `nvm` to using the latest `lts` version of `node` if it was on another version. + +Then finally go to the `static` folder in the WebControl project, which is where all the Javascript lives, and run `npm install` to get it to download all of the required JavaScript packages. It will create a folder called `node_modules` and put them all within that. + ### Now What? Let's Start Up WebControl🎉 Then you can run the code with. - python main.py +`python main.py` + +It will start up the server, find the IP address that the server is running at (this is provided by your router), and then start up a browser at that IP address using port `5000` as the complete URK. So in the terminal window you should see something like + +``` +Main: setting app data host address to 192.168.80.67:5000 +Main: opening browser on http://192.168.80.67:5000 +``` -The server will then be available at `http://localhost:5000` +If you close the browser window/tab that you're running WebControl on, you can always use that URL to access it again. -* If you get an error message with something like `ModuleNotFoundError: No module named '_ctypes'` within it. Then it means that you didn't get your system properly prepared before creating your virtual environment (looking at you Ubuntu). Please follow the walkthrough linked above to: +* If you get an error message in the Terminal window with something like `ModuleNotFoundError: No module named '_ctypes'` within it. Then it means that you didn't get your system properly prepared before creating your virtual environment (looking at you Ubuntu). Please follow the walkthrough linked above to: 1. deactivate your virtual environment, 2. delete it, 3. prepare your system, diff --git a/ReleaseManager/releaseManager.py b/ReleaseManager/releaseManager.py index 5844a75e..2094eccc 100644 --- a/ReleaseManager/releaseManager.py +++ b/ReleaseManager/releaseManager.py @@ -35,7 +35,7 @@ def getLatestRelease(self): def checkForLatestPyRelease(self): if True: # self.data.platform=="PYINSTALLER": - print("Checking latest pyrelease.") + print(f"{__name__}: Checking latest pyrelease.") try: enableExperimental = self.data.config.getValue( "WebControl Settings", "experimentalReleases" @@ -63,20 +63,20 @@ def checkForLatestPyRelease(self): latestVersionGithub = tag_float self.latestRelease = release - print(f"Latest pyrelease: {latestVersionGithub}") + print(f"{__name__}: Latest pyrelease: {latestVersionGithub}") if ( self.latestRelease is not None and latestVersionGithub > self.data.pyInstallCurrentVersion ): - print(f"Latest release tag: {self.latestRelease.tag_name}") + print(f"{__name__}: Latest release tag: {self.latestRelease.tag_name}") assets = self.latestRelease.get_assets() for asset in assets: if ( asset.name.find(self.data.pyInstallType) != -1 and asset.name.find(self.data.pyInstallPlatform) != -1 ): - print(asset.name) - print(asset.url) + print(f"{__name__}: {asset.name}") + print(f"{__name__}: {asset.url}") self.data.ui_queue1.put("Action", "pyinstallUpdate", "on") self.data.pyInstallUpdateAvailable = True self.data.pyInstallUpdateBrowserUrl = ( @@ -84,7 +84,7 @@ def checkForLatestPyRelease(self): ) self.data.pyInstallUpdateVersion = self.latestRelease except Exception as e: - print(f"Error checking pyrelease: {e}") + print(f"{__name__}: Error checking pyrelease: {e}") def isExperimental(self, tag): """ @@ -103,21 +103,21 @@ def isExperimental(self, tag): def processAbsolutePath(self, path): index = path.find("main.py") self.data.pyInstallInstalledPath = path[0 : index - 1] - print(self.data.pyInstallInstalledPath) + print(f"{__name__}: {self.data.pyInstallInstalledPath}") def updatePyInstaller(self, bypassCheck=False): home = self.data.config.getHome() if self.data.pyInstallUpdateAvailable == True or bypassCheck: if not os.path.exists(home + "/.WebControl/downloads"): - print("creating downloads directory") + print(f"{__name__}: creating downloads directory") os.mkdir(home + "/.WebControl/downloads") fileList = glob.glob(home + "/.WebControl/downloads/*.gz") for filePath in fileList: try: os.remove(filePath) except: - print("error cleaning download directory: ", filePath) - print("---") + print(f"{__name__}: error cleaning download directory: {filePath}") + print(f"{__name__}: ---") req = requests.get(self.data.pyInstallUpdateBrowserUrl) if ( self.data.pyInstallPlatform == "win32" @@ -127,7 +127,7 @@ def updatePyInstaller(self, bypassCheck=False): else: filename = home + "/.WebControl/downloads" open(filename, "wb").write(req.content) - print(filename) + print(f"{__name__}: {filename}") if self.data.platform == "PYINSTALLER": lhome = os.path.join(self.data.platformHome) @@ -161,20 +161,20 @@ def updatePyInstaller(self, bypassCheck=False): arguments = [filename, self.data.pyInstallInstalledPath, tool_path] command = [program_name] command.extend(arguments) - print("popening") - print(command) + print(f"{__name__}: popening") + print(f"{__name__}: {command}") subprocess.Popen(command) return True return False def make_executable(self, path): - print("1") + print(f"{__name__}: 1") mode = os.stat(path).st_mode - print("2") + print(f"{__name__}: 2") mode |= (mode & 0o444) >> 2 # copy R bits to X - print("3") + print(f"{__name__}: 3") os.chmod(path, mode) - print("4") + print(f"{__name__}: 4") def update(self, version): """ @@ -190,10 +190,10 @@ def update(self, version): asset.name.find(self.data.pyInstallType) != -1 and asset.name.find(self.data.pyInstallPlatform) != -1 ): - print(asset.name) - print(asset.url) + print(f"{__name__}: {asset.name}") + print(f"{__name__}: {asset.url}") self.data.pyInstallUpdateBrowserUrl = asset.browser_download_url - print(self.data.pyInstallUpdateBrowserUrl) + print(f"{__name__}: {self.data.pyInstallUpdateBrowserUrl}") return self.updatePyInstaller(True) - print("hmmm.. issue") + print(f"{__name__}: hmmm.. issue") return False diff --git a/WebPageProcessor/webPageProcessor.py b/WebPageProcessor/webPageProcessor.py index 200cb86d..b5773cc2 100644 --- a/WebPageProcessor/webPageProcessor.py +++ b/WebPageProcessor/webPageProcessor.py @@ -2,6 +2,7 @@ import re import os +# import os from os.path from flask import render_template import frontmatter @@ -11,190 +12,138 @@ class WebPageProcessor: data = None + namespace = "/MaslowCNC" def __init__(self, data): self.data = data def createWebPage(self, pageID, isMobile, args): # returns a page and a bool specifying whether the user has to click close to exit modal + settingsTemplate = "settings_mobile.html" if isMobile else "settings.html" + + enableCustom = self.data.controllerFirmwareVersion >= 100 + fourMotor = self.data.controllerFirmwareVersion >= 100 + + home = self.data.config.getHome() + gcodeDir = f"{home}/.WebControl/gcode" + boardsDir = f"{home}/.WebControl/boards" + if pageID == "maslowSettings": setValues = self.data.config.getJSONSettingSection("Maslow Settings") # because this page allows you to select the comport from a list that is not stored in webcontrol.json, we need to package and send the list of comports # Todo:? change it to store the list? ports = self.data.comPorts - if self.data.controllerFirmwareVersion < 100: - enableCustom = False - else: - enableCustom = True - if isMobile: - page = render_template( - "settings_mobile.html", - title="Maslow Settings", - settings=setValues, - ports=ports, - pageID="maslowSettings", - enableCustom=enableCustom, - ) - else: - page = render_template( - "settings.html", - title="Maslow Settings", - settings=setValues, - ports=ports, - pageID="maslowSettings", - enableCustom=enableCustom, - ) + page = render_template( + settingsTemplate, + title="Maslow Settings", + settings=setValues, + ports=ports, + pageID="maslowSettings", + enableCustom=enableCustom, + ) return page, "Maslow Settings", False, "medium", "content", "footerSubmit" elif pageID == "advancedSettings": setValues = self.data.config.getJSONSettingSection("Advanced Settings") - if self.data.controllerFirmwareVersion < 100: - enableCustom = False - else: - enableCustom = True - if isMobile: - page = render_template( - "settings_mobile.html", - title="Advanced Settings", - settings=setValues, - pageID="advancedSettings", - enableCustom=enableCustom, - ) - else: - page = render_template( - "settings.html", - title="Advanced Settings", - settings=setValues, - pageID="advancedSettings", - enableCustom=enableCustom, - ) + page = render_template( + settingsTemplate, + title="Advanced Settings", + settings=setValues, + pageID="advancedSettings", + enableCustom=enableCustom, + ) return page, "Advanced Settings", False, "medium", "content", "footerSubmit" elif pageID == "webControlSettings": setValues = self.data.config.getJSONSettingSection("WebControl Settings") - if self.data.controllerFirmwareVersion < 100: - enableCustom = False - else: - enableCustom = True - if isMobile: - page = render_template( - "settings_mobile.html", - title="WebControl Settings", - settings=setValues, - pageID="webControlSettings", - enableCustom=enableCustom, - ) - else: - page = render_template( - "settings.html", - title="WebControl Settings", - settings=setValues, - pageID="webControlSettings", - enableCustom=enableCustom, - ) + page = render_template( + settingsTemplate, + title="WebControl Settings", + settings=setValues, + pageID="webControlSettings", + enableCustom=enableCustom, + ) return page, "WebControl Settings", False, "medium", "content", "footerSubmit" elif pageID == "cameraSettings": setValues = self.data.config.getJSONSettingSection("Camera Settings") - if self.data.controllerFirmwareVersion < 100: - enableCustom = False - else: - enableCustom = True - if isMobile: - page = render_template( - "settings_mobile.html", - title="Camera Settings", - settings=setValues, - pageID="cameraSettings", - enableCustom=enableCustom, - ) - else: - page = render_template( - "settings.html", - title="Camera Settings", - settings=setValues, - pageID="cameraSettings", - enableCustom=enableCustom, - ) + page = render_template( + settingsTemplate, + title="Camera Settings", + settings=setValues, + pageID="cameraSettings", + enableCustom=enableCustom, + ) return page, "Camera Settings", False, "medium", "content", "footerSubmit" elif pageID == "gpioSettings": setValues = self.data.config.getJSONSettingSection("GPIO Settings") - if self.data.controllerFirmwareVersion < 100: - enableCustom = False - else: - enableCustom = True + gpioTemplate = "gpio_mobile.html" if isMobile else "gpio.html" options = self.data.gpioActions.getActionList() - if isMobile: - page = render_template( - "gpio_mobile.html", - title="GPIO Settings", - settings=setValues, - options=options, - pageID="gpioSettings", - enableCustom=enableCustom, - ) - else: - page = render_template( - "gpio.html", - title="GPIO Settings", - settings=setValues, - options=options, - pageID="gpioSettings", - enableCustom=enableCustom, - ) + page = render_template( + gpioTemplate, + title="GPIO Settings", + settings=setValues, + options=options, + pageID="gpioSettings", + enableCustom=enableCustom, + ) return page, "GPIO Settings", False, "medium", "content", "footerSubmit" elif pageID == "openGCode": lastSelectedFile = self.data.config.getValue("Maslow Settings", "openFile") - print(lastSelectedFile) lastSelectedDirectory = self.data.config.getValue("Computed Settings", "lastSelectedDirectory") - home = self.data.config.getHome() - homedir = home+"/.WebControl/gcode" directories = [] files = [] try: - for _root, _dirs, _files in os.walk(homedir): + for _root, _dirs, _files in os.walk(gcodeDir): if _dirs: directories = _dirs for file in _files: - if _root != homedir: + if _root != gcodeDir: _dir = _root.split("\\")[-1].split("/")[-1] else: _dir = "." files.append({"directory":_dir, "file":file}) except Exception as e: print(e) - # files = [f for f in listdir(homedir) if isfile(join(homedir, f))] + # files = [f for f in listdir(gcodeDir) if isfile(join(gcodeDir, f))] directories.insert(0, "./") if lastSelectedDirectory is None: lastSelectedDirectory="." page = render_template( - "openGCode.html", directories=directories, files=files, lastSelectedFile=lastSelectedFile, lastSelectedDirectory=lastSelectedDirectory, isOpen=True + "openGCode.html", + directories=directories, + files=files, + lastSelectedFile=lastSelectedFile, + lastSelectedDirectory=lastSelectedDirectory, + isOpen=True, ) return page, "Open GCode", False, "medium", "content", "footerSubmit" elif pageID == "saveGCode": lastSelectedFile = self.data.config.getValue("Maslow Settings", "openFile") print(lastSelectedFile) lastSelectedDirectory = self.data.config.getValue("Computed Settings", "lastSelectedDirectory") - home = self.data.config.getHome() - homedir = home + "/.WebControl/gcode" directories = [] files = [] try: - for _root, _dirs, _files in os.walk(homedir): + for _root, _dirs, _files in os.walk(gcodeDir): if _dirs: directories = _dirs for file in _files: - if _root != homedir: + if _root != gcodeDir: _dir = _root.split("\\")[-1].split("/")[-1] else: _dir = "." files.append({"directory": _dir, "file": file}) except Exception as e: print(e) - # files = [f for f in listdir(homedir) if isfile(join(homedir, f))] + # files = [f for f in listdir(gcodeDir) if isfile(join(gcodeDir, f))] directories.insert(0, "./") if lastSelectedDirectory is None: lastSelectedDirectory = "." page = render_template( - "saveGCode.html", directories=directories, files=files, lastSelectedFile=lastSelectedFile, - lastSelectedDirectory=lastSelectedDirectory, isOpen=False + "saveGCode.html", + directories=directories, + files=files, + lastSelectedFile=lastSelectedFile, + lastSelectedDirectory=lastSelectedDirectory, + isOpen=False, ) return page, "Save GCode", False, "medium", "content", "footerSubmit" @@ -203,11 +152,9 @@ def createWebPage(self, pageID, isMobile, args): "WebControl Settings", "validExtensions" ) lastSelectedDirectory = self.data.config.getValue("Computed Settings", "lastSelectedDirectory") - home = self.data.config.getHome() - homedir = home + "/.WebControl/gcode" directories = [] try: - for _root, _dirs, _files in os.walk(homedir): + for _root, _dirs, _files in os.walk(gcodeDir): if _dirs: directories = _dirs except Exception as e: @@ -215,7 +162,12 @@ def createWebPage(self, pageID, isMobile, args): directories.insert(0, "./") if lastSelectedDirectory is None: lastSelectedDirectory = "." - page = render_template("uploadGCode.html", validExtensions=validExtensions, directories=directories, lastSelectedDirectory=lastSelectedDirectory) + page = render_template( + "uploadGCode.html", + validExtensions=validExtensions, + directories=directories, + lastSelectedDirectory=lastSelectedDirectory, + ) return page, "Upload GCode", False, "medium", "content", "footerSubmit" elif pageID == "importGCini": url = "importFile" @@ -230,10 +182,6 @@ def createWebPage(self, pageID, isMobile, args): page = render_template("importFile.html", url=url) return page, "Restore WebControl", False, "medium", "content", False elif pageID == "actions": - if self.data.controllerFirmwareVersion < 100: - enableCustom = False - else: - enableCustom = True if self.data.controllerFirmwareVersion < 50: enableHoley = False else: @@ -252,90 +200,65 @@ def createWebPage(self, pageID, isMobile, args): else: updateAvailable = False updateRelease = "N/A" - if (float(self.data.controllerFirmwareVersion) > 1.26): - firmwareSupportsZaxisLimit = True - else: - firmwareSupportsZaxisLimit = False + firmwareSupportsZaxisLimit = float(self.data.controllerFirmwareVersion) > 1.26 print("action render-> firmware version is :", self.data.controllerFirmwareVersion, " so: ", firmwareSupportsZaxisLimit) - page = render_template("actions.html", - updateAvailable=updateAvailable, - updateRelease=updateRelease, - docker=docker, - customFirmwareVersion=self.data.customFirmwareVersion, - stockFirmwareVersion=self.data.stockFirmwareVersion, - holeyFirmwareVersion=self.data.holeyFirmwareVersion, - enableCustom=enableCustom, - enableHoley=enableHoley, - enableRPIshutdown = enableRPIshutdown, - firmwareSupportsZaxisLimit = firmwareSupportsZaxisLimit - ) + page = render_template( + "actions.html", + updateAvailable=updateAvailable, + updateRelease=updateRelease, + docker=docker, + customFirmwareVersion=self.data.customFirmwareVersion, + stockFirmwareVersion=self.data.stockFirmwareVersion, + holeyFirmwareVersion=self.data.holeyFirmwareVersion, + enableCustom=enableCustom, + enableHoley=enableHoley, + enableRPIshutdown = enableRPIshutdown, + firmwareSupportsZaxisLimit = firmwareSupportsZaxisLimit, + ) return page, "Actions", False, "large", "content", False elif pageID == "zAxis": - socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace="/MaslowCNC") + socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace=self.namespace) distToMoveZ = self.data.config.getValue("Computed Settings", "distToMoveZ") unitsZ = self.data.config.getValue("Computed Settings", "unitsZ") touchPlate = self.data.config.getValue("Advanced Settings", "touchPlate") - if isMobile: - page = render_template("zaxis_mobile.html", - distToMoveZ=distToMoveZ, - unitsZ=unitsZ, - touchPlate=touchPlate - ) - else: - page = render_template("zaxis.html", - distToMoveZ=distToMoveZ, - unitsZ=unitsZ, - touchPlate=touchPlate - ) + zAxisTemplate = "zaxis_mobile.html" if isMobile else "zaxis.html" + page = render_template( + zAxisTemplate, + distToMoveZ=distToMoveZ, + unitsZ=unitsZ, + touchPlate=touchPlate, + ) return page, "Z-Axis", False, "medium", "content", False elif pageID == "setZaxis": - socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace="/MaslowCNC") + socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace=self.namespace) minZlimit = 0 #self.data.config.getValue("Advanced Settings", "minZlimit") maxZlimit = 0 #self.data.config.getValue("Advanced Settings", "maxZlimit") distToMoveZ = self.data.config.getValue("Computed Settings", "distToMoveZ") unitsZ = self.data.config.getValue("Computed Settings", "unitsZ") - if isMobile: - page = render_template("setZaxis_mobile.html", - minZlimit = minZlimit, - maxZlimit = maxZlimit, - distToMoveZ=distToMoveZ, - unitsZ=unitsZ - ) - else: - page = render_template("setZaxis.html", - minZlimit = minZlimit, - maxZlimit = maxZlimit, - distToMoveZ=distToMoveZ, - unitsZ=unitsZ - ) + setZAxisTemplate = "setZaxis_mobile.html" if isMobile else "setZaxis.html" + page = render_template( + setZAxisTemplate, + minZlimit = minZlimit, + maxZlimit = maxZlimit, + distToMoveZ=distToMoveZ, + unitsZ=unitsZ + ) return page, "Z-Axis Limits", False, "medium", "content", False elif pageID == "setSprockets": - if self.data.controllerFirmwareVersion < 100: - fourMotor = False - else: - fourMotor = True chainExtendLength = self.data.config.getValue("Advanced Settings", "chainExtendLength") - socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace="/MaslowCNC") - if isMobile: - page = render_template("setSprockets_mobile.html", chainExtendLength=chainExtendLength, fourMotor=fourMotor) - else: - page = render_template("setSprockets.html", chainExtendLength=chainExtendLength, fourMotor=fourMotor) + socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace=self.namespace) + setSprocketsTemplate = "setSprockets_mobile.html" if isMobile else "setSprockets.html" + page = render_template(setSprocketsTemplate, chainExtendLength=chainExtendLength, fourMotor=fourMotor) return page, "Set Sprockets", False, "medium", "content", False elif pageID == "resetChains": - if self.data.controllerFirmwareVersion < 100: - fourMotor = False - else: - fourMotor = True chainExtendLength = self.data.config.getValue("Advanced Settings", "chainExtendLength") - socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace="/MaslowCNC") - if isMobile: - page = render_template("resetChains_mobile.html", chainExtendLength=chainExtendLength, fourMotor=fourMotor) - else: - page = render_template("resetChains.html", chainExtendLength=chainExtendLength, fourMotor=fourMotor) + socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace=self.namespace) + resetChainsTemplate = "resetChains_mobile.html" if isMobile else "resetChains.html" + page = render_template(resetChainsTemplate, chainExtendLength=chainExtendLength, fourMotor=fourMotor) return page, "Reset Chains", False, "medium", "content", False elif pageID == "triangularCalibration": - socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace="/MaslowCNC") + socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace=self.namespace) motorYoffset = self.data.config.getValue("Maslow Settings", "motorOffsetY") rotationRadius = self.data.config.getValue("Advanced Settings", "rotationRadius") chainSagCorrection = self.data.config.getValue( @@ -350,7 +273,7 @@ def createWebPage(self, pageID, isMobile, args): ) return page, "Triangular Calibration", True, "medium", "content", False elif pageID == "opticalCalibration": - socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace="/MaslowCNC") + socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace=self.namespace) opticalCenterX = self.data.config.getValue("Optical Calibration Settings", "opticalCenterX") opticalCenterY = self.data.config.getValue("Optical Calibration Settings", "opticalCenterY") scaleX = self.data.config.getValue("Optical Calibration Settings", "scaleX") @@ -367,10 +290,30 @@ def createWebPage(self, pageID, isMobile, args): brY = self.data.config.getValue("Optical Calibration Settings", "brY") calibrationExtents = self.data.config.getValue("Optical Calibration Settings", "calibrationExtents") positionTolerance = self.data.config.getValue("Optical Calibration Settings", "positionTolerance") - page = render_template("opticalCalibration.html", pageID="opticalCalibration", opticalCenterX=opticalCenterX, opticalCenterY=opticalCenterY, scaleX=scaleX, scaleY=scaleY, gaussianBlurValue=gaussianBlurValue, cannyLowValue=cannyLowValue, cannyHighValue=cannyHighValue, autoScanDirection=autoScanDirection, markerX=markerX, markerY=markerY, tlX=tlX, tlY=tlY, brX=brX, brY=brY, calibrationExtents=calibrationExtents, isMobile=isMobile, positionTolerance=positionTolerance) + page = render_template( + "opticalCalibration.html", + pageID="opticalCalibration", + opticalCenterX=opticalCenterX, + opticalCenterY=opticalCenterY, + scaleX=scaleX, + scaleY=scaleY, + gaussianBlurValue=gaussianBlurValue, + cannyLowValue=cannyLowValue, + cannyHighValue=cannyHighValue, + autoScanDirection=autoScanDirection, + markerX=markerX, + markerY=markerY, + tlX=tlX, + tlY=tlY, + brX=brX, + brY=brY, + calibrationExtents=calibrationExtents, + isMobile=isMobile, + positionTolerance=positionTolerance + ) return page, "Optical Calibration", True, "large", "content", False elif pageID == "holeyCalibration": - socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace="/MaslowCNC") + socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace=self.namespace) motorYoffset = self.data.config.getValue("Maslow Settings", "motorOffsetY") distanceBetweenMotors = self.data.config.getValue("Maslow Settings", "motorSpacingX") leftChainTolerance = self.data.config.getValue("Advanced Settings", "leftChainTolerance") @@ -385,7 +328,7 @@ def createWebPage(self, pageID, isMobile, args): ) return page, "Holey Calibration", True, "medium", "content", False elif pageID == "quickConfigure": - socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace="/MaslowCNC") + socketio.emit("closeModals", {"data": {"title": "Actions"}}, namespace=self.namespace) motorOffsetY = self.data.config.getValue("Maslow Settings", "motorOffsetY") rotationRadius = self.data.config.getValue("Advanced Settings", "rotationRadius") kinematicsType = self.data.config.getValue("Advanced Settings", "kinematicsType") @@ -428,10 +371,6 @@ def createWebPage(self, pageID, isMobile, args): page = render_template("editGCode.html", gcode=text, pageID="sendGCode", ) return page, "Edit GCode", True, "medium", "content", "footerSubmit" elif pageID == "pidTuning": - if self.data.controllerFirmwareVersion < 100: - fourMotor = False - else: - fourMotor = True KpP = self.data.config.getValue("Advanced Settings", "KpPos") KiP = self.data.config.getValue("Advanced Settings", "KiPos") KdP = self.data.config.getValue("Advanced Settings", "KdPos") @@ -440,16 +379,18 @@ def createWebPage(self, pageID, isMobile, args): KdV = self.data.config.getValue("Advanced Settings", "KdV") vVersion = "1" pVersion = "1" - page = render_template("pidTuning.html", - KpP=KpP, - KiP=KiP, - KdP=KdP, - KpV=KpV, - KiV=KiV, - KdV=KdV, - vVersion=vVersion, - pVersion=pVersion, - fourMotor=fourMotor) + page = render_template( + "pidTuning.html", + KpP=KpP, + KiP=KiP, + KdP=KdP, + KpV=KpV, + KiV=KiV, + KdV=KdV, + vVersion=vVersion, + pVersion=pVersion, + fourMotor=fourMotor + ) return page, "PID Tuning", False, "large", "content", False elif pageID == "editBoard": board = self.data.boardManager.getCurrentBoard() @@ -458,22 +399,21 @@ def createWebPage(self, pageID, isMobile, args): if self.data.units == "MM": scale = 25.4 units = "mm" - if isMobile: - pageName = "editBoard_mobile.html" - else: - pageName = "editBoard.html" - page = render_template(pageName, - units=units, - boardID=board.boardID, - material=board.material, - height=round(board.height*scale, 2), - width=round(board.width*scale, 2), - thickness=round(board.thickness*scale, 2), - centerX=round(board.centerX*scale, 2), - centerY=round(board.centerY*scale, 2), - routerHorz=self.data.xval, - routerVert=self.data.yval, - pageID="editBoard") + editBoardTemplate = "editBoard_mobile.html" if isMobile else "editBoard.html" + page = render_template( + editBoardTemplate, + units=units, + boardID=board.boardID, + material=board.material, + height=round(board.height*scale, 2), + width=round(board.width*scale, 2), + thickness=round(board.thickness*scale, 2), + centerX=round(board.centerX*scale, 2), + centerY=round(board.centerY*scale, 2), + routerHorz=self.data.xval, + routerVert=self.data.yval, + pageID="editBoard", + ) return page, "Create/Edit Board", False, "medium", "content", "footerSubmit" elif pageID == "trimBoard": board = self.data.boardManager.getCurrentBoard() @@ -482,70 +422,71 @@ def createWebPage(self, pageID, isMobile, args): if self.data.units == "MM": scale = 25.4 units = "mm" - if isMobile: - pageName = "trimBoard.html" - else: - pageName = "trimBoard.html" - page = render_template(pageName, - units=units, - pageID="trimBoard") + # Yep the mobile version is the same + trimBoardTemplate = "trimBoard.html" if isMobile else "trimBoard.html" + page = render_template(trimBoardTemplate, units=units, pageID="trimBoard") return page, "Trim Board", False, "medium", "content", "footerSubmit" elif pageID == "saveBoard": #lastSelectedFile = self.data.config.getValue("Maslow Settings", "openBoardFile") #print(lastSelectedFile) lastSelectedDirectory = self.data.config.getValue("Computed Settings", "lastSelectedBoardDirectory") lastSelectedFile = self.data.boardManager.getCurrentBoardFilename() - home = self.data.config.getHome() - homedir = home + "/.WebControl/boards" directories = [] files = [] try: - for _root, _dirs, _files in os.walk(homedir): + for _root, _dirs, _files in os.walk(boardsDir): if _dirs: directories = _dirs for file in _files: - if _root != homedir: + if _root != boardsDir: _dir = _root.split("\\")[-1].split("/")[-1] else: _dir = "." files.append({"directory": _dir, "file": file}) except Exception as e: print(e) - # files = [f for f in listdir(homedir) if isfile(join(homedir, f))] + # files = [f for f in listdir(boardsDir) if isfile(join(boardsDir, f))] directories.insert(0, "./") if lastSelectedDirectory is None: lastSelectedDirectory = "." page = render_template( - "saveBoard.html", directories=directories, files=files, lastSelectedFile=lastSelectedFile, - lastSelectedDirectory=lastSelectedDirectory, isOpen=False + "saveBoard.html", + directories=directories, + files=files, + lastSelectedFile=lastSelectedFile, + lastSelectedDirectory=lastSelectedDirectory, + isOpen=False, ) return page, "Save Board", False, "medium", "content", "footerSubmit" elif pageID == "openBoard": lastSelectedFile = self.data.config.getValue("Maslow Settings", "openBoardFile") print(lastSelectedFile) lastSelectedDirectory = self.data.config.getValue("Computed Settings", "lastSelectedBoardDirectory") - home = self.data.config.getHome() - homedir = home+"/.WebControl/boards" directories = [] files = [] try: - for _root, _dirs, _files in os.walk(homedir): + for _root, _dirs, _files in os.walk(boardsDir): if _dirs: directories = _dirs for file in _files: - if _root != homedir: + if _root != boardsDir: _dir = _root.split("\\")[-1].split("/")[-1] else: _dir = "." files.append({"directory":_dir, "file":file}) except Exception as e: print(e) - # files = [f for f in listdir(homedir) if isfile(join(homedir, f))] + # files = [f for f in listdir(boardsDir) if isfile(join(boardsDir, f))] directories.insert(0, "./") if lastSelectedDirectory is None: lastSelectedDirectory="." page = render_template( - "openBoard.html", directories=directories, files=files, lastSelectedFile=lastSelectedFile, lastSelectedDirectory=lastSelectedDirectory, isOpen=True + "openBoard.html", + directories=directories, + files=files, + lastSelectedFile=lastSelectedFile, + lastSelectedDirectory=lastSelectedDirectory, + isOpen=True, ) return page, "Open Board", False, "medium", "content", "footerSubmit" elif pageID == "about": diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app.py b/app.py index 5e6bec93..5a0ef216 100644 --- a/app.py +++ b/app.py @@ -1,3 +1,5 @@ +# from pyjion.wsgi import PyjionWsgiMiddleware + from flask import Flask from flask_mobility import Mobility from flask_socketio import SocketIO @@ -18,6 +20,18 @@ template_folder=os.path.join(base_dir, "templates"), ) app.debug = True -socketio = SocketIO(app) + +# Override the app wsgi_app property +# app.wsgi_app = PyjionWsgiMiddleware(app.wsgi_app) + +# import eventlet is implied for engine.io async_mode parameter +socketio = SocketIO( + app, + cors_allowed_origins="*", + logger=True, + engineio_logger=False, + always_connect=True +) + mobility = Mobility(app) # md.init_app(app) diff --git a/config/config.py b/config/config.py index 87fa7d96..b700186e 100644 --- a/config/config.py +++ b/config/config.py @@ -25,6 +25,14 @@ class Config(MakesmithInitFuncs): home1 = "." firstRun = False + def pathBuild(self, path, title): + required_path = f"{self.home}/{path}" + if not os.path.isdir(required_path): + print(f"{__name__}: creating {title} directory at {required_path}") + os.mkdir(required_path) + else: + print(f"{__name__}: {title} directory is {required_path}") + def __init__(self): ''' This function determines if a pyinstaller version is being run and if so, @@ -34,29 +42,23 @@ def __init__(self): self.home = str(Path.home()) if hasattr(sys, '_MEIPASS'): self.home1 = os.path.join(sys._MEIPASS) - print(self.home1) + print(f"{__name__}: {self.home1}") ''' This portion creates directories that are missing and creates a new webcontrol.json file if this is the first run (copies defaultwebcontrol.json) ''' - print("Initializing Configuration") - if not os.path.isdir(self.home+"/.WebControl"): - print("creating "+self.home+"/.WebControl directory") - os.mkdir(self.home+"/.WebControl") + print(f"{__name__}: Initializing") + print(f"{__name__}: Home path is: {self.home}") + self.pathBuild(".WebControl", "WebControl") + self.pathBuild(".WebControl/gcode", "GCode") + self.pathBuild(".WebControl/imports", "Imports") + self.pathBuild(".WebControl/boards", "Boards") + if not os.path.exists(self.home+"/.WebControl/webcontrol.json"): - print("copying defaultwebcontrol.json to "+self.home+"/.WebControl/") + print(f"{__name__}: copying defaultwebcontrol.json to {self.home}/.WebControl/") copyfile(self.home1+"/defaultwebcontrol.json",self.home+"/.WebControl/webcontrol.json") self.firstRun = True - if not os.path.isdir(self.home+"/.WebControl/gcode"): - print("creating "+self.home+"/.WebControl/gcode directory") - os.mkdir(self.home+"/.WebControl/gcode") - if not os.path.isdir(self.home+"/.WebControl/imports"): - print("creating "+self.home+"/.WebControl/imports directory") - os.mkdir(self.home+"/.WebControl/imports") - if not os.path.isdir(self.home+"/.WebControl/boards"): - print("creating "+self.home+"/.WebControl/boards directory") - os.mkdir(self.home+"/.WebControl/boards") with open(self.home+"/.WebControl/webcontrol.json", "r") as infile: self.settings = json.load(infile) # load default and see if there is anything missing.. if so, add it @@ -83,11 +85,11 @@ def __init__(self): break if found == False: if sectionFound: - print("section found") + print(f"{__name__}: section found") else: - print("section not found") + print(f"{__name__}: section not found") self.settings[section]=[] - print(section+"->"+self.defaults[section][x]["key"]+" was not found..") + print(f"{__name__}: {section}->{self.defaults[section][x]['key']} was not found..") t = {} if "default" in self.defaults[section][x]: t["default"]=self.defaults[section][x]["default"] @@ -109,7 +111,7 @@ def __init__(self): t["options"]=self.defaults[section][x]["options"] self.settings[section].append(t) - print("added "+section+"->"+self.settings[section][len(self.settings[section])-1]["key"]) + print(f"{__name__}: added {section}->{self.settings[section][len(self.settings[section])-1]['key']}") updated = True ''' @@ -118,7 +120,7 @@ def __init__(self): ''' if updated: with open(self.home+"/.WebControl/webcontrol.json", "w") as outfile: - # print ("writing file") + # print(f"{__name__}: writing file") json.dump( self.settings, outfile, sort_keys=True, indent=4, ensure_ascii=False ) @@ -133,7 +135,7 @@ def __init__(self): for item in dir: if item.startswith("webcontrol"): if item.endswith("gz") or item.endswith("zip") or item.endswith("dmg"): - print("Removing file:"+item) + print(f"{__name__}: Removing file: {item}") os.remove(os.path.join(dirName, item)) def checkForTouchedPort(self): @@ -144,17 +146,17 @@ def checkForTouchedPort(self): ''' home = self.home path = home+"/.WebControl/webcontrol-*.port" - print(path) + print(f"{__name__}: {path}") try: for filename in glob.glob(path): - print(filename) + print(f"{__name__}: {filename}") port = filename.split("-") port = port[1].split(".port") - print(port[0]) + print(f"{__name__}: {port[0]}") self.data.config.setValue("WebControl Settings", "webPort", port[0]) os.remove(filename) except Exception as e: - print(e) + print(f"{__name__}: {e}") return False return True @@ -727,7 +729,7 @@ def parseFirmwareVersions(self): home = "." if hasattr(sys, '_MEIPASS'): home = os.path.join(sys._MEIPASS) - print(self.home) + print(f"{__name__}: {self.home}") path = home+"/firmware/madgrizzle/*.hex" for filename in glob.glob(path): version = filename.split("-") @@ -789,7 +791,7 @@ def firmwareKeyValue(self, value): try: return fmt.format(value) except: - print('firmwareKeyString Exception: value = ' + str(value)) + print(f"{__name__}: firmwareKeyString Exception: value = {str(value)}") return str(value) def reloadWebControlJSON(self): @@ -802,5 +804,5 @@ def reloadWebControlJSON(self): self.settings = json.load(infile) return True except: - print("error reloading WebControlJSON") + print(f"{__name__}: error reloading WebControlJSON") return False diff --git a/docs/index.md b/docs/index.md index e2f3a5d8..f4dc141e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -111,7 +111,7 @@ Open your web browser to `localhost:5000` (or use the IP address of your device) * [Flask](http://flask.pocoo.org/) - The web framework used * [Flask-Socketio](https://github.com/miguelgrinberg/Flask-SocketIO) - Websocket integration for communications with browser clients -* [Bootstrap4](https://getbootstrap.com/) - Front-end component library +* [Bootstrap5](https://getbootstrap.com/) - Front-end component library * [Jinja2](http://jinja.pocoo.org/) - Template engine for web page generation * [Feather.js](https://feathericons.com/) - Only icon library I could find that had diagonal arrows.. works well to boot. * [OpenCV](https://github.com/skvark/opencv-python) - Library for computer vision to implement optical calibration diff --git a/main.py b/main.py index da57114d..ea484f8c 100644 --- a/main.py +++ b/main.py @@ -5,63 +5,32 @@ from app import app, socketio -import webbrowser -import socket import os -import sys - -from werkzeug.utils import secure_filename +import socket +import threading +import time +import webbrowser import schedule -import time -import threading -import json - -from flask import ( - jsonify, - render_template, - current_app, - request, - send_file, - send_from_directory, -) -from flask_mobility.decorators import mobile_template + +from flask import current_app + +from App.data import init_data +from App.route import init_route +from App.socket_maslowcnc import init_socket_maslowcnc +from App.socket_maslowcnclogs import init_socket_maslowcnclogs +from App.socket_mcp import init_socket_mcp +from App.socket_catchall import init_socket_catchall from Background.UIProcessor import UIProcessor # do this after socketio is declared from Background.LogStreamer import LogStreamer # do this after socketio is declared -from DataStructures.data import Data from Connection.nonVisibleWidgets import NonVisibleWidgets from WebPageProcessor.webPageProcessor import WebPageProcessor +app = init_data(app) -app.data = Data() app.nonVisibleWidgets = NonVisibleWidgets() app.nonVisibleWidgets.setUpData(app.data) -app.data.config.computeSettings(None, None, None, True) -app.data.config.parseFirmwareVersions() -app.data.units = app.data.config.getValue("Computed Settings", "units") -app.data.tolerance = app.data.config.getValue("Computed Settings", "tolerance") -app.data.distToMove = app.data.config.getValue("Computed Settings", "distToMove") -app.data.distToMoveZ = app.data.config.getValue("Computed Settings", "distToMoveZ") -app.data.unitsZ = app.data.config.getValue("Computed Settings", "unitsZ") -app.data.comport = app.data.config.getValue("Maslow Settings", "COMport") -app.data.gcodeShift = [ - float(app.data.config.getValue("Advanced Settings", "homeX")), - float(app.data.config.getValue("Advanced Settings", "homeY")), -] - -version = sys.version_info - -if version[:2] > (3, 5): - app.data.pythonVersion35 = False - print("Using routines for Python > 3.5") -else: - app.data.pythonVersion35 = True - print("Using routines for Python == 3.5") - -app.data.firstRun = False -# app.previousPosX = 0.0 -# app.previousPosY = 0.0 app.UIProcessor = UIProcessor() app.webPageProcessor = WebPageProcessor(app.data) @@ -90,781 +59,22 @@ def run_schedule(): app.th2.start() ## uithread set to None.. will be activated upon first websocket connection from browser +# TODO: This requires handling in a completely different way - this breaks socketio.on connect handling app.uithread = None ## uithread set to None.. will be activated upon first websocket connection from webmcp +# TODO: This requires handling in a completely different way - this breaks socketio.on connect handling app.mcpthread = None ## logstreamerthread set to None.. will be activated upon first websocket connection from log streamer browser +# TODO: This requires handling in a completely different way - this breaks socketio.on connect handling app.logstreamerthread = None - -@app.route("/") -@mobile_template("{mobile/}") -def index(template): - app.data.logger.resetIdler() - macro1Title = (app.data.config.getValue("Maslow Settings", "macro1_title"))[:6] - macro2Title = (app.data.config.getValue("Maslow Settings", "macro2_title"))[:6] - if template == "mobile/": - return render_template( - "frontpage3d_mobile.html", - modalStyle="modal-lg", - macro1_title=macro1Title, - macro2_title=macro2Title, - ) - else: - return render_template( - "frontpage3d.html", - modalStyle="mw-100 w-75", - macro1_title=macro1Title, - macro2_title=macro2Title, - ) - - -@app.route("/controls") -@mobile_template("/controls/{mobile/}") -def controls(template): - app.data.logger.resetIdler() - macro1Title = (app.data.config.getValue("Maslow Settings", "macro1_title"))[:6] - macro2Title = (app.data.config.getValue("Maslow Settings", "macro2_title"))[:6] - if template == "/controls/mobile/": - return render_template( - "frontpage3d_mobilecontrols.html", - modalStyle="modal-lg", - isControls=True, - macro1_title=macro1Title, - macro2_title=macro2Title, - ) - else: - return render_template( - "frontpage3d.html", - modalStyle="mw-100 w-75", - macro1_title=macro1Title, - macro2_title=macro2Title, - ) - - -@app.route("/text") -@mobile_template("/text/{mobile/}") -def text(template): - app.data.logger.resetIdler() - macro1Title = (app.data.config.getValue("Maslow Settings", "macro1_title"))[:6] - macro2Title = (app.data.config.getValue("Maslow Settings", "macro2_title"))[:6] - if template == "/text/mobile": - return render_template( - "frontpageText_mobile.html", - modalStyle="modal-lg", - isControls=True, - macro1_title=macro1Title, - macro2_title=macro2Title, - ) - else: - return render_template( - "frontpageText.html", - modalStyle="mw-100 w-75", - macro1_title=macro1Title, - macro2_title=macro2Title, - ) - - -@app.route("/logs") -@mobile_template("/logs/{mobile/}") -def logs(template): - print("here") - app.data.logger.resetIdler() - if template == "/logs/mobile/": - return render_template("logs.html") - else: - return render_template("logs.html") - - -@app.route("/maslowSettings", methods=["POST"]) -def maslowSettings(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - app.data.config.updateSettings("Maslow Settings", result) - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - - -@app.route("/advancedSettings", methods=["POST"]) -def advancedSettings(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - app.data.config.updateSettings("Advanced Settings", result) - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - - -@app.route("/webControlSettings", methods=["POST"]) -def webControlSettings(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - app.data.config.updateSettings("WebControl Settings", result) - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - - -@app.route("/cameraSettings", methods=["POST"]) -def cameraSettings(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - app.data.config.updateSettings("Camera Settings", result) - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - - -@app.route("/gpioSettings", methods=["POST"]) -def gpioSettings(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - app.data.config.updateSettings("GPIO Settings", result) - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - - -@app.route("/uploadGCode", methods=["POST"]) -def uploadGCode(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - directory = result["selectedDirectory"] - # print(directory) - f = request.files.getlist("file[]") - print(f) - home = app.data.config.getHome() - app.data.config.setValue( - "Computed Settings", "lastSelectedDirectory", directory - ) - - if len(f) > 0: - firstFile = f[0] - for file in f: - app.data.gcodeFile.filename = ( - home - + "/.WebControl/gcode/" - + directory - + "/" - + secure_filename(file.filename) - ) - file.save(app.data.gcodeFile.filename) - app.data.gcodeFile.filename = ( - home - + "/.WebControl/gcode/" - + directory - + "/" - + secure_filename(firstFile.filename) - ) - returnVal = app.data.gcodeFile.loadUpdateFile() - if returnVal: - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/openGCode", methods=["POST"]) -def openGCode(): - app.data.logger.resetIdler() - if request.method == "POST": - f = request.form["selectedGCode"] - app.data.console_queue.put(f"selectedGcode={f}") - tDir = f.split("/") - app.data.config.setValue("Computed Settings", "lastSelectedDirectory", tDir[0]) - home = app.data.config.getHome() - app.data.gcodeFile.filename = home + "/.WebControl/gcode/" + f - app.data.config.setValue("Maslow Settings", "openFile", tDir[1]) - returnVal = app.data.gcodeFile.loadUpdateFile() - if returnVal: - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/saveGCode", methods=["POST"]) -def saveGCode(): - app.data.logger.resetIdler() - if request.method == "POST": - print(request.form) - f = request.form["fileName"] - d = request.form["selectedDirectory"] - app.data.console_queue.put(f"selectedGcode={f}") - app.data.config.setValue("Computed Settings", "lastSelectedDirectory", d) - home = app.data.config.getHome() - returnVal = app.data.gcodeFile.saveFile(f, home + "/.WebControl/gcode/" + d) - """ - tDir = f.split("/") - app.data.config.setValue("Computed Settings","lastSelectedDirectory",tDir[0]) - home = app.data.config.getHome() - app.data.gcodeFile.filename = home+"/.WebControl/gcode/" + f - app.data.config.setValue("Maslow Settings", "openFile", tDir[1]) - returnVal = app.data.gcodeFile.loadUpdateFile() - """ - if returnVal: - app.data.config.setValue("Maslow Settings", "openFile", f) - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/openBoard", methods=["POST"]) -def openBoard(): - app.data.logger.resetIdler() - if request.method == "POST": - f = request.form["selectedBoard"] - app.data.console_queue.put(f"selectedBoard={f}") - tDir = f.split("/") - app.data.config.setValue( - "Computed Settings", "lastSelectedBoardDirectory", tDir[0] - ) - home = app.data.config.getHome() - app.data.gcodeFile.filename = home + "/.WebControl/boards/" + f - app.data.config.setValue("Maslow Settings", "openBoardFile", tDir[1]) - returnVal = app.data.boardManager.loadBoard(home + "/.WebControl/boards/" + f) - if returnVal: - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/saveBoard", methods=["POST"]) -def saveBoard(): - app.data.logger.resetIdler() - if request.method == "POST": - print(request.form) - f = request.form["fileName"] - d = request.form["selectedDirectory"] - app.data.console_queue.put(f"selectedBoard={f}") - app.data.config.setValue("Computed Settings", "lastSelectedBoardDirectory", d) - home = app.data.config.getHome() - returnVal = app.data.boardManager.saveBoard( - f, home + "/.WebControl/boards/" + d - ) - app.data.config.setValue("Maslow Settings", "openBoardFile", f) - if returnVal: - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/importFile", methods=["POST"]) -def importFile(): - app.data.logger.resetIdler() - if request.method == "POST": - f = request.files["file"] - home = app.data.config.getHome() - secureFilename = home + "/.WebControl/imports/" + secure_filename(f.filename) - f.save(secureFilename) - returnVal = app.data.importFile.importGCini(secureFilename) - if returnVal: - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/importFileWCJSON", methods=["POST"]) -def importFileJSON(): - app.data.logger.resetIdler() - if request.method == "POST": - f = request.files["file"] - home = app.data.config.getHome() - secureFilename = home + "/.WebControl/imports/" + secure_filename(f.filename) - f.save(secureFilename) - returnVal = app.data.importFile.importWebControlJSON(secureFilename) - if returnVal: - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/importRestoreWebControl", methods=["POST"]) -def importRestoreWebControl(): - app.data.logger.resetIdler() - if request.method == "POST": - f = request.files["file"] - home = app.data.config.getHome() - secureFilename = home + "/" + secure_filename(f.filename) - f.save(secureFilename) - returnVal = app.data.actions.restoreWebControl(secureFilename) - if returnVal: - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/sendGCode", methods=["POST"]) -def sendGcode(): - app.data.logger.resetIdler() - # print(request.form)#["gcodeInput"]) - if request.method == "POST": - returnVal = app.data.actions.sendGCode(request.form["gcode"].rstrip()) - if returnVal: - message = {"status": 200} - resp = jsonify("success") - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify("failed") - resp.status_code = 500 - return resp - - -@app.route("/triangularCalibration", methods=["POST"]) -def triangularCalibration(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - ( - motorYoffsetEst, - rotationRadiusEst, - chainSagCorrectionEst, - cut34YoffsetEst, - ) = app.data.actions.calibrate(result) - # print(returnVal) - if motorYoffsetEst: - message = { - "status": 200, - "data": { - "motorYoffset": motorYoffsetEst, - "rotationRadius": rotationRadiusEst, - "chainSagCorrection": chainSagCorrectionEst, - "calibrationError": cut34YoffsetEst, - }, - } - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/holeyCalibration", methods=["POST"]) -def holeyCalibration(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - ( - motorYoffsetEst, - distanceBetweenMotors, - leftChainTolerance, - rightChainTolerance, - calibrationError, - ) = app.data.actions.holeyCalibrate(result) - # print(returnVal) - if motorYoffsetEst: - message = { - "status": 200, - "data": { - "motorYoffset": motorYoffsetEst, - "distanceBetweenMotors": distanceBetweenMotors, - "leftChainTolerance": leftChainTolerance, - "rightChainTolerance": rightChainTolerance, - "calibrationError": calibrationError, - }, - } - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/opticalCalibration", methods=["POST"]) -def opticalCalibration(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify(message) - resp.status_code = 500 - return resp - - -@app.route("/quickConfigure", methods=["POST"]) -def quickConfigure(): - app.data.logger.resetIdler() - if request.method == "POST": - result = request.form - app.data.config.updateQuickConfigure(result) - message = {"status": 200} - resp = jsonify(message) - resp.status_code = 200 - return resp - - -@app.route("/editGCode", methods=["POST"]) -def editGCode(): - app.data.logger.resetIdler() - # print(request.form["gcode"]) - if request.method == "POST": - returnVal = app.data.actions.updateGCode(request.form["gcode"].rstrip()) - if returnVal: - message = {"status": 200} - resp = jsonify("success") - resp.status_code = 200 - return resp - else: - message = {"status": 500} - resp = jsonify("failed") - resp.status_code = 500 - return resp - - -@app.route("/downloadDiagnostics", methods=["GET"]) -def downloadDiagnostics(): - app.data.logger.resetIdler() - if request.method == "GET": - returnVal = app.data.actions.downloadDiagnostics() - if returnVal != False: - print(returnVal) - return send_file(returnVal, as_attachment=True) - else: - resp = jsonify("failed") - resp.status_code = 500 - return resp - - -@app.route("/backupWebControl", methods=["GET"]) -def backupWebControl(): - app.data.logger.resetIdler() - if request.method == "GET": - returnVal = app.data.actions.backupWebControl() - if returnVal != False: - print(returnVal) - return send_file(returnVal) - else: - resp = jsonify("failed") - resp.status_code = 500 - return resp - - -@app.route("/editBoard", methods=["POST"]) -def editBoard(): - app.data.logger.resetIdler() - if request.method == "POST": - returnVal = app.data.boardManager.editBoard(request.form) - if returnVal: - resp = jsonify("success") - resp.status_code = 200 - return resp - else: - resp = jsonify("failed") - resp.status_code = 500 - return resp - - -@app.route("/trimBoard", methods=["POST"]) -def trimBoard(): - app.data.logger.resetIdler() - if request.method == "POST": - returnVal = app.data.boardManager.trimBoard(request.form) - if returnVal: - resp = jsonify("success") - resp.status_code = 200 - return resp - else: - resp = jsonify("failed") - resp.status_code = 500 - return resp - - -@app.route("/assets/") -def sendDocs(path): - print(path) - return send_from_directory("docs/assets/", path) - - -@socketio.on("checkInRequested", namespace="/WebMCP") -def checkInRequested(): - socketio.emit("checkIn", namespace="/WebMCP") - - -@socketio.on("connect", namespace="/WebMCP") -def watchdog_connect(): - app.data.console_queue.put("watchdog connected") - app.data.console_queue.put(request.sid) - socketio.emit("connect", namespace="/WebMCP") - if app.mcpthread == None: - app.data.console_queue.put("going to start mcp thread") - app.mcpthread = socketio.start_background_task( - app.data.mcpProcessor.start, current_app._get_current_object() - ) - app.data.console_queue.put("created mcp thread") - app.mcpthread.start() - app.data.console_queue.put("started mcp thread") - - -@socketio.on("my event", namespace="/MaslowCNC") -def my_event(msg): - app.data.console_queue.put(msg["data"]) - - -@socketio.on("modalClosed", namespace="/MaslowCNC") -def modalClosed(msg): - app.data.logger.resetIdler() - data = json.dumps({"title": msg["data"]}) - socketio.emit( - "message", - {"command": "closeModals", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", - ) - - -@socketio.on("contentModalClosed", namespace="/MaslowCNC") -def contentModalClosed(msg): - # Note, this shouldn't be called anymore - # todo: cleanup - app.data.logger.resetIdler() - data = json.dumps({"title": msg["data"]}) - print(data) - # socketio.emit("message", {"command": "closeContentModals", "data": data, "dataFormat": "json"}, - # namespace="/MaslowCNC", ) - - -""" -todo: cleanup -not used -@socketio.on("actionModalClosed", namespace="/MaslowCNC") -def actionModalClosed(msg): - app.data.logger.resetIdler() - data = json.dumps({"title": msg["data"]}) - socketio.emit("message", {"command": "closeActionModals", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", ) -""" - - -@socketio.on("alertModalClosed", namespace="/MaslowCNC") -def alertModalClosed(msg): - app.data.logger.resetIdler() - data = json.dumps({"title": msg["data"]}) - socketio.emit( - "message", - {"command": "closeAlertModals", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", - ) - - -@socketio.on("requestPage", namespace="/MaslowCNC") -def requestPage(msg): - app.data.logger.resetIdler() - app.data.console_queue.put(request.sid) - client = request.sid - try: - ( - page, - title, - isStatic, - modalSize, - modalType, - resume, - ) = app.webPageProcessor.createWebPage( - msg["data"]["page"], msg["data"]["isMobile"], msg["data"]["args"] - ) - # if msg["data"]["page"] != "help": - # client = "all" - data = json.dumps( - { - "title": title, - "message": page, - "isStatic": isStatic, - "modalSize": modalSize, - "modalType": modalType, - "resume": resume, - "client": client, - } - ) - socketio.emit( - "message", - {"command": "activateModal", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", - ) - except Exception as e: - app.data.console_queue.put(e) - - -@socketio.on("connect", namespace="/MaslowCNC") -def test_connect(): - app.data.console_queue.put("connected") - app.data.console_queue.put(request.sid) - if app.uithread == None: - app.uithread = socketio.start_background_task( - app.UIProcessor.start, current_app._get_current_object() - ) - app.uithread.start() - - if not app.data.connectionStatus: - app.data.console_queue.put( - "Attempting to re-establish connection to controller" - ) - app.data.serialPort.openConnection() - - socketio.emit("my response", {"data": "Connected", "count": 0}) - address = app.data.hostAddress - data = json.dumps({"hostAddress": address}) - print(data) - socketio.emit( - "message", - {"command": "hostAddress", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", - ) - if app.data.pyInstallUpdateAvailable: - app.data.ui_queue1.put("Action", "pyinstallUpdate", "on") - - -@socketio.on("disconnect", namespace="/MaslowCNC") -def test_disconnect(): - app.data.console_queue.put("Client disconnected") - - -@socketio.on("action", namespace="/MaslowCNC") -def command(msg): - app.data.logger.resetIdler() - retval = app.data.actions.processAction(msg) - if retval == "Shutdown": - print("Shutting Down") - socketio.stop() - print("Shutdown") - if retval == "TurnOffRPI": - print("Turning off RPI") - os.system("sudo poweroff") - - -@socketio.on("settingRequest", namespace="/MaslowCNC") -def settingRequest(msg): - app.data.logger.resetIdler() - # didn't move to actions.. this request is just to send it computed values.. keeping it here makes it faster than putting it through the UIProcessor - setting, value = app.data.actions.processSettingRequest( - msg["data"]["section"], msg["data"]["setting"] - ) - if setting is not None: - data = json.dumps({"setting": setting, "value": value}) - socketio.emit( - "message", - {"command": "requestedSetting", "data": data, "dataFormat": "json"}, - namespace="/MaslowCNC", - ) - - -@socketio.on("updateSetting", namespace="/MaslowCNC") -def updateSetting(msg): - app.data.logger.resetIdler() - if not app.data.actions.updateSetting(msg["data"]["setting"], msg["data"]["value"]): - app.data.ui_queue1.put("Alert", "Alert", "Error updating setting") - - -@socketio.on("checkForGCodeUpdate", namespace="/MaslowCNC") -def checkForGCodeUpdate(msg): - app.data.logger.resetIdler() - # this currently doesn't check for updated gcode, it just resends it.. - ## the gcode file might change the active units so we need to inform the UI of the change. - app.data.ui_queue1.put("Action", "unitsUpdate", "") - app.data.ui_queue1.put("Action", "gcodeUpdate", "") - - -@socketio.on("checkForBoardUpdate", namespace="/MaslowCNC") -def checkForBoardUpdate(msg): - app.data.logger.resetIdler() - # this currently doesn't check for updated board, it just resends it.. - app.data.ui_queue1.put("Action", "boardUpdate", "") - - -@socketio.on("connect", namespace="/MaslowCNCLogs") -def log_connect(): - app.data.console_queue.put("connected to log") - app.data.console_queue.put(request.sid) - if app.logstreamerthread == None: - app.logstreamerthread = socketio.start_background_task( - app.LogStreamer.start, current_app._get_current_object() - ) - app.logstreamerthread.start() - - socketio.emit( - "my response", {"data": "Connected", "count": 0}, namespace="/MaslowCNCLog" - ) - - -@socketio.on("disconnect", namespace="/MaslowCNCLogs") -def log_disconnect(): - app.data.console_queue.put("Client disconnected") +init_route(app) +init_socket_mcp(app) +init_socket_maslowcnc(app) +init_socket_maslowcnclogs(app) +init_socket_catchall(app) @app.template_filter("isnumber") @@ -879,6 +89,24 @@ def isnumber(s): # def shutdown(): # print("Shutdown") +with app.app_context(): + capp = current_app._get_current_object() + + if app.uithread == None: + app.data.console_queue.put(f"{__name__}: starting up the app.uithread as a background task") + app.uithread = socketio.server.eio._async['thread'](target=app.UIProcessor.start, args=capp) + app.data.console_queue.put(f"{__name__}: created and started app.uithread") + + if app.logstreamerthread == None: + app.data.console_queue.put(f"{__name__}: starting up the app.logstreamerthread as a background task") + app.logstreamerthread = socketio.server.eio._async['thread'](target=app.LogStreamer.start, args=capp) + app.data.console_queue.put(f"{__name__}: created and started app.logstreamerthread") + + if app.mcpthread == None: + app.data.console_queue.put(f"{__name__}: starting up the app.mcpthread as a background task") + app.mcpthread = socketio.server.eio._async['thread'](target=app.data.mcpProcessor.start, args=capp) + app.data.console_queue.put(f"{__name__}: created and started app.mcpthread") + if __name__ == "__main__": app.debug = False @@ -893,20 +121,25 @@ def isnumber(s): webPortInt = 5000 except Exception as e: app.data.console_queue.put(e) - app.data.console_queue.put("Invalid port assignment found in webcontrol.json") + app.data.console_queue.put("Main: Invalid port assignment found in webcontrol.json") - print("-$$$$$-") - print(os.path.abspath(__file__)) + app.data.console_queue.put("Main: -$$$$$-") + app.data.console_queue.put(f"Main: {os.path.abspath(__file__)}") app.data.releaseManager.processAbsolutePath(os.path.abspath(__file__)) - print("-$$$$$-") + app.data.console_queue.put("Main: -$$$$$-") - print("opening browser") - webPortStr = str(webPortInt) - webbrowser.open_new_tab("http://localhost:" + webPortStr) + default_host_ip = "0.0.0.0" host_name = socket.gethostname() host_ip = socket.gethostbyname(host_name) - app.data.hostAddress = host_ip + ":" + webPortStr + if host_ip.startswith("127.0."): + # Thanks Ubuntu for mucking it up + host_ip = default_host_ip + app.data.console_queue.put(f"Main: setting app data host address to {host_ip}:{webPortInt}") + app.data.hostAddress = f"http://{host_ip}:{webPortInt}" + + app.data.console_queue.put(f"Main: opening browser on {app.data.hostAddress}") + webbrowser.open_new_tab(app.data.hostAddress) # app.data.shutdown = shutdown - socketio.run(app, use_reloader=False, host="0.0.0.0", port=webPortInt) + socketio.run(app, use_reloader=False, host=host_ip, port=webPortInt, log_output=False) # socketio.run(app, host='0.0.0.0') diff --git a/requirements.in b/requirements.in index a5e7f742..62b75315 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,5 @@ -r ./Actions/requirements.in +# -r ./App/requirements.in -r ./Background/requirements.in -r ./Boards/requirements.in -r ./Connection/requirements.in @@ -7,16 +8,13 @@ -r ./ReleaseManager/requirements.in -r ./WebPageProcessor/requirements.in +eventlet # Flask-Misaka Flask-Mobility Flask-SocketIO gevent -# greenlet # used by gevent -# itsdangerous -# Jinja2 -# MarkupSafe -# python-engineio -# python-socketio -# six +# Pyjion requires Python 3.10+ and .net 7+ to work +pyjion + Werkzeug diff --git a/requirements.txt b/requirements.txt index 7074ce1e..dc7fd2e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,115 +1,128 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile --resolver=backtracking requirements.in # -absl-py==1.3.0 +absl-py==1.4.0 # via ortools -bidict==0.22.0 +bidict==0.22.1 # via python-socketio certifi==2022.12.7 # via requests cffi==1.15.1 - # via pynacl -charset-normalizer==2.1.1 + # via + # cryptography + # gevent + # pynacl +charset-normalizer==3.1.0 # via requests click==8.1.3 # via flask +colorama==0.4.6 + # via click colorzero==2.0 # via gpiozero +cryptography==40.0.1 + # via pyjwt deprecated==1.2.13 # via pygithub distro==1.8.0 # via -r ./Connection/requirements.in -flask==2.2.2 +dnspython==2.3.0 + # via eventlet +eventlet==0.33.3 + # via -r requirements.in +flask==2.2.3 # via # -r ./WebPageProcessor/requirements.in # flask-mobility # flask-socketio flask-mobility==1.1.0 # via -r requirements.in -flask-socketio==5.3.2 +flask-socketio==5.3.3 # via -r requirements.in gevent==22.10.2 # via -r requirements.in gpiozero==1.6.2 # via -r ./Actions/requirements.in -greenlet==2.0.1 - # via gevent +greenlet==2.0.2 + # via + # eventlet + # gevent idna==3.4 # via requests -importlib-metadata==5.1.0 - # via - # flask - # markdown itsdangerous==2.1.2 # via flask jinja2==3.1.2 # via flask -markdown==3.4.1 +markdown==3.4.3 # via -r ./WebPageProcessor/requirements.in -markupsafe==2.1.1 +markupsafe==2.1.2 # via # jinja2 # werkzeug -numpy==1.23.5 +numpy==1.24.2 # via # -r ./Actions/requirements.in # opencv-python # ortools # scipy -opencv-python==4.6.0.66 +opencv-python==4.7.0.72 # via # -r ./Actions/requirements.in # -r ./Background/requirements.in -ortools==9.5.2237 +ortools==9.6.2534 # via -r ./GCodeOptimizer/requirements.in -protobuf==4.21.12 +protobuf==4.22.1 # via ortools psutil==5.9.4 # via -r ./Background/requirements.in pycparser==2.21 # via cffi -pygithub==1.57 +pygithub==1.58.1 # via # -r ./HelpManager/requirements.in # -r ./ReleaseManager/requirements.in -pyjwt==2.6.0 +pyjion==2.0.0 + # via -r requirements.in +pyjwt[crypto]==2.6.0 # via pygithub pynacl==1.5.0 # via pygithub pyserial==3.5 # via -r ./Connection/requirements.in -python-engineio==4.3.4 +python-engineio==4.4.0 # via python-socketio python-frontmatter==1.0.0 # via -r ./WebPageProcessor/requirements.in -python-socketio==5.7.2 +python-socketio==5.8.0 # via flask-socketio pyyaml==6.0 # via python-frontmatter -requests==2.28.1 +requests==2.28.2 # via # -r ./ReleaseManager/requirements.in # pygithub schedule==1.1.0 # via -r ./Connection/requirements.in -scipy==1.9.3 - # via -r ./Actions/requirements.in -urllib3==1.26.13 +scipy==1.10.1 + # via + # -r ./Actions/requirements.in + # ortools +six==1.16.0 + # via eventlet +urllib3==1.26.15 # via requests -werkzeug==2.2.2 +werkzeug==2.2.3 # via # -r requirements.in # flask -wrapt==1.14.1 +wrapt==1.15.0 # via deprecated -zipp==3.11.0 - # via importlib-metadata zope-event==4.6 # via gevent -zope-interface==5.5.2 +zope-interface==6.0 # via gevent # The following packages are considered to be unsafe in a requirements file: diff --git a/static/package-lock.json b/static/package-lock.json new file mode 100644 index 00000000..ae4dbb39 --- /dev/null +++ b/static/package-lock.json @@ -0,0 +1,203 @@ +{ + "name": "webcontrol", + "version": "0.94.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "webcontrol", + "version": "0.94.0", + "license": "GPL-3.0", + "dependencies": { + "ace-builds": "^1.14.0", + "bootstrap": "^5.2.3", + "feather-icons": "^4.29.0", + "jquery": "^3.6.3", + "pako": "^2.1.0", + "socket.io-client": "^4.5.4", + "three-spritetext": "^1.6.5" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/ace-builds": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.14.0.tgz", + "integrity": "sha512-3q8LvawomApRCt4cC0OzxVjDsZ609lDbm8l0Xl9uqG06dKEq4RT0YXLUyk7J2SxmqIp5YXzZNw767Dr8GKUruw==" + }, + "node_modules/bootstrap": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", + "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.6" + } + }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, + "node_modules/core-js": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.0.tgz", + "integrity": "sha512-wY6cKosevs430KRkHUIsvepDXHGjlXOZO3hYXNyqpD6JvB0X28aXyv0t1Y1vZMwE7SoKmtfa6IASHCPN52FwBQ==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.3.tgz", + "integrity": "sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/feather-icons": { + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/feather-icons/-/feather-icons-4.29.0.tgz", + "integrity": "sha512-Y7VqN9FYb8KdaSF0qD1081HCkm0v4Eq/fpfQYQnubpqi0hXx14k+gF9iqtRys1SIcTEi97xDi/fmsPFZ8xo0GQ==", + "dependencies": { + "classnames": "^2.2.5", + "core-js": "^3.1.3" + } + }, + "node_modules/jquery": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", + "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/socket.io-client": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.4.tgz", + "integrity": "sha512-ZpKteoA06RzkD32IbqILZ+Cnst4xewU7ZYK12aS1mzHftFFjpoMz69IuhP/nL25pJfao/amoPI527KnuhFm01g==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.2.3", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/three": { + "version": "0.148.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.148.0.tgz", + "integrity": "sha512-8uzVV+qhTPi0bOFs/3te3RW6hb3urL8jYEl6irjCWo/l6sr8MPNMcClFev/MMYeIxr0gmDcoXTy/8LXh/LXkfw==", + "peer": true + }, + "node_modules/three-spritetext": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/three-spritetext/-/three-spritetext-1.6.5.tgz", + "integrity": "sha512-ttA1ce3tJz6OFojLGY3VjtqF9johetq50TztYcPFWqdEUrwPmej5XXcHahVyQGd88FHRDzJmW2rW3zSifUsdYA==", + "peerDependencies": { + "three": ">=0.86.0" + } + }, + "node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/static/package.json b/static/package.json new file mode 100644 index 00000000..8a1b2243 --- /dev/null +++ b/static/package.json @@ -0,0 +1,34 @@ +{ + "name": "webcontrol", + "version": "0.94.0", + "description": "Web interface for the Maslow CNC router", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/WebControlCNC/WebControl.git" + }, + "keywords": [ + "Maslow", + "CNC", + "WebControl", + "GroundControl" + ], + "author": "", + "license": "GPL-3.0", + "bugs": { + "url": "https://github.com/WebControlCNC/WebControl/issues" + }, + "homepage": "https://github.com/WebControlCNC/WebControl#readme", + "dependencies": { + "ace-builds": "^1.14.0", + "bootstrap": "^5.2.3", + "feather-icons": "^4.29.0", + "jquery": "^3.6.3", + "pako": "^2.1.0", + "socket.io-client": "^4.5.4", + "three-spritetext": "^1.6.5" + } +} diff --git a/static/scripts/OrbitControls.js b/static/scripts/OrbitControls.js new file mode 100644 index 00000000..9d76462a --- /dev/null +++ b/static/scripts/OrbitControls.js @@ -0,0 +1,1289 @@ +// This is the same as that found in /node_modules/three/examples/jsm/controls/OrbitControls.js +// However, the import location is changed so that it can be used raw without relying on some transpiler +import { + EventDispatcher, + MOUSE, + Quaternion, + Spherical, + TOUCH, + Vector2, + Vector3 +} from "three"; + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +const _changeEvent = { type: 'change' }; +const _startEvent = { type: 'start' }; +const _endEvent = { type: 'end' }; + +class OrbitControls extends EventDispatcher { + + constructor( object, domElement ) { + + super(); + + this.object = object; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 + + // The four arrow keys + this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; + + // Mouse buttons + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // the target DOM element for key events + this._domElementKeyEvents = null; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.getDistance = function () { + + return this.object.position.distanceTo( this.target ); + + }; + + this.listenToKeyEvents = function ( domElement ) { + + domElement.addEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = domElement; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( _changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + const offset = new Vector3(); + + // so camera.up is the orbit axis + const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + const quatInverse = quat.clone().invert(); + + const lastPosition = new Vector3(); + const lastQuaternion = new Quaternion(); + + const twoPI = 2 * Math.PI; + + return function update() { + + const position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + + let min = scope.minAzimuthAngle; + let max = scope.maxAzimuthAngle; + + if ( isFinite( min ) && isFinite( max ) ) { + + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + + if ( min <= max ) { + + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + + } else { + + spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, spherical.theta ) : + Math.min( max, spherical.theta ); + + } + + } + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + + if ( scope._domElementKeyEvents !== null ) { + + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + + } + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + const scope = this; + + const STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + let state = STATE.NONE; + + const EPS = 0.000001; + + // current position in spherical coordinates + const spherical = new Spherical(); + const sphericalDelta = new Spherical(); + + let scale = 1; + const panOffset = new Vector3(); + let zoomChanged = false; + + const rotateStart = new Vector2(); + const rotateEnd = new Vector2(); + const rotateDelta = new Vector2(); + + const panStart = new Vector2(); + const panEnd = new Vector2(); + const panDelta = new Vector2(); + + const dollyStart = new Vector2(); + const dollyEnd = new Vector2(); + const dollyDelta = new Vector2(); + + const pointers = []; + const pointerPositions = {}; + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + const panLeft = function () { + + const v = new Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + const panUp = function () { + + const v = new Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + const pan = function () { + + const offset = new Vector3(); + + return function pan( deltaX, deltaY ) { + + const element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + const position = scope.object.position; + offset.copy( position ).sub( scope.target ); + let targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + const element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + let needsUpdate = false; + + switch ( event.code ) { + + case scope.keys.UP: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + + } else { + + pan( 0, scope.keyPanSpeed ); + + } + + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + + } else { + + pan( 0, - scope.keyPanSpeed ); + + } + + needsUpdate = true; + break; + + case scope.keys.LEFT: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + + } else { + + pan( scope.keyPanSpeed, 0 ); + + } + + needsUpdate = true; + break; + + case scope.keys.RIGHT: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + + } else { + + pan( - scope.keyPanSpeed, 0 ); + + } + + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate() { + + if ( pointers.length === 1 ) { + + rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan() { + + if ( pointers.length === 1 ) { + + panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly() { + + const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX; + const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enablePan ) handleTouchStartPan(); + + } + + function handleTouchStartDollyRotate() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enableRotate ) handleTouchStartRotate(); + + } + + function handleTouchMoveRotate( event ) { + + if ( pointers.length == 1 ) { + + rotateEnd.set( event.pageX, event.pageY ); + + } else { + + const position = getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + const element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( pointers.length === 1 ) { + + panEnd.set( event.pageX, event.pageY ); + + } else { + + const position = getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + const position = getSecondPointerPosition( event ); + + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyOut( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + if ( pointers.length === 0 ) { + + scope.domElement.setPointerCapture( event.pointerId ); + + scope.domElement.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.addEventListener( 'pointerup', onPointerUp ); + + } + + // + + addPointer( event ); + + if ( event.pointerType === 'touch' ) { + + onTouchStart( event ); + + } else { + + onMouseDown( event ); + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + onTouchMove( event ); + + } else { + + onMouseMove( event ); + + } + + } + + function onPointerUp( event ) { + + removePointer( event ); + + if ( pointers.length === 0 ) { + + scope.domElement.releasePointerCapture( event.pointerId ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + } + + scope.dispatchEvent( _endEvent ); + + state = STATE.NONE; + + } + + function onPointerCancel( event ) { + + removePointer( event ); + + } + + function onMouseDown( event ) { + + let mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( _startEvent ); + + } + + } + + function onMouseMove( event ) { + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; + + event.preventDefault(); + + scope.dispatchEvent( _startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( _endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + trackPointer( event ); + + switch ( pointers.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate(); + + state = STATE.TOUCH_ROTATE; + + break; + + case TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan(); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan(); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate(); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( _startEvent ); + + } + + } + + function onTouchMove( event ) { + + trackPointer( event ); + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + function addPointer( event ) { + + pointers.push( event ); + + } + + function removePointer( event ) { + + delete pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < pointers.length; i ++ ) { + + if ( pointers[ i ].pointerId == event.pointerId ) { + + pointers.splice( i, 1 ); + return; + + } + + } + + } + + function trackPointer( event ) { + + let position = pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + function getSecondPointerPosition( event ) { + + const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ]; + + return pointerPositions[ pointer.pointerId ]; + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + + // force an update at start + + this.update(); + + } + +} + + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// This is very similar to OrbitControls, another set of touch behavior +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or arrow keys / touch: one-finger move + +class MapControls extends OrbitControls { + + constructor( object, domElement ) { + + super( object, domElement ); + + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = MOUSE.PAN; + this.mouseButtons.RIGHT = MOUSE.ROTATE; + + this.touches.ONE = TOUCH.PAN; + this.touches.TWO = TOUCH.DOLLY_ROTATE; + + } + +} + +export { OrbitControls, MapControls }; diff --git a/static/scripts/ace.js b/static/scripts/archive/ace.js similarity index 100% rename from static/scripts/ace.js rename to static/scripts/archive/ace.js diff --git a/static/scripts/feather.min.js b/static/scripts/archive/feather.min.js similarity index 100% rename from static/scripts/feather.min.js rename to static/scripts/archive/feather.min.js diff --git a/static/scripts/archive/frontpage.js b/static/scripts/archive/frontpage.js index d3371155..4d7c5ca3 100644 --- a/static/scripts/archive/frontpage.js +++ b/static/scripts/archive/frontpage.js @@ -1,3 +1,7 @@ +import "feather"; +import "jquery"; +import pako from "pako"; + //checkForGCodeUpdate(); //setInterval(function(){ alert("Hello"); }, 3000); @@ -104,7 +108,8 @@ function unitSwitch(){ } } -$(document).ready(function(){ +$(() => { + // document.ready settingRequest("Computed Settings","units"); settingRequest("Computed Settings","distToMove"); settingRequest("Computed Settings","homePosition"); diff --git a/static/scripts/jquery-3.3.1.min.js b/static/scripts/archive/jquery-3.3.1.min.js similarity index 100% rename from static/scripts/jquery-3.3.1.min.js rename to static/scripts/archive/jquery-3.3.1.min.js diff --git a/static/scripts/mode-javascript.js b/static/scripts/archive/mode-javascript.js similarity index 100% rename from static/scripts/mode-javascript.js rename to static/scripts/archive/mode-javascript.js diff --git a/static/scripts/orbitcontrols.js b/static/scripts/archive/orbitcontrols.js similarity index 100% rename from static/scripts/orbitcontrols.js rename to static/scripts/archive/orbitcontrols.js diff --git a/static/scripts/pako.min.js b/static/scripts/archive/pako.min.js similarity index 100% rename from static/scripts/pako.min.js rename to static/scripts/archive/pako.min.js diff --git a/static/scripts/setZaxis.js b/static/scripts/archive/setZaxis.js similarity index 92% rename from static/scripts/setZaxis.js rename to static/scripts/archive/setZaxis.js index 7bd9c6ce..20e4da41 100644 --- a/static/scripts/setZaxis.js +++ b/static/scripts/archive/setZaxis.js @@ -1,6 +1,5 @@ -function (processZAxisLimitMessage){ +import "jquery"; -} function processZAxisRequestedSetting(msg){ if (msg.setting=="unitsZ"){ console.log("requestedSetting:"+msg.value); @@ -11,6 +10,7 @@ function processZAxisRequestedSetting(msg){ $("#distToMoveZ").val(msg.value) } } + function unitSwitchZ(){ if ( $("#unitsZ").text()=="MM") { $("#unitsZ").text("INCHES"); @@ -24,7 +24,9 @@ function unitSwitchZ(){ updateSetting('toMMZ',distToMoveZ); } } -$(document).ready(function(){ + +$(() => { + // document.ready settingRequest("Computed Settings","unitsZ"); settingRequest("Computed Settings","distToMoveZ"); }); \ No newline at end of file diff --git a/static/scripts/socket.io-2.1.1.js b/static/scripts/archive/socket.io-2.1.1.js similarity index 100% rename from static/scripts/socket.io-2.1.1.js rename to static/scripts/archive/socket.io-2.1.1.js diff --git a/static/scripts/sprite3D.js b/static/scripts/archive/sprite3D.js similarity index 100% rename from static/scripts/sprite3D.js rename to static/scripts/archive/sprite3D.js diff --git a/static/scripts/theme-twilight.js b/static/scripts/archive/theme-twilight.js similarity index 100% rename from static/scripts/theme-twilight.js rename to static/scripts/archive/theme-twilight.js diff --git a/static/scripts/three.js b/static/scripts/archive/three.js similarity index 100% rename from static/scripts/three.js rename to static/scripts/archive/three.js diff --git a/static/scripts/worker-javascript.js b/static/scripts/archive/worker-javascript.js similarity index 100% rename from static/scripts/worker-javascript.js rename to static/scripts/archive/worker-javascript.js diff --git a/static/scripts/base.js b/static/scripts/base.js index 4964cbbc..2884c6c0 100644 --- a/static/scripts/base.js +++ b/static/scripts/base.js @@ -1,203 +1,191 @@ +import { Modal } from "bootstrap"; +import "jquery"; -var isMobile = false; //initialize as false $('.disabler').prop('disabled', true); -var isDisabled = true; -if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) - || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4))) { - isMobile = true; -} - -$(document).ready(function(){ - // Make all navbar drop down items collapse the menu when clicked. - if (isMobile) { - $("#navbarSupportedContent a.dropdown-item").attr("data-toggle", "collapse").attr("data-target", "#navbarSupportedContent"); - } +$(() => { + // document.ready + // Make all navbar drop down items collapse the menu when clicked. + window.isMobile = testForMobile(); + if (window.isMobile) { + $("#navbarSupportedContent a.dropdown-item").attr("data-bs-toggle", "collapse").attr("data-bs-target", "#navbarSupportedContent"); + } + window.isDisabled = true; }) -function processHealthMessage(data){ - //console.log(data.cpuUsage); - $("#cpuUsage").text("CPU: "+Math.round(data.cpuUsage).toString()+"%"); - $("#mobileCPUUsage").text(Math.round(data.cpuUsage).toString()+"%"); - if (data.bufferSize == -1){ - if ($("#bufferSize").is(":hidden") == false){ - $("#bufferSize").hide(); - } - if ((isMobile) && ($("#mobileBufferSize").is(":hidden") == false)){ - $("#mobileBufferSize").hide(); - } - } - else{ - if ($("#bufferSize").is(":hidden") == true){ - $("#bufferSize").show(); - } - if ((isMobile) && ($("#mobileBufferSize").is(":hidden") == true)){ - $("#mobileBufferSize").show(); - } - $("#bufferSize").text("Buffer: "+data.bufferSize.toString()); - $("#mobileBufferSize").text(data.bufferSize.toString()); - } +$.fn.scrollBottom = function () { + return $(this).scrollTop($(this)[0].scrollHeight); +}; +function testForMobile() { + return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) + || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0, 4))); +} +function processHealthMessage(data) { + //console.log(data.cpuUsage); + $("#cpuUsage").text("CPU: " + Math.round(data.cpuUsage).toString() + "%"); + $("#mobileCPUUsage").text(Math.round(data.cpuUsage).toString() + "%"); + if (data.bufferSize == -1) { + if ($("#bufferSize").is(":hidden") == false) { + $("#bufferSize").hide(); + } + if ((window.isMobile) && ($("#mobileBufferSize").is(":hidden") == false)) { + $("#mobileBufferSize").hide(); + } + } else { + if ($("#bufferSize").is(":hidden") == true) { + $("#bufferSize").show(); + } + if ((window.isMobile) && ($("#mobileBufferSize").is(":hidden") == true)) { + $("#mobileBufferSize").show(); + } + $("#bufferSize").text("Buffer: " + data.bufferSize.toString()); + $("#mobileBufferSize").text(data.bufferSize.toString()); + } } -function processControllerStatus(data){ - if (data.status=="disconnected"){ - $("#controllerStatusAlert").text("Not Connected"); - $("#controllerStatusAlert").removeClass('alert-success').addClass('alert-danger'); - $("#mobileControllerStatusAlert").removeClass('alert-success').addClass('alert-danger'); - if (isMobile) - { +function processControllerStatus(data) { + if (data.status == "disconnected") { + $("#controllerStatusAlert").text("Not Connected"); + $("#controllerStatusAlert").removeClass('alert-success').addClass('alert-danger'); + $("#mobileControllerStatusAlert").removeClass('alert-success').addClass('alert-danger'); + if (window.isMobile) { + $("#mobileControllerStatusAlert").show(); + $("#mobileControllerStatusAlert svg.feather.feather-check-circle").replaceWith(feather.icons["alert-circle"].toSvg()); + } else { + $("#mobileControllerStatusAlert").hide(); + } + $("#mobileControllerStatusButton").hide(); + + //feather.replace(); + } else { + if (data.fakeServoStatus) { + text = data.port + " / Fake Servo ON"; + $("#controllerStatusAlert").hide(); + $("#mobileControllerStatusAlert").hide(); + if (window.isMobile) { + $("#mobileControllerStatusButton").show(); + $("#mobileControllerStatusAlert svg.feather.feather-alert-circle").replaceWith(feather.icons["check-circle"].toSvg()); + } else { + $("#mobileControllerStatusAlert").hide(); + } + $("#controllerStatusButton").show(); + $("#controllerStatusButton").html(text); + //feather.replace(); + //$("#mobileControllerStatus").removeClass('alert-success').addClass('alert-danger'); + } else { + text = data.port; + $("#controllerStatusAlert").show(); + if (window.isMobile) { $("#mobileControllerStatusAlert").show(); - $("#mobileControllerStatusAlert svg.feather.feather-check-circle").replaceWith(feather.icons["alert-circle"].toSvg()); + $("#mobileControllerStatusAlert svg.feather.feather-alert-circle").replaceWith(feather.icons["check-circle"].toSvg()); + $("#mobileControllerStatusAlert").removeClass('alert-danger').addClass('alert-success'); } - else - $("#mobileControllerStatusAlert").hide(); + $("#controllerStatusButton").hide(); $("#mobileControllerStatusButton").hide(); + $("#controllerStatusAlert").text(text); + $("#controllerStatusAlert").removeClass('alert-danger').addClass('alert-success'); //feather.replace(); } - else - { - if (data.fakeServoStatus){ - text = data.port+" / Fake Servo ON"; - $("#controllerStatusAlert").hide(); - $("#mobileControllerStatusAlert").hide(); - if(isMobile) - { - $("#mobileControllerStatusButton").show(); - $("#mobileControllerStatusAlert svg.feather.feather-alert-circle").replaceWith(feather.icons["check-circle"].toSvg()); - } - else - $("#mobileControllerStatusAlert").hide(); - $("#controllerStatusButton").show(); - $("#controllerStatusButton").html(text); - //feather.replace(); - //$("#mobileControllerStatus").removeClass('alert-success').addClass('alert-danger'); + } +} - } - else{ - text = data.port; - $("#controllerStatusAlert").show(); - if (isMobile){ - $("#mobileControllerStatusAlert").show(); - $("#mobileControllerStatusAlert svg.feather.feather-alert-circle").replaceWith(feather.icons["check-circle"].toSvg()); - $("#mobileControllerStatusAlert").removeClass('alert-danger').addClass('alert-success'); - } - $("#controllerStatusButton").hide(); - $("#mobileControllerStatusButton").hide(); - $("#controllerStatusAlert").text(text); - $("#controllerStatusAlert").removeClass('alert-danger').addClass('alert-success'); +function showHide(elementName, shouldShow) { + if (shouldShow) { + $(elementName).show(); + } else { + $(elementName).hide(); + } +} - //feather.replace(); - } - } +function initializeModal(data) { + const $modal = $(`#${data.modalType}Modal`); + + $modal.data("name", data.title); + + const modalOptions = data.isStatic + ? {backdrop: "static", keyboard: false} + : {backdrop: "true", keyboard: true}; + const bsModal = new Modal($modal, modalOptions); + + return bsModal; } -function processActivateModal(data){ - var $modal, $modalTitle, $modalText - var message - //console.log(data) - if (data.modalType == "content"){ - $modal = $('#contentModal'); - $modalDialog = $('#contentDialog'); - $modalTitle = $('#contentModalTitle'); - $modalText = $('#contentModalText'); - if (data.resume=='footerSubmit'){ - $('#footerSubmit').show(); - } else { - $('#footerSubmit').hide(); - } - console.log("content modal") - //message = JSON.parse(data.message); - message = data.message; - } - else if (data.modalType == "alert") { - $modal = $('#alertModal'); - $modalDialog = $('#alertDialog'); - $modalTitle = $('#alertModalTitle'); - $modalText = $('#alertModalText'); - if (data.resume=="clear"){ - $('#clearButton').show(); - } else { - $('#clearButton').hide(); - } - //data is coming in as a jsonified string.. need to drop the extra quotes - message = JSON.parse(data.message); - } - else{ - $modal = $('#notificationModal'); - $modalDialog = $('#notificationDialog'); - $modalTitle = $('#notificationModalTitle'); - $modalText = $('#notificationModalText'); - if (data.resume=="resume"){ - $('#resumeButton').show(); - } else { - $('#resumeButton').hide(); - } - if (data.fakeServo=="fakeServo"){ - $('#fakeServoButton').show(); - } else { - $('#fakeServoButton').hide(); - } - if (data.progress=="true"){ - $('#progressBarDiv').show(); - } else { - $('#progressBarDiv').hide(); - } - if (data.progress=="spinner"){ - $('#notificationCircle').show(); - } else { - $('#notificationCircle').hide(); - } - message = data.message; - } - $modalDialog.removeClass('modal-lg'); - $modalDialog.removeClass('modal-sm'); - $modalDialog.removeClass('mw-100 w-75'); - if (data.modalSize=="large"){ - if (isMobile) - $modalDialog.addClass('modal-lg'); - else - $modalDialog.addClass('mw-100 w-75'); - } - if (data.modalSize=="medium") - $modalDialog.addClass('modal-lg'); - if (data.modalSize=="small") - $modalDialog.addClass('modal-sm'); - //$modal.data('bs.modal',null); - $modal.data('name',data.title); +function initializeModalDialog(data) { + const $modalDialog = $(`#${data.modalType}Dialog`); - $modalTitle.html("

"+data.title+"${data.title}

`); +} -function closeModals(data){ - console.log(data) - if ($('#notificationModal').data('name') == data.title) - { - console.log("here, closing notification modal"); - $('#notificationModal').modal('hide'); - $('#notificationCircle').hide() - } +function initialiseMessage(data) { + if (data.modalType == "content") { + showHide("#footerSubmit", data.resume == "footerSubmit"); + return data.message; + } else if (data.modalType == "alert") { + showHide("#clearButton", data.resume == "clear"); + //data is coming in as a jsonified string.. need to drop the extra quotes + return JSON.parse(data.message); + } + + showHide("#resumeButton", data.resume == "resume"); + showHide("#fakeServoButton", data.fakeServo == "fakeServo"); + showHide("#progressBarDiv", data.progress == "true"); + showHide("#notificationCircle", data.progress == "spinner"); + + return data.message; +} + +function initializeModalText(data) { + return $(`#${data.modalType}ModalText`); +} + +function processActivateModal(data) { + const modalTypes = ["content", "alert", "notification"]; + if (!modalTypes.includes(data.modalType)) { + // notification is the default modal type + data.modalType = "notification"; + } + + const bsModal = initializeModal(data); + initializeModalDialog(data); + initializeModalTitle(data); + const $modalText = initializeModalText(data); + const message = initialiseMessage(data); + + $modalText.html(message); + $modalText.scrollTop(0); + + bsModal.show(); +} + +function closeModals(data) { + if ($('#notificationModal').data('name') == data.title) { + $('#notificationModal').modal('hide'); + $('#notificationCircle').hide() + } } /* @@ -211,32 +199,24 @@ function closeActionModals(data){ } */ -function closeAlertModals(data){ - if ($('#alertModal').data('name') == data.title) - { - $('#alertModal').modal('hide'); - } +function closeAlertModals(data) { + if ($('#alertModal').data('name') == data.title) { + $('#alertModal').modal('hide'); + } } -function closeContentModals(data){ - if ($('#contentModal').data('name') == data.title) - { - $('#contentModal').modal('hide'); - } -}; - - -$.fn.scrollBottom = function() { - return $(this).scrollTop($(this)[0].scrollHeight); +function closeContentModals(data) { + if ($('#contentModal').data('name') == data.title) { + $('#contentModal').modal('hide'); + } }; - -function setupStatusButtons(){ - if (isMobile){ - $('#mobileClientStatus').show(); - $('#mobileCPUUsage').show(); - $('#mobileControllerStatusAlert').show(); - $('.navbar-brand').hide(); +function setupStatusButtons() { + if (window.isMobile) { + $('#mobileClientStatus').show(); + $('#mobileCPUUsage').show(); + $('#mobileControllerStatusAlert').show(); + $('.navbar-brand').hide(); } else { $('#mobileClientStatus').hide(); $('#mobileCPUUsage').hide(); @@ -246,11 +226,21 @@ function setupStatusButtons(){ } } -function pyInstallUpdateBadge(data){ - console.log("---##-") - console.log(data); - $('#helpBadge').html("1"); - $('#updateBadge').html("1"); - console.log("---##-") +function pyInstallUpdateBadge(data) { + console.log("---##-") + console.log(data); + $('#helpBadge').html("1"); + $('#updateBadge').html("1"); + console.log("---##-") } +export { + closeAlertModals, + closeContentModals, + closeModals, + processActivateModal, + processControllerStatus, + processHealthMessage, + pyInstallUpdateBadge, + setupStatusButtons, +}; \ No newline at end of file diff --git a/static/scripts/baseSocket.js b/static/scripts/baseSocket.js index 6beb1aa7..45b36998 100644 --- a/static/scripts/baseSocket.js +++ b/static/scripts/baseSocket.js @@ -1,58 +1,113 @@ -var socket; -var socketClientID; -var controllerMessages = []; -var hostAddress = "..." -var enable3D = true; -$(document).ready(function(){ - namespace = '/MaslowCNC'; // change to an empty string to use the global namespace +import { io } from "socket.io"; + +import { + closeAlertModals, + closeContentModals, + closeModals, + processActivateModal, + processControllerStatus, + processHealthMessage, + pyInstallUpdateBadge, + setupStatusButtons, +} from "./base.js"; +import { + boardDataUpdate, + clearAlarm, + processAlarm, + processControllerMessage, + processErrorValueMessage, + processGCodePositionMessage, + processHomePositionMessage, + processPositionMessage, + processRequestedSetting, + processStatusMessage, +} from "./frontpageControlsCommon.js"; +import { + initializeOpticalCalibration, + processPositionMessageOptical, + updateCalibrationImage, + updateOpticalCalibrationCurve, + updateOpticalCalibrationError, + updateOpticalCalibrationFindCenter, +} from "./opticalCalibration.js"; +import { updatePIDData } from "./pidTuning.js"; +import { processCalibrationMessage } from "./setSprockets.js"; +import { updatePorts } from "./settings.js"; +import { action, settingRequest, checkForGCodeUpdate, checkForBoardUpdate } from "./socketEmits.js"; +import { updateDirectories } from "./uploadGCode.js"; + +// Note: because this is a module, these variables are NOT global +let socketClientID; +let hostAddress = "..." + +$(() => { + // document.ready + const namespace = '/MaslowCNC'; // change to an empty string to use the global namespace // the socket.io documentation recommends sending an explicit package upon connection // this is specially important when using the global namespace - socket = io.connect('//' + document.domain + ':' + location.port + namespace, {'forceNew':true}); + const serverURL = `${location.protocol}//${location.hostname}:${location.port}${namespace}`; + console.log(serverURL); + + // Set up our globals + window.socket = io.connect(serverURL, {reconnect: true}); + window.enable3D = true; + window.controllerMessages = []; setListeners(); setupStatusButtons(); + + // Originally from frontpageControls.js + action("statusRequest", "cameraStatus"); + + // Originally From zAxis.js + settingRequest("Computed Settings", "unitsZ"); + settingRequest("Computed Settings", "distToMoveZ"); + + // opticalCalibration.js + initializeOpticalCalibration(); }); -function processHostAddress(data){ +function processHostAddress(data) { hostAddress = data["hostAddress"] - $("#clientStatus").text("Connected to "+hostAddress); + $("#clientStatus").text("Connected to " + hostAddress); } -function setListeners(){ +function setListeners() { console.log("setting Listeners"); - socket.on('connect', function(msg) { - socketClientID = socket.io.engine.id; - console.log("id="+socketClientID); - socket.emit('my event', {data: 'I\'m connected!'}); - $("#clientStatus").text("Connected: "+hostAddress); - $("#clientStatus").removeClass('alert-danger').addClass('alert-success'); - $("#mobileClientStatus").removeClass('alert-danger').addClass('alert-success'); - $("#mobileClientStatus svg.feather.feather-alert-circle").replaceWith(feather.icons["check-circle"].toSvg()); - settingRequest("Computed Settings","units"); - settingRequest("Computed Settings","distToMove"); - settingRequest("Computed Settings","homePosition"); - settingRequest("Computed Settings","unitsZ"); - settingRequest("Computed Settings","distToMoveZ"); - settingRequest("None","pauseButtonSetting"); - checkForGCodeUpdate(); - checkForBoardUpdate(); + + window.socket.on('after connect', function (msg) { + socketClientID = window.socket.id; + console.log("id=" + socketClientID); + window.socket.emit('my event', { data: 'I\'m connected!' }); + $("#clientStatus").text("Connected: " + hostAddress); + $("#clientStatus").removeClass('alert-danger').addClass('alert-success'); + $("#mobileClientStatus").removeClass('alert-danger').addClass('alert-success'); + $("#mobileClientStatus svg.feather.feather-alert-circle").replaceWith(feather.icons["check-circle"].toSvg()); + settingRequest("Computed Settings", "units"); + settingRequest("Computed Settings", "distToMove"); + settingRequest("Computed Settings", "homePosition"); + settingRequest("Computed Settings", "unitsZ"); + settingRequest("Computed Settings", "distToMoveZ"); + settingRequest("None", "pauseButtonSetting"); + checkForGCodeUpdate(); + checkForBoardUpdate(); }); - socket.on('disconnect', function(msg) { - $("#clientStatus").text("Not Connected"); - hostAddress = "Not Connected" - $("#clientStatus").removeClass('alert-success').addClass('alert-outline-danger'); - $("#mobileClientStatus").removeClass('alert-success').addClass('alert-danger'); - $("#mobileClientStatus svg.feather.feather-check-circle").replaceWith(feather.icons["alert-circle"].toSvg()); - $("#controllerStatus").removeClass('alert-success').removeClass('alert-danger').addClass('alert-secondary'); - $("#mobileControllerStatus").removeClass('alert-success').removeClass('alert-danger').addClass('alert-secondary'); + window.socket.on('disconnect', function (msg) { + $("#clientStatus").text("Not Connected"); + hostAddress = "Not Connected" + $("#clientStatus").removeClass('alert-success').addClass('alert-outline-danger'); + $("#mobileClientStatus").removeClass('alert-success').addClass('alert-danger'); + $("#mobileClientStatus svg.feather.feather-check-circle").replaceWith(feather.icons["alert-circle"].toSvg()); + $("#controllerStatus").removeClass('alert-success').removeClass('alert-danger').addClass('alert-secondary'); + $("#mobileControllerStatus").removeClass('alert-success').removeClass('alert-danger').addClass('alert-secondary'); }); - $("#notificationModal").on('hidden.bs.modal', function(e){ - var name = $('#notificationModal').data('name'); - console.log("closing modal:"+name); - socket.emit('modalClosed', {data:name}); + $("#notificationModal").on('hidden.bs.modal', function (e) { + var name = $('#notificationModal').data('name'); + console.log("closing modal:" + name); + window.socket.emit('modalClosed', { data: name }); }); /* @@ -62,244 +117,193 @@ function setListeners(){ $("#actionModal").on('hidden.bs.modal', function(e){ var name = $('#actionModal').data('name'); console.log("closing modal:"+name); - socket.emit('actionModalClosed', {data:name}); + window.socket.emit('actionModalClosed', {data:name}); }); */ - $("#alertModal").on('hidden.bs.modal', function(e){ - var name = $('#alertModal').data('name'); - console.log("closing modal:"+name); - socket.emit('alertModalClosed', {data:name}); + $("#alertModal").on('hidden.bs.modal', function (e) { + var name = $('#alertModal').data('name'); + console.log("closing modal:" + name); + window.socket.emit('alertModalClosed', { data: name }); }); - $("#contentModal").on('hidden.bs.modal', function(e){ - var name = $('#contentModal').data('name'); - console.log("closing modal:"+name); - //socket.emit('contentModalClosed', {data:name}); + $("#contentModal").on('hidden.bs.modal', function (e) { + var name = $('#contentModal').data('name'); + console.log("closing modal:" + name); + //window.socket.emit('contentModalClosed', {data:name}); }); - socket.on('message', function(msg){ - //console.log(msg); - //blink activity indicator - $("#cpuUsage").removeClass('alert-success').addClass('alertalert-warning'); - $("#mobileCPUUsage").removeClass('alert-success').addClass('alert-warning'); - setTimeout(function(){ - $("#cpuUsage").removeClass('alert-warning').addClass('alert-success'); - $("#mobileCPUUsage").removeClass('alert-warning').addClass('alert-success'); - },125); - //console.log(msg.command); - if (msg.dataFormat=='json') - data = JSON.parse(msg.data); - else - data = msg.data; - passValue = true - if ((data !=null) && (data.hasOwnProperty('client'))) - { - //console.log(data.client); - if ((data.client != socketClientID) && (data.client!="all")) - passValue = false; - } + window.socket.on('message', function (msg) { + //console.log(msg); + //blink activity indicator + $("#cpuUsage").removeClass('alert-success').addClass('alertalert-warning'); + $("#mobileCPUUsage").removeClass('alert-success').addClass('alert-warning'); + setTimeout(function () { + $("#cpuUsage").removeClass('alert-warning').addClass('alert-success'); + $("#mobileCPUUsage").removeClass('alert-warning').addClass('alert-success'); + }, 125); + //console.log(msg.command); + let data; + if (msg.dataFormat == 'json') + data = JSON.parse(msg.data); + else + data = msg.data; + let passValue = true; + if (data?.client) { + //console.log(data.client); + if ((data.client != socketClientID) && (data.client != "all")) + passValue = false; + } - if (passValue) - { - switch(msg.command) { - case 'statusMessage': - //completed - processStatusMessage(data); - break; - case 'healthMessage': - //completed - processHealthMessage(data); - break; - case 'hostAddress': - //completed - processHostAddress(data); - break; - case 'controllerMessage': - //completed - processControllerMessage(data); - break; - case 'connectionStatus': - //completed - processControllerStatus(data); - break; - case 'calibrationMessage': - processCalibrationMessage(data); - break; - case 'cameraMessage': - //completed - if (enable3D) - processCameraMessage(data); - break; - case 'positionMessage': - //completed - processPositionMessage(data) - if (typeof processPositionMessageOptical === "function") { - processPositionMessageOptical(data) - } - break; - case 'errorValueMessage': - processErrorValueMessage(data) - break; - case 'homePositionMessage': - //completed - processHomePositionMessage(data); - break; - case 'gcodePositionMessage': - //completed - processGCodePositionMessage(data); - break; - case 'activateModal': - //completed - //console.log(msg) - processActivateModal(data); - break; - case 'requestedSetting': - //completed - processRequestedSetting(data); - break; - case 'updateDirectories': - //completed - updateDirectories(data); - break; - case 'gcodeUpdate': - console.log("---gcodeUpdate received via socket---"); - if (enable3D) - gcodeUpdate(msg.message); - else - $("#fpCircle").hide(); - break; - case 'showFPSpinner': - //completed - showFPSpinner(msg.message); - break; - case 'gcodeUpdateCompressed': - if (enable3D) - gcodeUpdateCompressed(data); - else - $("#fpCircle").hide(); - break; - case 'boardDataUpdate': - boardDataUpdate(data); - break; - case 'boardCutDataUpdateCompressed': - if (enable3D) - boardCutDataUpdateCompressed(data); - else - $("#fpCircle").hide(); - break; - case 'updatePorts': - //completed - if (typeof updatePorts === "function") { - updatePorts(data); - } - break; - case 'closeModals': - //completed - closeModals(data); - break; - /* - todo: cleanup - Not used - case 'closeActionModals': - //completed - closeActionModals(data); - break; - */ - case 'closeAlertModals': - //completed - closeAlertModals(data); - break; - case 'closeContentModals': - //completed - closeContentModals(data); - break; - case 'updateOpticalCalibrationCurve': - //completed - updateOpticalCalibrationCurve(data); - break; - case 'updateOpticalCalibrationError': - //completed - updateOpticalCalibrationError(data); - break; - case 'updateOpticalCalibrationFindCenter': - //completed - updateOpticalCalibrationFindCenter(data); - break; - case 'updateCalibrationImage': - //completed - updateCalibrationImage(data); - break; - case 'updatePIDData': - //completed - updatePIDData(data); - break; - case 'alarm': - processAlarm(data); - break; - case 'clearAlarm': - clearAlarm(data); - break; - case 'pyinstallUpdate': - pyInstallUpdateBadge(data); - break; - default: - console.log("!!!!!!"); - console.log("uncaught action:"+msg.command); - console.log("!!!!!!"); - } + if (passValue) { + switch (msg.command) { + case 'statusMessage': + //completed + processStatusMessage(data); + break; + case 'healthMessage': + //completed + processHealthMessage(data); + break; + case 'hostAddress': + //completed + processHostAddress(data); + break; + case 'controllerMessage': + //completed + processControllerMessage(data); + break; + case 'connectionStatus': + //completed + processControllerStatus(data); + break; + case 'calibrationMessage': + // TODO: handle optical calibration not supporting this (and not being included) + processCalibrationMessage(data); + break; + case 'cameraMessage': + //completed + if (window.enable3D) { + window.frontpage3d.processCameraMessage(data); + } + break; + case 'positionMessage': + //completed + processPositionMessage(data) + if (typeof processPositionMessageOptical === "function") { + processPositionMessageOptical(data) + } + break; + case 'errorValueMessage': + processErrorValueMessage(data) + break; + case 'homePositionMessage': + //completed + processHomePositionMessage(data); + break; + case 'gcodePositionMessage': + //completed + processGCodePositionMessage(data); + break; + case 'activateModal': + //completed + processActivateModal(data); + break; + case 'requestedSetting': + //completed + processRequestedSetting(data); + break; + case 'updateDirectories': + //completed + updateDirectories(data); + break; + case 'gcodeUpdate': + console.log("---gcodeUpdate received via socket---"); + if (window.enable3D) + window.frontpage3d.gcodeUpdate(msg.message); + else + $("#fpCircle").hide(); + break; + case 'showFPSpinner': + //completed + window.showFPSpinner(msg.message); + break; + case 'gcodeUpdateCompressed': + if (window.enable3D) { + window.frontpage3d.gcodeUpdateCompressed(data); + } else { + $("#fpCircle").hide(); + } + break; + case 'boardDataUpdate': + boardDataUpdate(data); + break; + case 'boardCutDataUpdateCompressed': + if (window.enable3D) { + window.frontpage3d.boardCutDataUpdateCompressed(data); + } else { + $("#fpCircle").hide(); + } + break; + case 'updatePorts': + //completed + if (typeof updatePorts === "function") { + updatePorts(data); + } + break; + case 'closeModals': + //completed + closeModals(data); + break; + /* + todo: cleanup + Not used + case 'closeActionModals': + //completed + closeActionModals(data); + break; + */ + case 'closeAlertModals': + //completed + closeAlertModals(data); + break; + case 'closeContentModals': + //completed + closeContentModals(data); + break; + case 'updateOpticalCalibrationCurve': + //completed + updateOpticalCalibrationCurve(data); + break; + case 'updateOpticalCalibrationError': + //completed + updateOpticalCalibrationError(data); + break; + case 'updateOpticalCalibrationFindCenter': + //completed + updateOpticalCalibrationFindCenter(data); + break; + case 'updateCalibrationImage': + //completed + updateCalibrationImage(data); + break; + case 'updatePIDData': + //completed + updatePIDData(data); + break; + case 'alarm': + processAlarm(data); + break; + case 'clearAlarm': + clearAlarm(data); + break; + case 'pyinstallUpdate': + pyInstallUpdateBadge(data); + break; + default: + console.log("!!!!!!"); + console.log("uncaught action:" + msg.command); + console.log("!!!!!!"); } + } }); - -} - -function action(command, arg, arg1){ - if (arg==null) - arg = ""; - if (arg1==null) - arg1 = ""; - console.log("action="+command); - socket.emit('action',{data:{command:command,arg:arg, arg1:arg1}}); -} - -function move(direction){ - distToMove = $("#distToMove").val(); - console.log(distToMove) - socket.emit('move',{data:{direction:direction,distToMove:distToMove}}); -} - -function moveZ(direction){ - distToMoveZ = $("#distToMoveZ").val(); - console.log(distToMoveZ) - socket.emit('moveZ',{data:{direction:direction,distToMoveZ:distToMoveZ}}); -} - -function settingRequest(section,setting){ - console.log("requesting..") - socket.emit('settingRequest',{data:{section:section,setting:setting}}); -} - -function statusRequest(status){ - console.log("requesting status..") - socket.emit('statusRequest',{data:{status:status}}); -} - -function requestPage(page, args=""){ - console.log("requesting page..") - socket.emit('requestPage',{data:{page:page, isMobile:isMobile, args:args}}); -} - -function updateSetting(setting, value){ - socket.emit('updateSetting',{data:{setting:setting,value:value}}); -} - -function checkForGCodeUpdate(){ - socket.emit('checkForGCodeUpdate',{data:"Please"}); -} - -function checkForBoardUpdate(){ - socket.emit('checkForBoardUpdate',{data:"Please"}); -} - -function checkForHostAddress(){ - socket.emit('checkForHostAddress',{data:"Please"}); } diff --git a/static/scripts/bootstrap.bundle.min.js b/static/scripts/bootstrap.bundle.min.js deleted file mode 100644 index 72a46cf9..00000000 --- a/static/scripts/bootstrap.bundle.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.1.3 (https://getbootstrap.com/) - * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("jquery")):"function"==typeof define&&define.amd?define(["exports","jquery"],t):t(e.bootstrap={},e.jQuery)}(this,function(e,t){"use strict";function i(e,t){for(var n=0;nthis._items.length-1||e<0))if(this._isSliding)k(this._element).one(q.SLID,function(){return t.to(e)});else{if(n===e)return this.pause(),void this.cycle();var i=n=i.clientWidth&&n>=i.clientHeight}),u=0l[e]&&!i.escapeWithReference&&(n=Math.min(u[t],l[e]-("right"===e?u.width:u.height))),Ve({},t,n)}};return c.forEach(function(e){var t=-1!==["left","top"].indexOf(e)?"primary":"secondary";u=ze({},u,f[t](e))}),e.offsets.popper=u,e},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,n=t.popper,i=t.reference,r=e.placement.split("-")[0],o=Math.floor,s=-1!==["top","bottom"].indexOf(r),a=s?"right":"bottom",l=s?"left":"top",c=s?"width":"height";return n[a]o(i[a])&&(e.offsets.popper[l]=o(i[a])),e}},arrow:{order:500,enabled:!0,fn:function(e,t){var n;if(!pt(e.instance.modifiers,"arrow","keepTogether"))return e;var i=t.element;if("string"==typeof i){if(!(i=e.instance.popper.querySelector(i)))return e}else if(!e.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),e;var r=e.placement.split("-")[0],o=e.offsets,s=o.popper,a=o.reference,l=-1!==["left","right"].indexOf(r),c=l?"height":"width",u=l?"Top":"Left",f=u.toLowerCase(),h=l?"left":"top",d=l?"bottom":"right",p=nt(i)[c];a[d]-ps[d]&&(e.offsets.popper[f]+=a[f]+p-s[d]),e.offsets.popper=Ge(e.offsets.popper);var m=a[f]+a[c]/2-p/2,g=Pe(e.instance.popper),_=parseFloat(g["margin"+u],10),v=parseFloat(g["border"+u+"Width"],10),y=m-e.offsets.popper[f]-_-v;return y=Math.max(Math.min(s[c]-p,y),0),e.arrowElement=i,e.offsets.arrow=(Ve(n={},f,Math.round(y)),Ve(n,h,""),n),e},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(p,m){if(at(p.instance.modifiers,"inner"))return p;if(p.flipped&&p.placement===p.originalPlacement)return p;var g=$e(p.instance.popper,p.instance.reference,m.padding,m.boundariesElement,p.positionFixed),_=p.placement.split("-")[0],v=it(_),y=p.placement.split("-")[1]||"",E=[];switch(m.behavior){case vt:E=[_,v];break;case yt:E=_t(_);break;case Et:E=_t(_,!0);break;default:E=m.behavior}return E.forEach(function(e,t){if(_!==e||E.length===t+1)return p;_=p.placement.split("-")[0],v=it(_);var n,i=p.offsets.popper,r=p.offsets.reference,o=Math.floor,s="left"===_&&o(i.right)>o(r.left)||"right"===_&&o(i.left)o(r.top)||"bottom"===_&&o(i.top)o(g.right),c=o(i.top)o(g.bottom),f="left"===_&&a||"right"===_&&l||"top"===_&&c||"bottom"===_&&u,h=-1!==["top","bottom"].indexOf(_),d=!!m.flipVariations&&(h&&"start"===y&&a||h&&"end"===y&&l||!h&&"start"===y&&c||!h&&"end"===y&&u);(s||f||d)&&(p.flipped=!0,(s||f)&&(_=E[t+1]),d&&(y="end"===(n=y)?"start":"start"===n?"end":n),p.placement=_+(y?"-"+y:""),p.offsets.popper=ze({},p.offsets.popper,rt(p.instance.popper,p.offsets.reference,p.placement)),p=st(p.instance.modifiers,p,"flip"))}),p},behavior:"flip",padding:5,boundariesElement:"viewport"},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,n=t.split("-")[0],i=e.offsets,r=i.popper,o=i.reference,s=-1!==["left","right"].indexOf(n),a=-1===["top","left"].indexOf(n);return r[s?"left":"top"]=o[n]-(a?r[s?"width":"height"]:0),e.placement=it(t),e.offsets.popper=Ge(r),e}},hide:{order:800,enabled:!0,fn:function(e){if(!pt(e.instance.modifiers,"hide","preventOverflow"))return e;var t=e.offsets.reference,n=ot(e.instance.modifiers,function(e){return"preventOverflow"===e.name}).boundaries;if(t.bottomn.right||t.top>n.bottom||t.rightdocument.documentElement.clientHeight;!this._isBodyOverflowing&&e&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!e&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var e=document.body.getBoundingClientRect();this._isBodyOverflowing=e.left+e.right
',trigger:"hover focus",title:"",delay:0,html:!(An={AUTO:"auto",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left"}),selector:!(Dn={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)"}),placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},Nn="out",kn={HIDE:"hide"+wn,HIDDEN:"hidden"+wn,SHOW:(On="show")+wn,SHOWN:"shown"+wn,INSERTED:"inserted"+wn,CLICK:"click"+wn,FOCUSIN:"focusin"+wn,FOCUSOUT:"focusout"+wn,MOUSEENTER:"mouseenter"+wn,MOUSELEAVE:"mouseleave"+wn},xn="fade",Pn="show",Ln=".tooltip-inner",jn=".arrow",Hn="hover",Mn="focus",Fn="click",Wn="manual",Rn=function(){function i(e,t){if("undefined"==typeof Ct)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=e,this.config=this._getConfig(t),this.tip=null,this._setListeners()}var e=i.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(e){if(this._isEnabled)if(e){var t=this.constructor.DATA_KEY,n=yn(e.currentTarget).data(t);n||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),yn(e.currentTarget).data(t,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(yn(this.getTipElement()).hasClass(Pn))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),yn.removeData(this.element,this.constructor.DATA_KEY),yn(this.element).off(this.constructor.EVENT_KEY),yn(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&yn(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===yn(this.element).css("display"))throw new Error("Please use show on visible elements");var e=yn.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){yn(this.element).trigger(e);var n=yn.contains(this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!n)return;var i=this.getTipElement(),r=we.getUID(this.constructor.NAME);i.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&yn(i).addClass(xn);var o="function"==typeof this.config.placement?this.config.placement.call(this,i,this.element):this.config.placement,s=this._getAttachment(o);this.addAttachmentClass(s);var a=!1===this.config.container?document.body:yn(document).find(this.config.container);yn(i).data(this.constructor.DATA_KEY,this),yn.contains(this.element.ownerDocument.documentElement,this.tip)||yn(i).appendTo(a),yn(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new Ct(this.element,i,{placement:s,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:jn},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(e){e.originalPlacement!==e.placement&&t._handlePopperPlacementChange(e)},onUpdate:function(e){t._handlePopperPlacementChange(e)}}),yn(i).addClass(Pn),"ontouchstart"in document.documentElement&&yn(document.body).children().on("mouseover",null,yn.noop);var l=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,yn(t.element).trigger(t.constructor.Event.SHOWN),e===Nn&&t._leave(null,t)};if(yn(this.tip).hasClass(xn)){var c=we.getTransitionDurationFromElement(this.tip);yn(this.tip).one(we.TRANSITION_END,l).emulateTransitionEnd(c)}else l()}},e.hide=function(e){var t=this,n=this.getTipElement(),i=yn.Event(this.constructor.Event.HIDE),r=function(){t._hoverState!==On&&n.parentNode&&n.parentNode.removeChild(n),t._cleanTipClass(),t.element.removeAttribute("aria-describedby"),yn(t.element).trigger(t.constructor.Event.HIDDEN),null!==t._popper&&t._popper.destroy(),e&&e()};if(yn(this.element).trigger(i),!i.isDefaultPrevented()){if(yn(n).removeClass(Pn),"ontouchstart"in document.documentElement&&yn(document.body).children().off("mouseover",null,yn.noop),this._activeTrigger[Fn]=!1,this._activeTrigger[Mn]=!1,this._activeTrigger[Hn]=!1,yn(this.tip).hasClass(xn)){var o=we.getTransitionDurationFromElement(n);yn(n).one(we.TRANSITION_END,r).emulateTransitionEnd(o)}else r();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(e){yn(this.getTipElement()).addClass(Tn+"-"+e)},e.getTipElement=function(){return this.tip=this.tip||yn(this.config.template)[0],this.tip},e.setContent=function(){var e=this.getTipElement();this.setElementContent(yn(e.querySelectorAll(Ln)),this.getTitle()),yn(e).removeClass(xn+" "+Pn)},e.setElementContent=function(e,t){var n=this.config.html;"object"==typeof t&&(t.nodeType||t.jquery)?n?yn(t).parent().is(e)||e.empty().append(t):e.text(yn(t).text()):e[n?"html":"text"](t)},e.getTitle=function(){var e=this.element.getAttribute("data-original-title");return e||(e="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),e},e._getAttachment=function(e){return An[e.toUpperCase()]},e._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(e){if("click"===e)yn(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(e){return i.toggle(e)});else if(e!==Wn){var t=e===Hn?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=e===Hn?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;yn(i.element).on(t,i.config.selector,function(e){return i._enter(e)}).on(n,i.config.selector,function(e){return i._leave(e)})}yn(i.element).closest(".modal").on("hide.bs.modal",function(){return i.hide()})}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var e=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==e)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(e,t){var n=this.constructor.DATA_KEY;(t=t||yn(e.currentTarget).data(n))||(t=new this.constructor(e.currentTarget,this._getDelegateConfig()),yn(e.currentTarget).data(n,t)),e&&(t._activeTrigger["focusin"===e.type?Mn:Hn]=!0),yn(t.getTipElement()).hasClass(Pn)||t._hoverState===On?t._hoverState=On:(clearTimeout(t._timeout),t._hoverState=On,t.config.delay&&t.config.delay.show?t._timeout=setTimeout(function(){t._hoverState===On&&t.show()},t.config.delay.show):t.show())},e._leave=function(e,t){var n=this.constructor.DATA_KEY;(t=t||yn(e.currentTarget).data(n))||(t=new this.constructor(e.currentTarget,this._getDelegateConfig()),yn(e.currentTarget).data(n,t)),e&&(t._activeTrigger["focusout"===e.type?Mn:Hn]=!1),t._isWithActiveTrigger()||(clearTimeout(t._timeout),t._hoverState=Nn,t.config.delay&&t.config.delay.hide?t._timeout=setTimeout(function(){t._hoverState===Nn&&t.hide()},t.config.delay.hide):t.hide())},e._isWithActiveTrigger=function(){for(var e in this._activeTrigger)if(this._activeTrigger[e])return!0;return!1},e._getConfig=function(e){return"number"==typeof(e=l({},this.constructor.Default,yn(this.element).data(),"object"==typeof e&&e?e:{})).delay&&(e.delay={show:e.delay,hide:e.delay}),"number"==typeof e.title&&(e.title=e.title.toString()),"number"==typeof e.content&&(e.content=e.content.toString()),we.typeCheckConfig(En,e,this.constructor.DefaultType),e},e._getDelegateConfig=function(){var e={};if(this.config)for(var t in this.config)this.constructor.Default[t]!==this.config[t]&&(e[t]=this.config[t]);return e},e._cleanTipClass=function(){var e=yn(this.getTipElement()),t=e.attr("class").match(Sn);null!==t&&t.length&&e.removeClass(t.join(""))},e._handlePopperPlacementChange=function(e){var t=e.instance;this.tip=t.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(e.placement))},e._fixTransition=function(){var e=this.getTipElement(),t=this.config.animation;null===e.getAttribute("x-placement")&&(yn(e).removeClass(xn),this.config.animation=!1,this.hide(),this.show(),this.config.animation=t)},i._jQueryInterface=function(n){return this.each(function(){var e=yn(this).data(bn),t="object"==typeof n&&n;if((e||!/dispose|hide/.test(n))&&(e||(e=new i(this,t),yn(this).data(bn,e)),"string"==typeof n)){if("undefined"==typeof e[n])throw new TypeError('No method named "'+n+'"');e[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.1.3"}},{key:"Default",get:function(){return In}},{key:"NAME",get:function(){return En}},{key:"DATA_KEY",get:function(){return bn}},{key:"Event",get:function(){return kn}},{key:"EVENT_KEY",get:function(){return wn}},{key:"DefaultType",get:function(){return Dn}}]),i}(),yn.fn[En]=Rn._jQueryInterface,yn.fn[En].Constructor=Rn,yn.fn[En].noConflict=function(){return yn.fn[En]=Cn,Rn._jQueryInterface},Rn),Qi=(Bn="popover",Kn="."+(qn="bs.popover"),Qn=(Un=t).fn[Bn],Yn="bs-popover",Vn=new RegExp("(^|\\s)"+Yn+"\\S+","g"),zn=l({},Ki.Default,{placement:"right",trigger:"click",content:"",template:''}),Gn=l({},Ki.DefaultType,{content:"(string|element|function)"}),Jn="fade",Xn=".popover-header",$n=".popover-body",ei={HIDE:"hide"+Kn,HIDDEN:"hidden"+Kn,SHOW:(Zn="show")+Kn,SHOWN:"shown"+Kn,INSERTED:"inserted"+Kn,CLICK:"click"+Kn,FOCUSIN:"focusin"+Kn,FOCUSOUT:"focusout"+Kn,MOUSEENTER:"mouseenter"+Kn,MOUSELEAVE:"mouseleave"+Kn},ti=function(e){var t,n;function i(){return e.apply(this,arguments)||this}n=e,(t=i).prototype=Object.create(n.prototype),(t.prototype.constructor=t).__proto__=n;var r=i.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(e){Un(this.getTipElement()).addClass(Yn+"-"+e)},r.getTipElement=function(){return this.tip=this.tip||Un(this.config.template)[0],this.tip},r.setContent=function(){var e=Un(this.getTipElement());this.setElementContent(e.find(Xn),this.getTitle());var t=this._getContent();"function"==typeof t&&(t=t.call(this.element)),this.setElementContent(e.find($n),t),e.removeClass(Jn+" "+Zn)},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var e=Un(this.getTipElement()),t=e.attr("class").match(Vn);null!==t&&0=this._offsets[r]&&("undefined"==typeof this._offsets[r+1]||e high) + return high; + return value; + } -function processError3d(data){ - if ( !data.computedEnabled ) - { - if (isComputedEnabled){ - scene.remove(computedSled); - isComputedEnabled = false; + onMouseMove(event) { + if (!window.isMobile) { + this.pos = this.cursorPosition(event); + this.cursor.position.set(this.pos.x, this.pos.y, this.pos.z); + const linePosX = this.confine(this.pos.x, -48, 48); + const linePosY = this.confine(this.pos.y, -24, 24); + + let positions = this.cursorVLine.geometry.attributes.position.array; + positions[0] = linePosX; + positions[1] = 24; + positions[2] = -0.001; + positions[3] = linePosX; + positions[4] = -24; + positions[5] = -0.001; + this.cursorVLine.geometry.attributes.position.needsUpdate = true; + + positions = this.cursorHLine.geometry.attributes.position.array; + positions[0] = 48; + positions[1] = linePosY; + positions[2] = -0.001; + positions[3] = -48; + positions[4] = linePosY; + positions[5] = -0.001; + this.cursorHLine.geometry.attributes.position.needsUpdate = true; + + if ($("#units").text() == "MM") { + this.pos.x *= 25.4 + this.pos.y *= 25.4 + } + $("#cursorPosition").text("X: " + this.pos.x.toFixed(2) + ", Y: " + this.pos.y.toFixed(2)); } - return; - } - else - { - var x = data.computedX/25.4; - var y = data.computedY/25.4; - if ($("#units").text()==""){ - x /= 25.4 - y /= 25.4 - } - computedSled.position.setComponent(0,x); - computedSled.position.setComponent(1,y); - if (!isComputedEnabled){ - scene.add(computedSled); - //scene.add(cutTrailGroup); - isComputedEnabled = true; - } - } -} + } + board3DDataUpdate(data) { + console.log("updating board data"); + this.boardOutlineGeometry.dispose(); + this.boardOutlineGeometry = new THREE.BoxGeometry(data.width, data.height, data.thickness); + this.boardOutlineFill.geometry = this.boardOutlineGeometry; + this.boardEdgesGeometry = new THREE.EdgesGeometry(this.boardOutlineGeometry) + this.boardOutlineOutline.geometry = this.boardEdgesGeometry; + + this.boardOutlineFill.geometry.needsUpdate = true; + this.boardOutlineOutline.geometry.needsUpdate = true; + this.boardGroup.position.set(data.centerX, data.centerY, data.thickness / -2.0); + } -function gcodeUpdateCompressed(data){ - console.log("updating gcode compressed"); - if (gcode.children.length!=0) { - for (var i = gcode.children.length -1; i>=0; i--){ - gcode.remove(gcode.children[i]); + positionUpdate(x, y, z) { + if ($("#units").text() == "MM") { + x /= 25.4 + y /= 25.4 + z /= 25.4 } + this.sled.position.set(x, y, z); + this.computedSled.position.setComponent(2, z - 0.01); + + //console.log("x="+x+", y="+y+", z="+z) } - if (textLabels.children.length!=0) { - for (var i = textLabels.children.length -1; i>=0; i--){ - textLabels.remove(textLabels.children[i]); + + homePositionUpdate(x, y) { + if ($("#units").text() == "MM") { + x /= 25.4 + y /= 25.4 } + this.home.position.set(x, y, 0); + //shift any gcode + this.homeX = x; + this.homeY = y; + this.gcode.position.set(x, y, 0); } - var gcodeLineSegments = new THREE.Geometry(); - var gcodeDashedLineSegments = new THREE.Geometry(); - - if ((data!=null) && (data!="")){ - var uncompressed = pako.inflate(data); - var _str = ab2str(uncompressed); - var data = JSON.parse(_str) - console.log(data) - var pX, pY, pZ = -99999.9 - var gcodeDashed; - var gcodeUndashed; - data.forEach(function(line) { - if (line.type=='line'){ - //console.log("Line length="+line.points.length+", dashed="+line.dashed); - if (line.dashed==true) { - var gcodeDashedLineSegments = new THREE.Geometry(); - line.points.forEach(function(point) { - gcodeDashedLineSegments.vertices.push(new THREE.Vector3(point[0], point[1], point[2])); - }) - gcodeDashed = new THREE.Line(gcodeDashedLineSegments, greenLineDashedMaterial) - gcodeDashed.computeLineDistances(); - gcode.add(gcodeDashed); - } else { - var gcodeLineSegments = new THREE.Geometry(); - line.points.forEach(function(point) { - gcodeLineSegments.vertices.push(new THREE.Vector3(point[0], point[1], point[2])); - }) - gcodeUndashed = new THREE.Line(gcodeLineSegments, blueLineMaterial) - gcode.add(gcodeUndashed); + gcodePositionUpdate(x, y, z) { + if ($("#units").text() == "MM") { + x /= 25.4 + y /= 25.4 + z /= 25.4 + } + this.gcodePos.position.set(x + homeX, y + homeY, z); + //console.log("x="+x+", y="+y) + } - } + processError3d(data) { + if (!data.computedEnabled) { + if (this.isComputedEnabled) { + this.scene.remove(this.computedSled); + this.isComputedEnabled = false; + } + return; + } else { + var x = data.computedX / 25.4; + var y = data.computedY / 25.4; + if ($("#units").text() == "") { + x /= 25.4 + y /= 25.4 + } + this.computedSled.position.setComponent(0, x); + this.computedSled.position.setComponent(1, y); + if (!this.isComputedEnabled) { + this.scene.add(this.computedSled); + //this.scene.add(this.cutTrailGroup); + this.isComputedEnabled = true; } - else - { - if ( (line.command == "SpindleOnCW") || (line.command=="SpindlenOnCCW") || (line.command=="SpindleOff") ) //(line.points[1][0]>=3) && (line.points[1][0]<=5) ) - { + } + } + + ab2str(buf) { + var bufView = new Uint16Array(buf); + var unis = ""; + for (var i = 0; i < bufView.length; i++) { + unis = unis + String.fromCharCode(bufView[i]); + } + return unis; + } + + gcodeUpdateCompressed(data) { + console.log("updating gcode compressed"); + if (this.gcode.children.length != 0) { + for (let i = this.gcode.children.length - 1; i >= 0; i--) { + this.gcode.remove(this.gcode.children[i]); + } + } + if (this.textLabels.children.length != 0) { + for (var i = this.textLabels.children.length - 1; i >= 0; i--) { + this.textLabels.remove(this.textLabels.children[i]); + } + } + + const greenLineDashedMaterial = new THREE.LineDashedMaterial({ color: 0x00aa00, dashSize: .1, gapSize: .1 }); + + if ((data != null) && (data != "")) { + var uncompressed = pako.inflate(data); + var _str = ab2str(uncompressed); + var data = JSON.parse(_str) + console.log(data) + + let gcodeDashed; + let gcodeUndashed; + data.forEach((line) => { + if (line.type == 'line') { + //console.log("Line length="+line.points.length+", dashed="+line.dashed); + if (line.dashed == true) { + const gcodeDashedLinePoints = []; + line.points.forEach(function (point) { + gcodeDashedLinePoints.push(new THREE.Vector3(point[0], point[1], point[2])); + }) + const gcodeDashedLineSegments = new THREE.BufferGeometry().setFromPoints( gcodeDashedLinePoints ); + gcodeDashed = new THREE.Line(gcodeDashedLineSegments, greenLineDashedMaterial) + gcodeDashed.computeLineDistances(); + this.gcode.add(gcodeDashed); + } else { + var gcodeLinePoints = []; + line.points.forEach(function (point) { + gcodeLinePoints.push(new THREE.Vector3(point[0], point[1], point[2])); + }) + var gcodeLineSegments = new THREE.BufferGeometry().setFromPoints( gcodeLinePoints ); + gcodeUndashed = new THREE.Line(gcodeLineSegments, blueLineMaterial) + this.gcode.add(gcodeUndashed); + } + } else { + if (["SpindleOnCW", "SpindleOnCCW", "SpindleOff"].includes(line.command)) { + //(line.points[1][0]>=3) && (line.points[1][0]<=5) ) //spindle - var gcodeCircleGeometry = new THREE.CircleGeometry(2.25/32,16); + var gcodeCircleGeometry = new THREE.CircleGeometry(2.25 / 32, 16); var gcodeCircleEdges = new THREE.EdgesGeometry(gcodeCircleGeometry) var circleMaterial = redLineMaterial; - var gcodeCircle = new THREE.LineSegments(gcodeCircleEdges,circleMaterial); + var gcodeCircle = new THREE.LineSegments(gcodeCircleEdges, circleMaterial); gcodeCircle.position.set(line.points[0][0], line.points[0][1], line.points[0][2]); - gcode.add(gcodeCircle); - var gcodeLineSegments = new THREE.Geometry(); + this.gcode.add(gcodeCircle); var xFactor = 3.25; var yFactor = 3.25; - if (line.command == "SpindleOff") //(line.points[1][0]==5) - { - xFactor = .707107*2.25; - yFactor = .707107*2.25; + if (line.command == "SpindleOff") { + //(line.points[1][0]==5) + xFactor = .707107 * 2.25; + yFactor = .707107 * 2.25; } - if (line.command == "SpindleOnCCW") // (line.points[1][0]==4) - { - xFactor = 3.25; - yFactor = 3.25; - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0]+xFactor/32, line.points[0][1]+yFactor/32, line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0]-xFactor/32, line.points[0][1]+yFactor/32, line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0]+xFactor/32, line.points[0][1]-yFactor/32, line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0]-xFactor/32, line.points[0][1]-yFactor/32, line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); - } - else - { - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0]+xFactor/32, line.points[0][1]+yFactor/32, line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0]+xFactor/32, line.points[0][1]-yFactor/32, line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0]-xFactor/32, line.points[0][1]+yFactor/32, line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0]-xFactor/32, line.points[0][1]-yFactor/32, line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); + + const gcodeLinePoints = []; + if (line.command == "SpindleOnCCW") { + // (line.points[1][0]==4) + xFactor = 3.25; + yFactor = 3.25; + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0] + xFactor / 32, line.points[0][1] + yFactor / 32, line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0] - xFactor / 32, line.points[0][1] + yFactor / 32, line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0] + xFactor / 32, line.points[0][1] - yFactor / 32, line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0] - xFactor / 32, line.points[0][1] - yFactor / 32, line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); + } else { + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0] + xFactor / 32, line.points[0][1] + yFactor / 32, line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0] + xFactor / 32, line.points[0][1] - yFactor / 32, line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0] - xFactor / 32, line.points[0][1] + yFactor / 32, line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0] - xFactor / 32, line.points[0][1] - yFactor / 32, line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); } + const gcodeLineSegments = new THREE.BufferGeometry().setFromPoints( gcodeLinePoints ); gcodeUndashed = new THREE.Line(gcodeLineSegments, redLineMaterial) - gcode.add(gcodeUndashed); - if (line.command != "SpindleOff") - { - text = new SpriteText('S'+line.points[1][1].toString(),.1, 'red'); - text.position.x = line.points[0][0]; - text.position.y = line.points[0][1]; - text.position.z = line.points[0][2]; - textLabels.add(text); + this.gcode.add(gcodeUndashed); + if (line.command != "SpindleOff") { + text = new SpriteText('S' + line.points[1][1].toString(), .1, 'red'); + text.position.x = line.points[0][0]; + text.position.y = line.points[0][1]; + text.position.z = line.points[0][2]; + textLabels.add(text); } - } - else if (line.command == "ToolChange") - { - var gcodeCircleGeometry = new THREE.CircleGeometry(2.25/32,16); + } else if (line.command == "ToolChange") { + var gcodeCircleGeometry = new THREE.CircleGeometry(2.25 / 32, 16); var gcodeCircleEdges = new THREE.EdgesGeometry(gcodeCircleGeometry) circleMaterial = redLineMaterial; - text = new SpriteText('T'+line.points[1][1].toString(),.1, 'red'); + text = new SpriteText('T' + line.points[1][1].toString(), .1, 'red'); text.position.x = line.points[0][0]; text.position.y = line.points[0][1]; text.position.z = line.points[0][2]; textLabels.add(text); - var gcodeCircle = new THREE.LineSegments(gcodeCircleEdges,circleMaterial); + var gcodeCircle = new THREE.LineSegments(gcodeCircleEdges, circleMaterial); gcodeCircle.position.set(line.points[0][0], line.points[0][1], line.points[0][2]); - gcode.add(gcodeCircle); - } - else - { - var gcodeCircleGeometry = new THREE.CircleGeometry(line.points[1][0]/32,16); + this.gcode.add(gcodeCircle); + } else { + var gcodeCircleGeometry = new THREE.CircleGeometry(line.points[1][0] / 32, 16); var gcodeCircleEdges = new THREE.EdgesGeometry(gcodeCircleGeometry) var circleMaterial = greenLineMaterial; - var gcodeCircle = new THREE.LineSegments(gcodeCircleEdges,circleMaterial); + var gcodeCircle = new THREE.LineSegments(gcodeCircleEdges, circleMaterial); gcodeCircle.position.set(line.points[0][0], line.points[0][1], line.points[0][2]); - gcode.add(gcodeCircle); + this.gcode.add(gcodeCircle); + } + + const gcodeLinePoints = []; + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); + gcodeLinePoints.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[1][2])); + const gcodeLineSegments = new THREE.BufferGeometry().setFromPoints( gcodeLinePoints ); + gcodeUndashed = new THREE.Line(gcodeLineSegments, blueLineMaterial) + this.gcode.add(gcodeUndashed); } - - var gcodeLineSegments = new THREE.Geometry(); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[0][2])); - gcodeLineSegments.vertices.push(new THREE.Vector3(line.points[0][0], line.points[0][1], line.points[1][2])); - gcodeUndashed = new THREE.Line(gcodeLineSegments, blueLineMaterial) - gcode.add(gcodeUndashed); - + }); + this.scene.add(this.gcode); + if (this.showLabels) { + this.scene.add(this.textLabels); } - }); - scene.add(gcode); - if (showLabels) - scene.add(textLabels); - } - else{ - scene.remove(gcode); - scene.remove(textLabels); - } - $("#fpCircle").hide(); - -} - -function toggleLabels() -{ - if (showLabels) - { - scene.remove(textLabels); - $("#labelsID").removeClass('btn-primary').addClass('btn-secondary'); - } - else - { - scene.add(textLabels); - $("#labelsID").removeClass('btn-secondary').addClass('btn-primary'); + } else { + this.scene.remove(this.gcode); + this.scene.remove(this.textLabels); } - showLabels = !showLabels; -} + $("#fpCircle").hide(); + } -function ab2str(buf) { - var bufView = new Uint16Array(buf); - var unis ="" - for (var i = 0; i < bufView.length; i++) { - unis=unis+String.fromCharCode(bufView[i]); + toggleLabels() { + if (this.showLabels) { + this.scene.remove(this.textLabels); + $("#labelsID").removeClass('btn-primary').addClass('btn-secondary'); } - return unis -} - -function showFPSpinner(msg){ - $("#fpCircle").show(); -} - - -function toggle3DView() -{ - console.log("toggling"); - if (view3D){ - controlsO.enableRotate = false; - controlsO.mouseButtons = { - LEFT: THREE.MOUSE.RIGHT, - MIDDLE: THREE.MOUSE.MIDDLE, - RIGHT: THREE.MOUSE.LEFT - } - controlsP.enableRotate = false; - controlsP.mouseButtons = { - LEFT: THREE.MOUSE.RIGHT, - MIDDLE: THREE.MOUSE.MIDDLE, - RIGHT: THREE.MOUSE.LEFT - } - - view3D=false; - if (isMobile) - { - $("#mobilebutton3D").removeClass('btn-primary').addClass('btn-secondary'); - } - else - { - $("#button3D").removeClass('btn-primary').addClass('btn-secondary'); - } - console.log("toggled off"); - } else { - controlsO.enableRotate = true; - controlsO.mouseButtons = { - LEFT: THREE.MOUSE.RIGHT, - MIDDLE: THREE.MOUSE.MIDDLE, - RIGHT: THREE.MOUSE.LEFT - } - controlsP.enableRotate = true; - controlsP.mouseButtons = { - LEFT: THREE.MOUSE.RIGHT, - MIDDLE: THREE.MOUSE.MIDDLE, - RIGHT: THREE.MOUSE.LEFT - } - - view3D=true; - if (isMobile) - { - $("#mobilebutton3D").removeClass('btn-secondary').addClass('btn-primary'); - } - else - { - $("#button3D").removeClass('btn-secondary').addClass('btn-primary'); - } - console.log("toggled on"); + else { + this.scene.add(this.textLabels); + $("#labelsID").removeClass('btn-secondary').addClass('btn-primary'); } - controlsO.update(); - controlsP.update(); -} + this.showLabels = !this.showLabels; + } -function resetView(){ - controlsO.reset(); - controlsP.reset(); -} + resetView() { + this.controlsO.reset(); + this.controlsP.reset(); + } -function cursorPosition(){ - var rect = renderer.domElement.getBoundingClientRect(); - var vec = new THREE.Vector3(); // create once and reuse - var pos = new THREE.Vector3(); // create once and reuse - vec.set( - ( ( event.clientX - rect.left ) / ( rect.right - rect.left ) ) * 2 - 1, - - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1, - 0.5 ); - - if (cameraPerspective == 0) - { - vec.unproject( cameraO ); - vec.sub( cameraO.position ).normalize(); - var distance = - cameraO.position.z / vec.z; - pos.copy( cameraO.position ).add( vec.multiplyScalar( distance ) ); - } - else - { - vec.unproject( cameraP ); - vec.sub( cameraP.position ).normalize(); - var distance = - cameraP.position.z / vec.z; - pos.copy( cameraP.position ).add( vec.multiplyScalar( distance ) ); + processCameraMessage(data) { + if (!["cameraImageUpdated", "updateCamera"].includes(data.command)) { + return; } - //console.log(pos); - return(pos); -} -function processCameraMessage(data){ - if(data.command=="cameraImageUpdated"){ - var newImg = new Image(); - if (imageShowing==1) - { - newImg.onload = function() { - document.getElementById("cameraImage2").setAttribute('src',this.src); - if (isMobile){ - document.getElementById("mobileCameraDiv2").style.zIndex = "95"; - document.getElementById("mobileCameraDiv1").style.zIndex = "94"; - } else { - document.getElementById("cameraDiv2").style.zIndex = "95"; - document.getElementById("cameraDiv1").style.zIndex = "94"; - } - imageShowing = 2 - } + if (data.command == "cameraImageUpdated") { + var newImg = new Image(); + if (imageShowing == 1) { + newImg.onload = function () { + document.getElementById("cameraImage2").setAttribute('src', this.src); + if (window.isMobile) { + document.getElementById("mobileCameraDiv2").style.zIndex = "95"; + document.getElementById("mobileCameraDiv1").style.zIndex = "94"; + } else { + document.getElementById("cameraDiv2").style.zIndex = "95"; + document.getElementById("cameraDiv1").style.zIndex = "94"; + } + imageShowing = 2; } - else - { - newImg.onload = function() { - document.getElementById("cameraImage1").setAttribute('src',this.src); - if (isMobile){ - document.getElementById("mobileCameraDiv1").style.zIndex = "95"; - document.getElementById("mobileCameraDiv2").style.zIndex = "94"; - } else { - document.getElementById("cameraDiv1").style.zIndex = "95"; - document.getElementById("cameraDiv2").style.zIndex = "94"; - } - imageShowing = 1 - } + } else { + newImg.onload = function () { + document.getElementById("cameraImage1").setAttribute('src', this.src); + if (window.isMobile) { + document.getElementById("mobileCameraDiv1").style.zIndex = "95"; + document.getElementById("mobileCameraDiv2").style.zIndex = "94"; + } else { + document.getElementById("cameraDiv1").style.zIndex = "95"; + document.getElementById("cameraDiv2").style.zIndex = "94"; + } + imageShowing = 1; } - newImg.setAttribute('src', 'data:image/png;base64,'+data.data) - + } + newImg.setAttribute('src', 'data:image/png;base64,' + data.data); + return; } - if(data.command=="updateCamera") - { - if (data.data=="on"){ - $("#videoStatus svg.feather.feather-video-off").replaceWith(feather.icons.video.toSvg()); - feather.replace(); - console.log("video on"); - document.getElementById("cameraImage1").style.display = "block" - document.getElementById("cameraImage2").style.display = "block" - if (isMobile) - document.getElementById("mobileCameraArea").style.display = "block" - } - - if (data.data=="off"){ - $("#videoStatus svg.feather.feather-video").replaceWith(feather.icons["video-off"].toSvg()); - feather.replace(); - console.log("video off") - document.getElementById("cameraImage1").style.display = "none"; - document.getElementById("cameraImage2").style.display = "none" - if (isMobile) - document.getElementById("mobileCameraArea").style.display = "none" - } + // data.command == "updateCamera" + if (data.data == "on") { + $("#videoStatus svg.feather.feather-video-off").replaceWith(feather.icons.video.toSvg()); + feather.replace(); + console.log("video on"); + document.getElementById("cameraImage1").style.display = "block"; + document.getElementById("cameraImage2").style.display = "block"; + if (window.isMobile) { + document.getElementById("mobileCameraArea").style.display = "block"; + } + return; } -} -document.onmousemove = function(event){ - if (!isMobile) - { - pos = cursorPosition(); - cursor.position.set(pos.x,pos.y,pos.z); - var linePosX = confine(pos.x,-48, 48); - var linePosY = confine(pos.y,-24, 24); - - var positions = cursorVLine.geometry.attributes.position.array; - positions[0]=linePosX; - positions[1]=24; - positions[2]=-0.001; - positions[3]=linePosX; - positions[4]=-24; - positions[5]=-0.001; - cursorVLine.geometry.attributes.position.needsUpdate=true; - - positions = cursorHLine.geometry.attributes.position.array; - positions[0]=48; - positions[1]=linePosY; - positions[2]=-0.001; - positions[3]=-48; - positions[4]=linePosY; - positions[5]=-0.001; - cursorHLine.geometry.attributes.position.needsUpdate=true; - - - if ($("#units").text()=="MM"){ - pos.x *= 25.4 - pos.y *= 25.4 - } - $("#cursorPosition").text("X: "+pos.x.toFixed(2)+", Y: "+pos.y.toFixed(2)); + // data.data == "off" + $("#videoStatus svg.feather.feather-video").replaceWith(feather.icons["video-off"].toSvg()); + feather.replace(); + console.log("video off"); + document.getElementById("cameraImage1").style.display = "none"; + document.getElementById("cameraImage2").style.display = "none"; + if (window.isMobile){ + document.getElementById("mobileCameraArea").style.display = "none"; } -} - -function confine(value, low, high) -{ - if (valuehigh) - return high; - return value; -} - -function board3DDataUpdate(data){ - console.log("updating board data"); - boardOutlineGeometry.dispose(); - boardOutlineGeometry = new THREE.BoxBufferGeometry(boardWidth,boardHeight,boardThickness); - boardOutlineFill.geometry = boardOutlineGeometry; - boardEdgesGeometry = new THREE.EdgesGeometry( boardOutlineGeometry ) - boardOutlineOutline.geometry = boardEdgesGeometry; - - boardOutlineFill.geometry.needsUpdate=true; - boardOutlineOutline.geometry.needsUpdate=true; - boardGroup.position.set(boardCenterX,boardCenterY,boardThickness/-2.0); - -} + } -function boardCutDataUpdateCompressed(data){ - console.log("updating board cut data compressed"); - if (cutSquareGroup.children.length!=0) { - for (var i = cutSquareGroup.children.length -1; i>=0; i--){ - cutSquareGroup.remove(cutSquareGroup.children[i]); + boardCutDataUpdateCompressed(data) { + console.log("updating board cut data compressed"); + if (this.cutSquareGroup.children.length != 0) { + for (var i = this.cutSquareGroup.children.length - 1; i >= 0; i--) { + this.cutSquareGroup.remove(this.cutSquareGroup.children[i]); + } } - } - if (data!=null){ - //var cutSquareMaterial = new THREE.MeshBasicMaterial( {color:0xffff00, side: THREE.DoubleSide}); - var cutSquareMaterial = new THREE.MeshBasicMaterial( {color:0xff6666}); - var noncutSquareMaterial = new THREE.MeshBasicMaterial( {color:0x333333}); - var uncompressed = pako.inflate(data); - var _str = ab2str(uncompressed); - var data = JSON.parse(_str) - - var pointsX = Math.ceil(boardWidth) - var pointsY = Math.ceil(boardHeight) - console.log("boardWidth="+boardWidth) - console.log("boardHeight="+boardHeight) - console.log("boardCenterY="+boardCenterY) - var offsetX = pointsX / 2 - var offsetY = pointsY / 2 - - for (var x =0; x { + // document.ready + window.frontpage3d = new Frontpage3d(); + window.frontpage3d.initAll(); + // Note that this is a call to a static method + window.requestAnimationFrame(Frontpage3d.animate); + $("#workarea").contextmenu(() => { + if (!window.frontpage3d.view3D) { + // cursorPosition expects an event + window.frontpage3d.pos = window.frontpage3d.cursorPosition({clientX: 0, clientY: 0}); + + let x = window.frontpage3d.pos.x; + x = x.toFixed(4); + window.frontpage3d.pos.x = x; + + let y = window.frontpage3d.pos.y; + y = y.toFixed(4); + window.frontpage3d.pos.y = y; + + requestPage("screenAction", window.frontpage3d.pos); } - else { - $("#buttonPO").text("Persp"); - $("#mobilebuttonPO").text("Persp"); - renderer.render(scene, cameraP) - } -} + }); + window.addEventListener("resize", Frontpage3d.onWindowResize, false); + document.onmousemove = (event) => { + window.frontpage3d.onMouseMove(event); + } +}); +/** You spin me right round, baby right round, like a record player, right round, round, round */ +window.showFPSpinner = (msg) => { + $("#fpCircle").show(); +} diff --git a/static/scripts/frontpage3dcontrols.js b/static/scripts/frontpage3dcontrols.js index d55cf71b..0820c1c9 100644 --- a/static/scripts/frontpage3dcontrols.js +++ b/static/scripts/frontpage3dcontrols.js @@ -1,154 +1,22 @@ - -function unitSwitch(){ - if ( $("#units").text()=="MM") { - distToMove = (parseFloat($("#distToMove").val())/25.4).toFixed(3) - updateSetting('toInches',distToMove); - } else { - distToMove = (parseFloat($("#distToMove").val())*25.4).toFixed(3) - updateSetting('toMM',distToMove); - } -} - -$(document).ready(function(){ - //settingRequest("Computed Settings","units"); - //settingRequest("Computed Settings","distToMove"); - //settingRequest("Computed Settings","homePosition"); - var controllerMessage = document.getElementById('controllerMessage'); - controllerMessage.scrollTop = controllerMessage.scrollHeight; +// This is used only in frontpage3d_mobilecontrols.html +import "jquery"; + +$(() => { + // document.ready + //settingRequest("Computed Settings","units"); + //settingRequest("Computed Settings","distToMove"); + //settingRequest("Computed Settings","homePosition"); + var controllerMessage = document.getElementById('controllerMessage'); + controllerMessage.scrollTop = controllerMessage.scrollHeight; + + window.isFrontpage3d_mobilecontrols = true; }); -function pauseRun(){ - if ($("#pauseButton").text()=="Pause"){ - action('pauseRun'); - } - else { - action('resumeRun'); - } -} - -function processRequestedSetting(data){ - if (data.setting=="pauseButtonSetting"){ - if(data.value=="Resume") - $('#pauseButton').removeClass('btn-warning').addClass('btn-info'); - else - $('#pauseButton').removeClass('btn-info').addClass('btn-warning'); - $("#pauseButton").text(data.value); - } - if (data.setting=="units"){ - console.log("requestedSetting:"+data.value); - $("#units").text(data.value) - } - if (data.setting=="distToMove"){ - console.log("requestedSetting for distToMove:"+data.value); - $("#distToMove").val(data.value) - } - if ((data.setting=="unitsZ") || (data.setting=="distToMoveZ")){ - if (typeof processZAxisRequestedSetting === "function") { - processZAxisRequestedSetting(data) - } - } -} - -function processPositionMessage(data){ - $('#positionMessage').html('X:'+parseFloat(data.xval).toFixed(2)+' Y:'+parseFloat(data.yval).toFixed(2)+' Z:'+parseFloat(data.zval).toFixed(2)); - $('#percentComplete').html(data.pcom) - $('#machineState').html(data.state) -} - -function processErrorValueMessage(data){ - $('#leftError').css('width', data.leftError*100+'%').attr('aria-valuenow', data.leftError*100); - $('#rightError').css('width', data.rightError*100+'%').attr('aria-valuenow', data.rightError*100); -} - -function processHomePositionMessage(data){ - $('#homePositionMessage').html('X:'+parseFloat(data.xval).toFixed(2)+' Y:'+parseFloat(data.yval).toFixed(2)); -} - -function processGCodePositionMessage(data){ - $('#gcodePositionMessage').html('X:'+parseFloat(data.xval).toFixed(2)+' Y:'+parseFloat(data.yval).toFixed(2)); - $('#gcodeLine').html(data.gcodeLine); - $('#gcodeLineIndex').val(data.gcodeLineIndex+1) -} - -function gcodeUpdate(msg){ +function unsupported() { console.log("Unsupported"); } -function boardDataUpdate(msg){ +function cursorPosition() { console.log("Unsupported"); } -function gcodeUpdateCompressed(data){ - console.log("Unsupported"); - $("#fpCircle").hide(); -} - -function boardCutDataUpdateCompressed(data){ - console.log("Unsupported"); - $("#fpCircle").hide(); -} - - -function showFPSpinner(msg){ - $("#fpCircle").show(); -} - -function toggle3DView() -{ - console.log("Unsupported"); -} - -function resetView(){ - console.log("Unsupported"); -} - -function cursorPosition(){ - console.log("Unsupported"); -} - -function processCameraMessage(data){ - console.log("Unsupported"); -} - -function processControllerMessage(data){ - if (controllerMessages.length >100) - controllerMessages.shift(); - controllerMessages.push(data); - $('#controllerMessage').html(''); - controllerMessages.forEach(function(message){ - $('#controllerMessage').append(message+"
"); - }); - $('#controllerMessage').scrollBottom(); -} - -function processAlarm(data){ - console.log("alarm received"); - $("#alarms").html(""+data.message+""); - $("#alarms").removeClass('alert-success').addClass('alert-danger'); - $("#stopButton").addClass('stopbutton'); -} - -function clearAlarm(data){ - console.log("clearing alarm"); - $("#alarms").text("Alarm cleared."); - $("#alarms").removeClass('alert-danger').addClass('alert-success'); -} - -function processStatusMessage(data){ - if (data.uploadFlag == 1){ - if (!isDisabled){ - $('.disabler').prop('disabled', true); - isDisabled = true; - } - } else { - if (isDisabled){ - $('.disabler').prop('disabled', false); - isDisabled = false; - } - } - $("#currentTool").text(data.currentTool.toString()); - if (data.positioningMode == 0) - $("#currentPositioningMode").text("Absolute (G90)"); - else - $("#currentPositioningMode").text("Incremental (G91)"); -} diff --git a/static/scripts/frontpageControls.js b/static/scripts/frontpageControls.js index 0db16aa2..4db49595 100644 --- a/static/scripts/frontpageControls.js +++ b/static/scripts/frontpageControls.js @@ -1,203 +1,12 @@ +import "feather"; +import "jquery"; -var boardWidth = 96 -var boardHeight = 48 -var boardThickness = 0.75 -var boardCenterX = 0 -var boardCenterY = 0 -var boardID = "-" -var boardMaterial = "-" +$(() => { + // document.ready + var controllerMessage = document.getElementById('controllerMessage'); + controllerMessage.scrollTop = controllerMessage.scrollHeight; + + window.isFrontpage3d_mobilecontrols = false; -function unitSwitch(){ - if ( $("#units").text()=="MM") { - distToMove = (parseFloat($("#distToMove").val())/25.4).toFixed(3) - updateSetting('toInches',distToMove); - } else { - distToMove = (parseFloat($("#distToMove").val())*25.4).toFixed(3) - updateSetting('toMM',distToMove); - } -} - -$(document).ready(function(){ - action("statusRequest","cameraStatus"); - var controllerMessage = document.getElementById('controllerMessage'); - controllerMessage.scrollTop = controllerMessage.scrollHeight; - $("#stickyButtons").css("top", $(".navbar").outerHeight()); + $("#stickyButtons").css("top", $(".navbar").outerHeight()); }); - -function pauseRun(){ - if ($("#pauseButton").text()=="Pause"){ - action('pauseRun'); - } - else { - action('resumeRun'); - } -} - -function resumeRun(){ - action('resumeRun') -} - -function processRequestedSetting(data){ - //console.log(data); - if (data.setting=="pauseButtonSetting"){ - if(data.value=="Resume") - $('#pauseButton').removeClass('btn-warning').addClass('btn-info'); - else - $('#pauseButton').removeClass('btn-info').addClass('btn-warning'); - $("#pauseButton").text(data.value); - } - if (data.setting=="units"){ - console.log("requestedSetting:"+data.value); - $("#units").text(data.value) - } - if (data.setting=="distToMove"){ - console.log("requestedSetting for distToMove:"+data.value); - $("#distToMove").val(data.value) - } - if ((data.setting=="unitsZ") || (data.setting=="distToMoveZ")){ - if (typeof processZAxisRequestedSetting === "function") { - processZAxisRequestedSetting(data) - } - } -} - -function pwr2color(pwr) { - var r, g, b = 0; - - if(pwr < 127) { - g = 255; - r = pwr*2; - } - else { - r = 255; - g = 255-(pwr-127)*2; - if(pwr>240) { - g=0 - b=(pwr-240)*16 - } - } - var h = r * 0x10000 + g * 0x100 + b * 0x1; - return '#' + ('000000' + h.toString(16)).slice(-6); -} - -function processPositionMessage(data){ - $('#positionMessage').html('X:'+parseFloat(data.xval).toFixed(2)+' Y:'+parseFloat(data.yval).toFixed(2)+' Z:'+parseFloat(data.zval).toFixed(2)+' V:'+parseFloat(data.vel).toFixed(2)+'/'+parseFloat(data.rqvel).toFixed(2)); - $('#percentComplete').html(data.pcom) - $('#machineState').html(data.state) - if (enable3D) - positionUpdate(data.xval,data.yval,data.zval); - - $('#leftError').css('background-color', pwr2color(data.lpwr)).attr('title', data.lpwr); - $('#rightError').css('background-color', pwr2color(data.rpwr)).attr('title', data.rpwr); - $('#zError').css('background-color', pwr2color(data.zpwr)).attr('title', data.zpwr); -} - -function processErrorValueMessage(data){ - $('#leftError').css('width', data.leftError*100+'%').attr('aria-valuenow', data.leftError*100); - $('#rightError').css('width', data.rightError*100+'%').attr('aria-valuenow', data.rightError*100); - if (enable3D){ - processError3d(data) - } -} - - -function processHomePositionMessage(data){ - console.log(data.xval) - $('#homePositionMessage').html('X:'+parseFloat(data.xval).toFixed(2)+' Y:'+parseFloat(data.yval).toFixed(2)); - if (enable3D) - homePositionUpdate(data.xval,data.yval); -} - -function processGCodePositionMessage(data){ - $('#gcodePositionMessage').html('X:'+parseFloat(data.xval).toFixed(2)+' Y:'+parseFloat(data.yval).toFixed(2)+' Z:'+parseFloat(data.zval).toFixed(2)); - $('#gcodeLine').html(data.gcodeLine); - $('#gcodeLineIndex').val(data.gcodeLineIndex+1) - if (enable3D) - gcodePositionUpdate(data.xval,data.yval,data.zval); -} - -function showFPSpinner(msg){ - $("#fpCircle").show(); -} - -function processControllerMessage(data){ - if (controllerMessages.length >100) - controllerMessages.shift(); - controllerMessages.push(data); - $('#controllerMessage').html(''); - controllerMessages.forEach(function(message){ - $('#controllerMessage').append(message+"
"); - }); - $('#controllerMessage').scrollBottom(); -} - -function processAlarm(data){ - console.log("alarm received"); - $("#alarms").html(""+data.message+""); - $("#alarms").removeClass('alert-success').addClass('alert-danger'); - $("#stopButton").addClass('stopbutton'); -} - -function clearAlarm(data){ - console.log("clearing alarm"); - $("#alarms").text("Alarm cleared."); - $("#alarms").removeClass('alert-danger').addClass('alert-success'); - $("#stopButton").removeClass('stopbutton'); -} - - -function boardDataUpdate(data){ - console.log("updating board data"); - boardWidth = data.width; - boardHeight = data.height; - boardThickness = data.thickness; - boardCenterX = data.centerX; - boardCenterY = data.centerY; - boardID = data.boardID; - boardMaterial = data.material; - $("#boardID").text("Board: "+boardID+", Material: "+boardMaterial); - if (enable3D) - board3DDataUpdate(data); -} - -function moveAction(direction) { - distance = $("#distToMove").val(); - distanceValid = distance.search(/^[0-9]*(\.[0-9]{0,3})?$/); - if (distanceValid == 0) { - action('move', direction, distance); - } else { - $("#distToMove").focus(); - } -} - -function processStatusMessage(data){ - //console.log(data) - if (data.uploadFlag==1){ - if (isDisabled!=data.uploadFlag){ - $('.disabler').prop('disabled', true); - $('.ndisabler').prop('disabled', false); - $('.gcdisabler').prop('disabled', true); - isDisabled =data.uploadFlag; - } - } else if (data.uploadFlag==2 || data.uploadFlag==-1 ){ - if (isDisabled!=data.uploadFlag){ - $('.gcdisabler').prop('disabled', true); - $('.disabler').prop('disabled', false); - $('.ndisabler').prop('disabled', false); - isDisabled = data.uploadFlag; - } - } - else { - if (isDisabled!=data.uploadFlag){ - $('.disabler').prop('disabled', false); - $('.ndisabler').prop('disabled', true); - $('.gcdisabler').prop('disabled', false); - isDisabled = data.uploadFlag; - } - } - $("#currentTool").text(data.currentTool.toString()); - if (data.positioningMode == 0) - $("#currentPositioningMode").text("Absolute (G90)"); - else - $("#currentPositioningMode").text("Incremental (G91)"); -} diff --git a/static/scripts/frontpageControlsCommon.js b/static/scripts/frontpageControlsCommon.js new file mode 100644 index 00000000..ca505468 --- /dev/null +++ b/static/scripts/frontpageControlsCommon.js @@ -0,0 +1,203 @@ +import { action } from "./socketEmits.js"; +import { dp2, pwr2color } from "./utilities.js"; +import { processZAxisRequestedSetting } from "./zAxis.js"; + +window.board = { + width: 96, + height: 48, + thickness: 0.75, + centerX: 0, + centerY: 0, + ID: "-", + material: "-", +}; + +function pauseRun() { + action($("#pauseButton").text() == "Pause" ? "pauseRun" : "resumeRun"); +} + +function resumeRun() { + action('resumeRun') +} + +function processRequestedSetting(data) { + if (data.setting == "pauseButtonSetting") { + if (data.value == "Resume") + $('#pauseButton').removeClass('btn-warning').addClass('btn-info'); + else + $('#pauseButton').removeClass('btn-info').addClass('btn-warning'); + $("#pauseButton").text(data.value); + } + if (data.setting == "units") { + console.log("requestedSetting:" + data.value); + $("#units").text(data.value); + } + if (data.setting == "distToMove") { + console.log("requestedSetting for distToMove:" + data.value); + $("#distToMove").val(data.value); + } + if ((data.setting == "unitsZ") || (data.setting == "distToMoveZ")) { + if (typeof processZAxisRequestedSetting === "function") { + processZAxisRequestedSetting(data); + } + } +} + +function processPositionMessage(data) { + $('#percentComplete').html(data.pcom) + $('#machineState').html(data.state) + if (window.isFrontpage3d_mobilecontrols) { + $('#positionMessage').html(`X:${dp2(data.xval)} Y:${dp2(data.yval)} Z:${dp2(data.zval)}`); + return; + } + + // All the others + $('#positionMessage').html(`X:${dp2(data.xval)} Y:${dp2(data.yval)} Z:${dp2(data.zval)} V:${(data.vel)}/${(data.rqvel)}`); + if (window.enable3D) { + window.frontpage3d.positionUpdate(data.xval, data.yval, data.zval); + } + + $('#leftError').css('background-color', pwr2color(data.lpwr)).attr('title', data.lpwr); + $('#rightError').css('background-color', pwr2color(data.rpwr)).attr('title', data.rpwr); + $('#zError').css('background-color', pwr2color(data.zpwr)).attr('title', data.zpwr); +} + +function processErrorValueMessage(data) { + $('#leftError').css('width', data.leftError * 100 + '%').attr('aria-valuenow', data.leftError * 100); + $('#rightError').css('width', data.rightError * 100 + '%').attr('aria-valuenow', data.rightError * 100); + if (window.isFrontpage3d_mobilecontrols && window.enable3D) { + window.frontpage3d.processError3d(data); + } +} + +function processHomePositionMessage(data) { + $('#homePositionMessage').html(`X:${dp2(data.xval)} Y:${dp2(data.yval)}`); + if (window.isFrontpage3d_mobilecontrols && enable3D) { + window.frontpage3d.homePositionUpdate(data.xval, data.yval); + } +} + +function processGCodePositionMessage(data) { + $('#gcodeLine').html(data.gcodeLine); + $('#gcodeLineIndex').val(data.gcodeLineIndex + 1) + if (window.isFrontpage3d_mobilecontrols) { + $('#positionMessage').html(`X:${dp2(data.xval)} Y:${dp2(data.yval)}`); + return; + } + + // All the others + $('#gcodePositionMessage').html(`X:${dp(data.xval)} Y:${dp(data.yval)} Z:${dp(data.zval)}`); + if (enable3D) { + window.frontpage3d.gcodePositionUpdate(data.xval, data.yval, data.zval); + } +} + +function processControllerMessage(data) { + if (window.controllerMessages.length > 100) { + window.controllerMessages.shift(); + } + window.controllerMessages.push(data); + $('#controllerMessage').html(''); + window.controllerMessages.forEach((message) => { + $('#controllerMessage').append(`${message}
`); + }); + $('#controllerMessage').scrollBottom(); +} + +function processAlarm(data) { + console.log("alarm received"); + // Can you say 'DEPRECATED'!!!! - TODO: get rid of the marquee + $("#alarms").html("" + data.message + ""); + $("#alarms").removeClass('alert-success').addClass('alert-danger'); + $("#stopButton").addClass('stopbutton'); +} + +function clearAlarm(data) { + console.log("clearing alarm"); + $("#alarms").text("Alarm cleared."); + $("#alarms").removeClass('alert-danger').addClass('alert-success'); + $("#stopButton").removeClass('stopbutton'); +} + +function boardDataUpdate(data) { + console.log("updating board data"); + window.board.width = data.width; + window.board.height = data.height; + window.board.thickness = data.thickness; + window.board.centerX = data.centerX; + window.board.centerY = data.centerY; + window.board.ID = data.boardID; + window.board.material = data.material; + $("#boardID").text(`Board: ${window.board.ID}, Material: ${window.board.material}`); + if (window.enable3D) { + window.frontpage3d.board3DDataUpdate(data); + } +} + +function moveAction(direction) { + distance = $("#distToMove").val(); + distanceValid = distance.search(/^[0-9]*(\.[0-9]{0,3})?$/); + if (distanceValid == 0) { + action('move', direction, distance); + } else { + $("#distToMove").focus(); + } +} + +function processStatusMessage(data) { + //console.log(data) + $("#currentTool").text(data.currentTool.toString()); + $("#currentPositioningMode").text((data.positioningMode == 0) ? "Absolute (G90)" : "Incremental (G91)"); + + if (window.isFrontpage3d_mobilecontrols) { + if (data.uploadFlag == 1) { + if (!window.isDisabled) { + $('.disabler').prop('disabled', true); + window.isDisabled = true; + } + } else { + if (window.isDisabled) { + $('.disabler').prop('disabled', false); + window.isDisabled = false; + } + } + return; + } + + if (data.uploadFlag == 1) { + if (window.isDisabled != data.uploadFlag) { + $('.disabler').prop('disabled', true); + $('.ndisabler').prop('disabled', false); + $('.gcdisabler').prop('disabled', true); + window.isDisabled = data.uploadFlag; + } + } else if (data.uploadFlag == 2 || data.uploadFlag == -1) { + if (window.isDisabled != data.uploadFlag) { + $('.gcdisabler').prop('disabled', true); + $('.disabler').prop('disabled', false); + $('.ndisabler').prop('disabled', false); + window.isDisabled = data.uploadFlag; + } + } else if (window.isDisabled != data.uploadFlag) { + $('.disabler').prop('disabled', false); + $('.ndisabler').prop('disabled', true); + $('.gcdisabler').prop('disabled', false); + window.isDisabled = data.uploadFlag; + } +} + +export { + boardDataUpdate, + clearAlarm, + moveAction, + pauseRun, + processAlarm, + processControllerMessage, + processErrorValueMessage, + processGCodePositionMessage, + processHomePositionMessage, + processPositionMessage, + processRequestedSetting, + processStatusMessage, + resumeRun, +}; diff --git a/static/scripts/logs.js b/static/scripts/logs.js index 499f3a2c..24dfa22a 100644 --- a/static/scripts/logs.js +++ b/static/scripts/logs.js @@ -1,112 +1,105 @@ -var socket -var alogMessages = []; -var logMessages = []; -var alogEnabled = true; -var logEnabled = true; -var loggingState = false; - -$(document).ready(function(){ - namespace = '/MaslowCNCLogs'; // change to an empty string to use the global namespace +import "jquery"; +import { io } from "socket.io"; + +window.logSocket; +window.alogMessages = []; +window.logMessages = []; +window.alogEnabled = true; +window.logEnabled = true; +window.loggingState = false; + +$(() => { + // document.ready + const namespace = "/MaslowCNCLogs"; // change to an empty string to use the global namespace // the socket.io documentation recommends sending an explicit package upon connection // this is specially important when using the global namespace - socket = io.connect('//' + document.domain + ':' + location.port + namespace, {'forceNew':true}); + const serverURL = `${location.protocol}//${location.hostname}:${location.port}${namespace}`; + window.logSocket = io.connect(serverURL); setListeners(); - $("#enablealog").change(function(){ - if ($(this).prop('checked')) - alogEnabled=true; - else - alogEnabled=false; + $("#enablealog").change(() => { + window.alogEnabled = $(this).prop('checked'); }); - $("#enablelog").change(function(){ - if ($(this).prop('checked')) - logEnabled=true; - else - logEnabled=false; + $("#enablelog").change(() => { + window.logEnabled = $(this).prop('checked'); }); - - }); -function setListeners(){ +function setListeners() { console.log("setting Listeners"); - socket.on('connect', function(msg) { - socket.emit('my event', {data: 'I\'m connected!'}); + window.logSocket.on('after connect', () => { + window.logSocket.emit('my event', { data: 'I\'m connected!' }); }); - socket.on('disconnect', function(msg) { + window.logSocket.on('disconnect', (msg) => { }); - socket.on('message', function(msg){ - switch(msg.log) { - case 'alog': - if (alogEnabled) - processalog(msg.data); - processLoggingState(msg.state); - break; - case 'log': - if (logEnabled) - processlog(msg.data); - processLoggingState(msg.state); - break; - case 'state': - if (loggingState != msg.state){ - processLoggingState(msg.state); - break; - } - - default: - console.log("!!!!!!"); - console.log("uncaught action:"+msg.command); - console.log("!!!!!!"); - } + window.logSocket.on('message', (msg) => { + switch (msg.log) { + case 'alog': + if (window.alogEnabled) { + processalog(msg.data); + } + processLoggingState(msg.state); + break; + case 'log': + if (window.logEnabled) { + processlog(msg.data); + } + processLoggingState(msg.state); + break; + case 'state': + if (window.loggingState != msg.state) { + processLoggingState(msg.state); + break; + } + + default: + console.log("!!!!!!"); + console.log("uncaught action:" + msg.command); + console.log("!!!!!!"); + } }); } -function processalog(data){ - if (alogMessages.length >1000){ - alogMessages.shift(); - $('#alogMessages').get(0).firstChild.remove(); - $('#alogMessages').get(0).firstChild.remove(); - } - alogMessages.push(data); - - $('#alogMessages').append(document.createTextNode(data)); - $('#alogMessages').append("
"); - $('#alogMessages').scrollBottom(); - - +function processalog(data) { + if (window.alogMessages.length > 1000) { + window.alogMessages.shift(); + $('#alogMessages').get(0).firstChild.remove(); + $('#alogMessages').get(0).firstChild.remove(); + } + window.alogMessages.push(data); + + $('#alogMessages').append(document.createTextNode(data)); + $('#alogMessages').append("
"); + $('#alogMessages').scrollBottom(); } -function processlog(data){ - - if (logMessages.length >1000){ - logMessages.shift(); - $('#logMessages').get(0).firstChild.remove(); - $('#logMessages').get(0).firstChild.remove(); - } - logMessages.push(data); - - $('#logMessages').append(document.createTextNode(data)); - $('#logMessages').append("
"); - $('#logMessages').scrollBottom(); +function processlog(data) { + if (window.logMessages.length > 1000) { + window.logMessages.shift(); + $('#logMessages').get(0).firstChild.remove(); + $('#logMessages').get(0).firstChild.remove(); + } + window.logMessages.push(data); + + $('#logMessages').append(document.createTextNode(data)); + $('#logMessages').append("
"); + $('#logMessages').scrollBottom(); } -function processLoggingState(data){ - if (data) - { - $("#loggingState").text("Logging Suspended"); - $("#loggingState").removeClass('alert-success').addClass('alert-secondary'); - } - else - { - $("#loggingState").text("Logging Active"); - $("#loggingState").removeClass('alert-secondary').addClass('alert-success'); - } +function processLoggingState(data) { + if (data) { + $("#loggingState").text("Logging Suspended"); + $("#loggingState").removeClass('alert-success').addClass('alert-secondary'); + } + else { + $("#loggingState").text("Logging Active"); + $("#loggingState").removeClass('alert-secondary').addClass('alert-success'); + } } - -$.fn.scrollBottom = function() { - return $(this).scrollTop($(this)[0].scrollHeight); +$.fn.scrollBottom = function () { + return $(this).scrollTop($(this)[0].scrollHeight); }; diff --git a/static/scripts/openBoard.js b/static/scripts/openBoard.js index 85fc175a..77611c80 100644 --- a/static/scripts/openBoard.js +++ b/static/scripts/openBoard.js @@ -1,9 +1,13 @@ +import "jquery"; + +import { checkForGCodeUpdate } from "./socketEmits.js"; $('#gcCircle').hide(); var unselected = []; var selectedDirectory = "{{lastSelectedDirectory}}" -$(document).ready(function () { +$(() => { + // document.ready refreshList(); $("#directorySelect").change(refreshList); }); @@ -49,4 +53,6 @@ function onFooterSubmit(){ $('#gcCircle').hide(); } }); -} \ No newline at end of file +} + +export { onFooterSubmit }; diff --git a/static/scripts/openGCode.js b/static/scripts/openGCode.js index 7f38d408..1dcd71ec 100644 --- a/static/scripts/openGCode.js +++ b/static/scripts/openGCode.js @@ -1,11 +1,16 @@ +import "jquery"; -$('#gcCircle').hide(); -var unselected = []; -var selectedDirectory = "{{lastSelectedDirectory}}" +import { checkForGCodeUpdate } from "./socketEmits.js"; -$(document).ready(function () { - refreshList(); - $("#directorySelect").change(refreshList); +$(() => { + // document.ready + + $('#gcCircle').hide(); + var unselected = []; + var selectedDirectory = $("#lastSelectedDirectory")?.text || "."; + + refreshList(unselected, selectedDirectory); + $("#directorySelect").on("change", {unselected, selectedDirectory}, refreshListHandler); /*$('#gcodeForm').on('submit', function(e) { e.preventDefault(); @@ -34,8 +39,12 @@ $(document).ready(function () { });*/ }); -function refreshList(){ - unselected.forEach(element => { +function refreshListHandler(event) { + refreshList(event.data.unselected, event.data.selectedDirectory); +} + +function refreshList(unselected, selectedDirectory){ + unselected.forEach((element) => { $("#fileSelect").append(element) }); unselected = []; @@ -51,7 +60,6 @@ function refreshList(){ }); } - function onFooterSubmit(){ //var formdata = $("#gcodeForm").serialize(); $('#gcCircle').show(); @@ -76,4 +84,6 @@ function onFooterSubmit(){ $('#gcCircle').hide(); } }); -} \ No newline at end of file +} + +export { onFooterSubmit }; diff --git a/static/scripts/opticalCalibration.js b/static/scripts/opticalCalibration.js index 49a349ab..b6dc1cfc 100644 --- a/static/scripts/opticalCalibration.js +++ b/static/scripts/opticalCalibration.js @@ -1,5 +1,8 @@ -settingRequest('Optical Calibration Settings','calibrationCurve'); -settingRequest('Optical Calibration Settings','calibrationError'); +import "jquery"; +import * as THREE from "three"; + +import { action, settingRequest } from "./socketEmits.js"; +import { OrbitControls } from "./OrbitControls.js"; var _xError = []; var _yError = []; @@ -31,7 +34,10 @@ var osledCircle; var osled; - +function initializeOpticalCalibration() { + settingRequest('Optical Calibration Settings', 'calibrationCurve'); + settingRequest('Optical Calibration Settings', 'calibrationError'); +} function oanimate() { requestAnimationFrame(oanimate); @@ -40,23 +46,20 @@ function oanimate() { } function positionUpdateOptical(x,y,z){ - if ($("#units").text()=="MM"){ - x /= 25.4 - y /= 25.4 - z /= 25.4 - } - if (osled !== undefined) - { - osled.position.set(x,y,z); - } - + if ($("#units").text()=="MM") { + x /= 25.4 + y /= 25.4 + z /= 25.4 + } + if (osled !== undefined) { + osled.position.set(x,y,z); + } } function processPositionMessageOptical(data){ positionUpdateOptical(data.xval,data.yval,data.zval); } - function process(processName){ var markerX = $("#markerX").val(); var markerY = $("#markerY").val(); @@ -169,7 +172,7 @@ function redrawCurveCharts(chart){ _y[i][j]=temp _x[j]=j } - if (isMobile == true) + if (window.isMobile) Plotly.plot(xCurvePlot, [{x: _x, y: _y[i] }], {title: "X-Curve", showlegend: false, margin: {l: 20,r: 20, b: 40, t: 40, pad: 4}, colorway: colorwayLayout } ); else Plotly.plot(xCurvePlot, [{x: _x, y: _y[i] }], {title: "X-Curve" , colorway: colorwayLayout } ); @@ -184,7 +187,7 @@ function redrawCurveCharts(chart){ _y[i][j]=temp _x[j]=j } - if (isMobile == true) + if (window.isMobile) Plotly.plot(yCurvePlot, [{x: _x, y: _y[i] }], {title: "Y-Curve", showlegend: false, margin: {l: 20,r: 20, b: 40, t: 40, pad: 4}, colorway: colorwayLayout } ); else Plotly.plot(yCurvePlot, [{x: _x, y: _y[i] }], {title: "Y-Curve" , colorway: colorwayLayout} ); @@ -193,7 +196,6 @@ function redrawCurveCharts(chart){ $('#curveChartButton').removeClass('btn-primary').addClass('btn-secondary'); } - function redrawErrorCharts(chart){ if ( (chart == "xError") || (chart=="all") ){ xErrorPlot = document.getElementById('xErrorPlot'); @@ -201,7 +203,7 @@ function redrawErrorCharts(chart){ while (xErrorPlot.data.length>0) Plotly.deleteTraces(xErrorPlot, [0]); for(var i = 0; i<15; i++){ - if (isMobile == true) + if (window.isMobile) Plotly.plot(xErrorPlot, [{x: _xValues, y: _xError[i] }], {title: "X-Error", showlegend: false, margin: {l: 20,r: 20, b: 40, t: 40, pad: 4}, colorway: colorwayLayout } ); else Plotly.plot(xErrorPlot, [{x: _xValues, y: _xError[i] }], {title: "X-Error", colorway: colorwayLayout } ); @@ -213,7 +215,7 @@ function redrawErrorCharts(chart){ while (yErrorPlot.data.length>0) Plotly.deleteTraces(yErrorPlot, [0]); for(var i = 0; i<15; i++){ - if (isMobile == true) + if (window.isMobile) Plotly.plot(yErrorPlot, [{x: _xValues, y: _yError[i] }], {title: "Y-Error", showlegend: false, margin: {l: 20,r: 20, b: 40, t: 40, pad: 4}, colorway: colorwayLayout } ); else Plotly.plot(yErrorPlot, [{x: _xValues, y: _yError[i] }], {title: "Y-Error", colorway: colorwayLayout } ); @@ -235,7 +237,7 @@ function redrawErrorCharts(chart){ x1 = [] y1 = [] //Plotly.plot(xyErrorPlot, [{x: x0, y: y0, mode:'markers', type:'scatter'}], {title: "XY-Error" } ); - if (isMobile == true) + if (window.isMobile) Plotly.plot(xyErrorPlot, [{x: x0, y: y0, mode:'markers', type:'scatter'}], {title: "XY-Error", showlegend: false, margin: {l: 20,r: 20, b: 40, t: 40, pad: 4} } ); else Plotly.plot(xyErrorPlot, [{x: x0, y: y0, mode:'markers', type:'scatter'}], {title: "XY-Error"} ); @@ -247,7 +249,7 @@ function redrawErrorCharts(chart){ } } //Plotly.plot(xyErrorPlot, [{x: x1, y: y1, mode:'markers', type:'scatter'}] ); - if (isMobile == true) + if (window.isMobile) Plotly.plot(xyErrorPlot, [{x: x1, y: y1, mode:'markers', type:'scatter'}], {title: "XY-Error", showlegend: false, margin: {l: 20,r: 20, b: 40, t: 40, pad: 4} } ); else Plotly.plot(xyErrorPlot, [{x: x1, y: y1, mode:'markers', type:'scatter'}], {title: "XY-Error"} ); @@ -262,7 +264,8 @@ function calSurface(x, i, j){ return retVal } -$(document).ready(function () { +$(() => { + // document.ready $('#calibrationExtents').change(function(){ selected = $("#calibrationExtents option:selected").val() console.log(selected); @@ -315,10 +318,12 @@ $(document).ready(function () { } }); console.log("here1"); - setupDisplay(); + ocontainer = document.getElementById('workareaOptical'); + if (ocontainer) { + setupDisplay(); + } }); - function setupDisplay(){ orenderer = new THREE.WebGLRenderer(); ow = $("#workareaOptical").width(); @@ -332,7 +337,7 @@ function setupDisplay(){ oimageShowing = 1 ocamera = new THREE.PerspectiveCamera(45, ow/oh, 1, 500); - ocontrols = new THREE.OrbitControls(ocamera, orenderer.domElement); + ocontrols = new OrbitControls(ocamera, orenderer.domElement); ocontrols.screenSpacePanning = true; ocamera.position.set(0, 0, 100); @@ -484,3 +489,12 @@ function drawCalibration(){ drawOptical.zoom(10, {x:originXOptical,y:originYOptical}); } */ + +export { + initializeOpticalCalibration, + processPositionMessageOptical, + updateCalibrationImage, + updateOpticalCalibrationCurve, + updateOpticalCalibrationError, + updateOpticalCalibrationFindCenter, +}; diff --git a/static/scripts/pidTuning.js b/static/scripts/pidTuning.js index 0db7f9af..27888745 100644 --- a/static/scripts/pidTuning.js +++ b/static/scripts/pidTuning.js @@ -1,20 +1,14 @@ +import { action } from "./socketEmits.js"; -var _xError = []; -var _yError = []; -var _xValues = []; var colorwayLayout = ['#313131','#3D019D','#3810DC','#2D47F9','#2503FF','#2ADEF6','#60FDFA','#AEFDFF','#BBBBBB','#FFFDA9','#FAFD5B','#F7DA29','#FF8E25','#F8432D','#D90D39','#D7023D','#313131'] - - - - -function updateVErrorCurve(data) { - console.log(data) - xCurve = data.curveX; - yCurve = data.curveY; - $('#curveChartButton').removeClass('btn-secondary').addClass('btn-primary'); - $('#curveFitButton').removeClass('btn-primary').addClass('btn-secondary'); -} +// function updateVErrorCurve(data) { +// console.log(data) +// xCurve = data.curveX; +// yCurve = data.curveY; +// $('#curveChartButton').removeClass('btn-secondary').addClass('btn-primary'); +// $('#curveFitButton').removeClass('btn-primary').addClass('btn-secondary'); +// } function updatePIDData(msg){ data = JSON.parse(msg.data); @@ -24,12 +18,11 @@ function updatePIDData(msg){ if ($("#vErrorPlot").html()!="") while (vErrorPlot.data.length>0) Plotly.deleteTraces(vErrorPlot, [0]); - if (data.version == "2") - { + if (data.version == "2") { var _setpoint = []; var _input = []; var _output = []; - for (var x = 0; x0) Plotly.deleteTraces(pErrorPlot, [0]); - if (data.version == "2") - { + if (data.version == "2") { var _setpoint = []; var _input = []; var _output = []; @@ -74,14 +66,6 @@ function updatePIDData(msg){ } } - - -$(document).ready(function () { - -}); - - - function vExecute(){ var vMotor = $('#vMotor label.active input').val(); var vStart= $("#vStart").val(); @@ -91,15 +75,16 @@ function vExecute(){ var KpV = $('#KpV').val(); var KiV = $('#KiV').val(); var KdV = $('#KdV').val(); - var parameters = {vMotor: vMotor, - vStart: vStart, - vStop: vStop, - vSteps: vSteps, - vVersion: vVersion, - KpV: KpV, - KiV: KiV, - KdV: KdV - }; + var parameters = { + vMotor: vMotor, + vStart: vStart, + vStop: vStop, + vSteps: vSteps, + vVersion: vVersion, + KpV: KpV, + KiV: KiV, + KdV: KdV + }; console.log(parameters); action('executeVelocityPIDTest',parameters); } @@ -114,17 +99,19 @@ function pExecute(){ var KpP = $('#KpP').val(); var KiP = $('#KiP').val(); var KdP = $('#KdP').val(); - var parameters = {pMotor: pMotor, - pStart: pStart, - pStop: pStop, - pSteps: pSteps, - pTime: pTime, - pVersion: pVersion, - KpP: KpP, - KiP: KiP, - KdP: KdP - }; + var parameters = { + pMotor: pMotor, + pStart: pStart, + pStop: pStop, + pSteps: pSteps, + pTime: pTime, + pVersion: pVersion, + KpP: KpP, + KiP: KiP, + KdP: KdP + }; console.log(parameters); action('executePositionPIDTest',parameters); } +export { pExecute, updatePIDData, vExecute }; diff --git a/static/scripts/saveBoard.js b/static/scripts/saveBoard.js index f154a213..966885e8 100644 --- a/static/scripts/saveBoard.js +++ b/static/scripts/saveBoard.js @@ -1,9 +1,13 @@ +import "jquery"; + +import { checkForGCodeUpdate } from "./socketEmits.js"; $('#gcCircle').hide(); var unselected = []; var selectedDirectory = "{{lastSelectedDirectory}}" -$(document).ready(function () { +$(() => { + // document.ready refreshList(); $("#directorySelect").change(refreshList); }); @@ -25,8 +29,6 @@ function refreshList(){ }); } - - function onFooterSubmit(){ //var formdata = $("#gcodeForm").serialize(); $('#gcCircle').show(); @@ -51,4 +53,6 @@ function onFooterSubmit(){ $('#gcCircle').hide(); } }); -} \ No newline at end of file +} + +export { onFooterSubmit }; diff --git a/static/scripts/saveGCode.js b/static/scripts/saveGCode.js index a5e3d68e..5e7f21dd 100644 --- a/static/scripts/saveGCode.js +++ b/static/scripts/saveGCode.js @@ -1,9 +1,13 @@ +import "jquery"; + +import { checkForGCodeUpdate } from "./socketEmits.js"; $('#gcCircle').hide(); var unselected = []; var selectedDirectory = "{{lastSelectedDirectory}}" -$(document).ready(function () { +$(() => { + // document.ready refreshList(); $("#directorySelect").change(refreshList); }); @@ -25,8 +29,6 @@ function refreshList(){ }); } - - function onFooterSubmit(){ //var formdata = $("#gcodeForm").serialize(); $('#gcCircle').show(); @@ -51,4 +53,6 @@ function onFooterSubmit(){ $('#gcCircle').hide(); } }); -} \ No newline at end of file +} + +export { onFooterSubmit }; diff --git a/static/scripts/setSprockets.js b/static/scripts/setSprockets.js index e23603a6..365c44e6 100644 --- a/static/scripts/setSprockets.js +++ b/static/scripts/setSprockets.js @@ -1,47 +1,30 @@ -function unitSwitch(){ - if ( $("#unitsZ").text()=="MM") { - //$("#unitsZ").text("INCHES"); - //distToMoveZ = (parseFloat($("#distToMoveZ").val())/25.4).toFixed(2) - //$("#distToMoveZ").val(distToMoveZ); - updateSetting('toInchesZ',distToMoveZ); - } else { - //$("#unitsZ").text("MM"); - //distToMoveZ = (parseFloat($("#distToMoveZ").val())*25.4).toFixed(2) - //$("#distToMoveZ").val(distToMoveZ); - updateSetting('toMMZ',distToMoveZ); - } -} +import { action } from "./socketEmits.js"; -function setSprocketsZero(){ - action('setSprocketsZero');$("input").prop('disabled', false); - $("#leftChainDefault").prop('disabled',false); - $("#rightChainDefault").prop('disabled',false); +export function setSprocketsZero() { + action('setSprocketsZero'); $("input").prop('disabled', false); + $("#leftChainDefault").prop('disabled', false); + $("#rightChainDefault").prop('disabled', false); } -function disableChainToDefault() -{ - $("#leftChainDefault").prop('disabled',true); - $("#rightChainDefault").prop('disabled',true); +export function disableChainToDefault() { + $("#leftChainDefault").prop('disabled', true); + $("#rightChainDefault").prop('disabled', true); } -function processCalibrationMessage(msg) -{ - data = msg.data.split(":"); - if(data[0]=="left") - { - if (data[1]!="0"){ - $("#leftChainDefault").text(data[1]+"..."); - } else { - $("#leftChainDefault").text("Left Chain To Default"); - } +export function processCalibrationMessage(msg) { + data = msg.data.split(":"); + if (data[0] == "left") { + if (data[1] != "0") { + $("#leftChainDefault").text(data[1] + "..."); + } else { + $("#leftChainDefault").text("Left Chain To Default"); } - if(data[0]=="right") - { - if (data[1]!="0"){ - $("#rightChainDefault").text(data[1]+"..."); - } else { - $("#rightChainDefault").text("Right Chain To Default"); - } + } + if (data[0] == "right") { + if (data[1] != "0") { + $("#rightChainDefault").text(data[1] + "..."); + } else { + $("#rightChainDefault").text("Right Chain To Default"); } - + } } \ No newline at end of file diff --git a/static/scripts/settings.js b/static/scripts/settings.js index ea7a5472..391921a6 100644 --- a/static/scripts/settings.js +++ b/static/scripts/settings.js @@ -1,3 +1,7 @@ +import "jquery"; + +import { action } from "./socketEmits.js"; + function onFooterSubmit(){ var url = $("#pageID").val() $.ajax({ @@ -14,7 +18,8 @@ function onFooterSubmit(){ }); } -$(document).ready(function () { +$(() => { + // document.ready $('#settingsForm').on('submit', function(e) { e.preventDefault(); var url = $("#pageID").val() @@ -55,3 +60,5 @@ function updatePorts(data){ }); } } + +export { onFooterSubmit, updatePorts }; diff --git a/static/scripts/socketEmits.js b/static/scripts/socketEmits.js new file mode 100644 index 00000000..9e7fa67a --- /dev/null +++ b/static/scripts/socketEmits.js @@ -0,0 +1,55 @@ +// These all assume that there is a global value called 'socket' + +function action(command, arg, arg1) { + arg = arg || ""; + arg1 = arg1 || ""; + console.log(`action=${command}`); + window.socket.emit('action', { data: { command: command, arg: arg, arg1: arg1 } }); +} + +function move(direction) { + window.distToMove = $("#distToMove").val(); + console.log(window.distToMove) + window.socket.emit('move', { data: { direction: direction, distToMove: window.distToMove } }); +} + +function moveZ(direction) { + window.distToMoveZ = $("#distToMoveZ").val(); + console.log(window.distToMoveZ) + window.socket.emit('moveZ', { data: { direction: direction, distToMoveZ: window.distToMoveZ } }); +} + +function settingRequest(section, setting) { + console.log(`requesting setting ${setting} in ${section}`); + window.socket.emit('settingRequest', { data: { section: section, setting: setting } }); +} + +// This is not exported +function statusRequest(status) { + console.log("requesting status..") + window.socket.emit('statusRequest', { data: { status: status } }); +} + +function requestPage(page, args = "") { + console.log(`requesting page: ${page}`) + window.socket.emit('requestPage', { data: { page: page, isMobile: window.isMobile, args: args } }); +} + +function updateSetting(setting, value) { + console.log(`updating setting ${setting} to ${value}`); + window.socket.emit('updateSetting', { data: { setting: setting, value: value } }); +} + +function checkForGCodeUpdate() { + window.socket.emit('checkForGCodeUpdate', { data: "Please" }); +} + +function checkForBoardUpdate() { + window.socket.emit('checkForBoardUpdate', { data: "Please" }); +} + +// function checkForHostAddress() { +// window.socket.emit('checkForHostAddress', { data: "Please" }); +// } + +export { action, move, moveZ, settingRequest, requestPage, updateSetting, checkForGCodeUpdate, checkForBoardUpdate }; \ No newline at end of file diff --git a/static/scripts/touch.js b/static/scripts/touch.js index eab10b48..a22e9cb9 100644 --- a/static/scripts/touch.js +++ b/static/scripts/touch.js @@ -1,8 +1,28 @@ -var onlongtouch; +import "jquery"; + +import { requestPage } from "./socketEmits.js"; + var timer; var touchduration = 1500; //length of time we want the user to touch before we do something var lastTouchEvent = null; +function onlongtouch(e) { + console.log("long touch detected", e.touches[0].clientX, e.touches[0].clientY); + // TODO: the global `event` is deprecated, this needs replacing + event = { + clientX: e.touches[0].clientX, + clientY: e.touches[0].clientY + } + var pos = cursorPosition(); + x = pos.x; + x = x.toFixed(4); + pos.x = x; + + y = pos.y; + y = y.toFixed(4); + pos.y = y; + requestPage("screenAction", pos); +}; function touchstart(e) { e.preventDefault(); @@ -12,7 +32,7 @@ function touchstart(e) { onlongtouch(lastTouchEvent); timer = null; lastTouchEvent = null; - }, touchduration); + }, touchduration); } } @@ -25,23 +45,6 @@ function touchend(e) { } } -onlongtouch = function(e) { - console.log("long touch detected", e.touches[0].clientX, e.touches[0].clientY); - event = { - clientX: e.touches[0].clientX, - clientY: e.touches[0].clientY - } - var pos = cursorPosition(); - x = pos.x; - x = x.toFixed(4); - pos.x = x; - - y = pos.y; - y = y.toFixed(4); - pos.y = y; - requestPage("screenAction",pos) -}; - document.addEventListener("DOMContentLoaded", function(event) { $("#workarea")[0].addEventListener("touchstart", touchstart, false); $("#workarea")[0].addEventListener("touchend", touchend, false); diff --git a/static/scripts/unitSwitch.js b/static/scripts/unitSwitch.js new file mode 100644 index 00000000..4a4f54de --- /dev/null +++ b/static/scripts/unitSwitch.js @@ -0,0 +1,27 @@ +import { updateSetting } from "./socketEmits.js"; + +function unitSwitch() { + if ($("#units").text() == "MM") { + window.distToMove = (parseFloat($("#distToMove").val()) / 25.4).toFixed(3) + updateSetting('toInches', window.distToMove); + } else { + window.distToMove = (parseFloat($("#distToMove").val()) * 25.4).toFixed(3) + updateSetting('toMM', window.distToMove); + } +} + +function unitSwitchZ() { + if ($("#unitsZ").text() == "MM") { + $("#unitsZ").text("INCHES"); + window.distToMoveZ = (parseFloat($("#distToMoveZ").val()) / 25.4).toFixed(2); + //$("#distToMoveZ").val(window.distToMoveZ); + updateSetting('toInchesZ', window.distToMoveZ); + } else { + $("#unitsZ").text("MM"); + window.distToMoveZ = (parseFloat($("#distToMoveZ").val()) * 25.4).toFixed(2); + //$("#distToMoveZ").val(window.distToMoveZ); + updateSetting('toMMZ', window.distToMoveZ); + } +} + +export { unitSwitch, unitSwitchZ } diff --git a/static/scripts/uploadGCode.js b/static/scripts/uploadGCode.js index 02960418..e1297bea 100644 --- a/static/scripts/uploadGCode.js +++ b/static/scripts/uploadGCode.js @@ -1,3 +1,7 @@ +import "jquery"; + +import { action, checkForGCodeUpdate } from "./socketEmits.js"; + function updateDirectories(data){ var directory = data.directory; console.log(directory); @@ -6,7 +10,8 @@ function updateDirectories(data){ $("#newDirectory").val(""); } -$(document).ready(function () { +$(() => { + // document.ready $('#newDirectoryButton').on('click',function(){ //bind click handler event.preventDefault(); directory=$("#newDirectory").val(); @@ -81,4 +86,6 @@ function onFooterSubmit(){ alert(errorThrown); } }); -} \ No newline at end of file +} + +export { updateDirectories }; diff --git a/static/scripts/utilities.js b/static/scripts/utilities.js new file mode 100644 index 00000000..744aff40 --- /dev/null +++ b/static/scripts/utilities.js @@ -0,0 +1,25 @@ +/** parse the supplied value as a float and return it with 2 decimal places */ +function dp2(value) { + return parseFloat(value).toFixed(2) +} + +function pwr2color(pwr) { + var r, g, b = 0; + + if (pwr < 127) { + g = 255; + r = pwr * 2; + } + else { + r = 255; + g = 255 - (pwr - 127) * 2; + if (pwr > 240) { + g = 0 + b = (pwr - 240) * 16 + } + } + var h = r * 0x10000 + g * 0x100 + b * 0x1; + return '#' + ('000000' + h.toString(16)).slice(-6); +} + +export { dp2, pwr2color }; \ No newline at end of file diff --git a/static/scripts/zAxis.js b/static/scripts/zAxis.js index 075a46a1..1efb7985 100644 --- a/static/scripts/zAxis.js +++ b/static/scripts/zAxis.js @@ -1,30 +1,10 @@ - -function unitSwitchZ(){ - if ( $("#unitsZ").text()=="MM") { - $("#unitsZ").text("INCHES"); - distToMoveZ = (parseFloat($("#distToMoveZ").val())/25.4).toFixed(2); - //$("#distToMoveZ").val(distToMoveZ); - updateSetting('toInchesZ',distToMoveZ); - } else { - $("#unitsZ").text("MM"); - distToMoveZ = (parseFloat($("#distToMoveZ").val())*25.4).toFixed(2); - //$("#distToMoveZ").val(distToMoveZ); - updateSetting('toMMZ',distToMoveZ); +export function processZAxisRequestedSetting(msg) { + if (msg.setting == "unitsZ") { + console.log("requestedSetting:" + msg.value); + $("#unitsZ").text(msg.value) + } + if (msg.setting == "distToMoveZ") { + console.log("requestedSetting for distToMoveZ:" + msg.value); + $("#distToMoveZ").val(msg.value) } } - -function processZAxisRequestedSetting(msg){ - if (msg.setting=="unitsZ"){ - console.log("requestedSetting:"+msg.value); - $("#unitsZ").text(msg.value) - } - if (msg.setting=="distToMoveZ"){ - console.log("requestedSetting for distToMoveZ:"+msg.value); - $("#distToMoveZ").val(msg.value) - } -} - -$(document).ready(function(){ - settingRequest("Computed Settings","unitsZ"); - settingRequest("Computed Settings","distToMoveZ"); -}); diff --git a/static/styles/bootstrap.min.css b/static/styles/bootstrap.min.css deleted file mode 100644 index 88269128..00000000 --- a/static/styles/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.1.3 (https://getbootstrap.com/) - * Copyright 2011-2018 The Bootstrap Authors - * Copyright 2011-2018 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014 \00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(2.25rem + 2px);padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.8125rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(2.875rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.custom-select.is-valid,.form-control.is-valid,.was-validated .custom-select:valid,.was-validated .form-control:valid{border-color:#28a745}.custom-select.is-valid:focus,.form-control.is-valid:focus,.was-validated .custom-select:valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{background-color:#71dd8a}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(40,167,69,.25)}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label::after,.was-validated .custom-file-input:valid~.custom-file-label::after{border-color:inherit}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.custom-select.is-invalid,.form-control.is-invalid,.was-validated .custom-select:invalid,.was-validated .form-control:invalid{border-color:#dc3545}.custom-select.is-invalid:focus,.form-control.is-invalid:focus,.was-validated .custom-select:invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{background-color:#efa2a9}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(220,53,69,.25)}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label::after,.was-validated .custom-file-input:invalid~.custom-file-label::after{border-color:inherit}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:focus,.btn:hover{text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;background-color:transparent}.btn-link:hover{color:#0056b3;text-decoration:underline;background-color:transparent;border-color:transparent}.btn-link.focus,.btn-link:focus{text-decoration:underline;border-color:transparent;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media screen and (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media screen and (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-right{right:0;left:auto}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;width:0;height:0;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:0 1 auto;flex:0 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.btn-group-vertical .btn+.btn,.btn-group-vertical .btn+.btn-group,.btn-group-vertical .btn-group+.btn,.btn-group-vertical .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical .btn,.btn-group-vertical .btn-group{width:100%}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{height:calc(2.875rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{height:calc(1.8125rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:active~.custom-control-label::before{color:#fff;background-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#dee2e6}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background-repeat:no-repeat;background-position:center center;background-size:50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(2.25rem + 2px);padding:.375rem 1.75rem .375rem .75rem;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center;background-size:8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(128,189,255,.5)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{height:calc(1.8125rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:75%}.custom-select-lg{height:calc(2.875rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:125%}.custom-file{position:relative;display:inline-block;width:100%;height:calc(2.25rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(2.25rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:focus~.custom-file-label::after{border-color:#80bdff}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(2.25rem + 2px);padding:.375rem .75rem;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:2.25rem;padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:1px solid #ced4da;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;padding-left:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media screen and (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media screen and (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media screen and (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler:not(:disabled):not(.disabled){cursor:pointer}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:first-child .card-header,.card-group>.card:first-child .card-img-top{border-top-right-radius:0}.card-group>.card:first-child .card-footer,.card-group>.card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:last-child .card-header,.card-group>.card:last-child .card-img-top{border-top-left-radius:0}.card-group>.card:last-child .card-footer,.card-group>.card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group>.card:only-child{border-radius:.25rem}.card-group>.card:only-child .card-header,.card-group>.card:only-child .card-img-top{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-group>.card:only-child .card-footer,.card-group>.card:only-child .card-img-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-group>.card:not(:first-child):not(:last-child):not(:only-child){border-radius:0}.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-footer,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-header,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-top{border-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion .card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion .card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion .card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion .card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-link:not(:disabled):not(.disabled){cursor:pointer}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}.badge-primary[href]:focus,.badge-primary[href]:hover{color:#fff;text-decoration:none;background-color:#0062cc}.badge-secondary{color:#fff;background-color:#6c757d}.badge-secondary[href]:focus,.badge-secondary[href]:hover{color:#fff;text-decoration:none;background-color:#545b62}.badge-success{color:#fff;background-color:#28a745}.badge-success[href]:focus,.badge-success[href]:hover{color:#fff;text-decoration:none;background-color:#1e7e34}.badge-info{color:#fff;background-color:#17a2b8}.badge-info[href]:focus,.badge-info[href]:hover{color:#fff;text-decoration:none;background-color:#117a8b}.badge-warning{color:#212529;background-color:#ffc107}.badge-warning[href]:focus,.badge-warning[href]:hover{color:#212529;text-decoration:none;background-color:#d39e00}.badge-danger{color:#fff;background-color:#dc3545}.badge-danger[href]:focus,.badge-danger[href]:hover{color:#fff;text-decoration:none;background-color:#bd2130}.badge-light{color:#212529;background-color:#f8f9fa}.badge-light[href]:focus,.badge-light[href]:hover{color:#212529;text-decoration:none;background-color:#dae0e5}.badge-dark{color:#fff;background-color:#343a40}.badge-dark[href]:focus,.badge-dark[href]:hover{color:#fff;text-decoration:none;background-color:#1d2124}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media screen and (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item:focus,.list-group-item:hover{z-index:1;text-decoration:none}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:not(:disabled):not(.disabled){cursor:pointer}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{color:#000;text-decoration:none;opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-25%);transform:translate(0,-25%)}@media screen and (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:translate(0,0);transform:translate(0,0)}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - (.5rem * 2))}.modal-dialog-centered::before{display:block;height:calc(100vh - (.5rem * 2));content:""}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #e9ecef}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-centered{min-height:calc(100% - (1.75rem * 2))}.modal-dialog-centered::before{height:calc(100vh - (1.75rem * 2))}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg{max-width:800px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::after,.bs-popover-top .arrow::before{border-width:.5rem .5rem 0}.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::before{bottom:0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-top .arrow::after{bottom:1px;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::after,.bs-popover-right .arrow::before{border-width:.5rem .5rem .5rem 0}.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::before{left:0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-right .arrow::after{left:1px;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::after,.bs-popover-bottom .arrow::before{border-width:0 .5rem .5rem .5rem}.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::before{top:0;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-bottom .arrow::after{top:1px;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::after,.bs-popover-left .arrow::before{border-width:.5rem 0 .5rem .5rem}.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::before{right:0;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-left .arrow::after{right:1px;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-item{position:relative;display:none;-ms-flex-align:center;align-items:center;width:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease}@media screen and (prefers-reduced-motion:reduce){.carousel-item-next,.carousel-item-prev,.carousel-item.active{transition:none}}.carousel-item-next,.carousel-item-prev{position:absolute;top:0}.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.active.carousel-item-right,.carousel-item-next{-webkit-transform:translateX(100%);transform:translateX(100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-right,.carousel-item-next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translateX(-100%);transform:translateX(-100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.carousel-fade .carousel-item{opacity:0;transition-duration:.6s;transition-property:opacity}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{opacity:0}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-prev,.carousel-fade .carousel-item-next,.carousel-fade .carousel-item-prev,.carousel-fade .carousel-item.active{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-prev,.carousel-fade .carousel-item-next,.carousel-fade .carousel-item-prev,.carousel-fade .carousel-item.active{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:transparent no-repeat center center;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:10px;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{position:relative;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:rgba(255,255,255,.5)}.carousel-indicators li::before{position:absolute;top:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators li::after{position:absolute;bottom:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-circle{border-radius:50%!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0062cc!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#545b62!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#1e7e34!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#117a8b!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#d39e00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#bd2130!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#dae0e5!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#1d2124!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/templates/about.html b/templates/about.html index 70d3189d..bb085a50 100644 --- a/templates/about.html +++ b/templates/about.html @@ -15,5 +15,5 @@

Webcontrol is licensed under the GNU General Public L {% endblock %} {% block javascript %} - + {% endblock %} diff --git a/templates/actions.html b/templates/actions.html index d71b6cb8..592db520 100644 --- a/templates/actions.html +++ b/templates/actions.html @@ -22,7 +22,7 @@

Diagnostics/Maintenance

- + @@ -34,11 +34,11 @@

Calibration/Setup

- + - - - + + + diff --git a/templates/archive/frontpage.html b/templates/archive/frontpage.html index 96643274..ae5af8dd 100644 --- a/templates/archive/frontpage.html +++ b/templates/archive/frontpage.html @@ -5,12 +5,13 @@ {% endblock %} {% block javascript %} - - - + {% endblock %} {% block content %} diff --git a/templates/archive/frontpage_mobile.html b/templates/archive/frontpage_mobile.html index a1064036..7747b906 100644 --- a/templates/archive/frontpage_mobile.html +++ b/templates/archive/frontpage_mobile.html @@ -6,12 +6,13 @@ {% endblock %} {% block javascript %} - - - + {% endblock %} {% block content %} diff --git a/templates/base.html b/templates/base.html index 0824927c..0b3a7ccc 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,21 +3,53 @@ {% block title %}{% endblock %}WebControl - + + - - - - - - + + + + + + + - + @@ -115,7 +148,7 @@ @@ -148,7 +181,7 @@ @@ -169,15 +202,15 @@ diff --git a/templates/editGCode.html b/templates/editGCode.html index f56e0729..044e8208 100644 --- a/templates/editGCode.html +++ b/templates/editGCode.html @@ -6,7 +6,7 @@ {% endblock %} {% block javascript %} - + + + - - - - - - - - {% endblock %} {% block content %}
-
-

Controls

-
-
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
-
-
-
- -
-
- -
+
+

Controls

+
+
+
+
-
-
-
No alarms
-
+
+
-
-
- -
-
- -
-
- -
+
+
-
-
- -
-
- -
-
- -
-
- -
+
+
-
-
-
Line #:
-
-
- -
-
- -
-
- -
+
+ +
+
-
-
-
Line:
-
-
-
-
+
+
-
-
-
Sled:
-
-
-
-
+
+
-
-
-
Error:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
Home:
-
-
-
-
+
+ +
+
-
-
-
GCode:
-
-
-
-
+
+
-
-
-
Complete:
-
-
-
-
-
-
State:
-
-
-
-
+
+
-
-
-
Tool:
-
-
-
-
-
-
Positioning:
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
No alarms
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
Line #:
+
+
+ +
+
+ +
+
+ +
+
+
+
+
Line:
+
+
+
+
+
+
+
+
Sled:
+
+
+
+
+
+
+
+
Error:
+
+
+
+
-
-
+
+
+
+
-
-
-

Controller Messages

+
+
+
+
+
+
+
Home:
+
+
+
+
+
+
+
+
GCode:
+
+
+
+
+
+
+
+
Complete:
+
+
+
+
+
+
State:
+
+
+
+
+
+
+
+
Tool:
+
+
+
-
+
+
Positioning:
+
+
+
+
+
+
+
+

Controller Messages

+
+
-
- -
- - - - - Cursor Position - - -
-
-
+
+
+
+
+ + + + + Cursor Position + +
- +
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/frontpage3d_mobile.html b/templates/frontpage3d_mobile.html index de18b452..9c299101 100644 --- a/templates/frontpage3d_mobile.html +++ b/templates/frontpage3d_mobile.html @@ -6,14 +6,11 @@ {% endblock %} {% block javascript %} - - - - - - - - + + {% endblock %} {% block content %} @@ -160,17 +157,17 @@
Error:
-
+
-
+
-
+
@@ -213,17 +210,17 @@
Positioning:
- +
- +
- +
- +
diff --git a/templates/frontpage3d_mobilecontrols.html b/templates/frontpage3d_mobilecontrols.html index 2aeb7354..003496f6 100644 --- a/templates/frontpage3d_mobilecontrols.html +++ b/templates/frontpage3d_mobilecontrols.html @@ -6,9 +6,11 @@ {% endblock %} {% block javascript %} - - - + + {% endblock %} {% block content %} diff --git a/templates/frontpageText.html b/templates/frontpageText.html index 69fce26c..84aa66fd 100644 --- a/templates/frontpageText.html +++ b/templates/frontpageText.html @@ -9,9 +9,11 @@ - - - + + {% endblock %} {% block content %} diff --git a/templates/gettingStarted.html b/templates/gettingStarted.html index bbbaf9b5..b8c67cb5 100644 --- a/templates/gettingStarted.html +++ b/templates/gettingStarted.html @@ -55,5 +55,5 @@

5) Calibrate

{% endblock %} {% block javascript %} - + {% endblock %} diff --git a/templates/gpio.html b/templates/gpio.html index 2c58f3a6..71aaf196 100644 --- a/templates/gpio.html +++ b/templates/gpio.html @@ -11,7 +11,7 @@
{% if setting.type=="options" %} - {% for option in options %}
{% if setting.type=="options" %} - {% for option in options %}
- +
+
{% endblock %} {% block javascript %} - + {% endblock %} diff --git a/templates/openGCode.html b/templates/openGCode.html index 43e67c83..eb7edc12 100644 --- a/templates/openGCode.html +++ b/templates/openGCode.html @@ -4,23 +4,25 @@
- {% for directory in directories %} {% endfor %} +
- {% for file in files %} {% endfor %} +
@@ -32,11 +34,15 @@
- +
+
{% endblock %} {% block javascript %} - + {% endblock %} diff --git a/templates/opticalCalibration.html b/templates/opticalCalibration.html index 5874b48c..5b080737 100644 --- a/templates/opticalCalibration.html +++ b/templates/opticalCalibration.html @@ -5,12 +5,12 @@ {% if isMobile == true %}
- -
+
@@ -76,10 +76,10 @@

- -
+

Calibration Extents

Top Left @@ -356,12 +356,12 @@

Test Results

- -
+
@@ -370,7 +370,7 @@

Settings

-
+
@@ -471,7 +471,15 @@

Test Results

{% endblock %} {% block javascript %} - + + - + {% endblock %} diff --git a/templates/quickConfigure.html b/templates/quickConfigure.html index 278a4e72..0282ff09 100644 --- a/templates/quickConfigure.html +++ b/templates/quickConfigure.html @@ -106,10 +106,12 @@

Height of Motors Above Workspace

{% block javascript %} - - + + {% endblock %} diff --git a/templates/resetChains_mobile.html b/templates/resetChains_mobile.html index 067dbb12..26cb47cb 100644 --- a/templates/resetChains_mobile.html +++ b/templates/resetChains_mobile.html @@ -123,7 +123,9 @@

Right Motor

{% endblock %} {% block javascript %} - - - + + {% endblock %} diff --git a/templates/saveBoard.html b/templates/saveBoard.html index d3af41e1..3bb696d0 100644 --- a/templates/saveBoard.html +++ b/templates/saveBoard.html @@ -5,7 +5,7 @@
- {% for directory in directories %}
- {% for file in files %}
- +
+
{% endblock %} {% block javascript %} - + {% endblock %} diff --git a/templates/saveGCode.html b/templates/saveGCode.html index 4c7e8183..4d8d9bbd 100644 --- a/templates/saveGCode.html +++ b/templates/saveGCode.html @@ -5,7 +5,7 @@
- {% for directory in directories %}
- {% for file in files %}
- +
+
{% endblock %} {% block javascript %} - + {% endblock %} diff --git a/templates/screenAction.html b/templates/screenAction.html index e900a6fa..f6ed6073 100644 --- a/templates/screenAction.html +++ b/templates/screenAction.html @@ -25,6 +25,8 @@ {% endblock %} {% block javascript %} - - + {% endblock %} diff --git a/templates/sendGCode.html b/templates/sendGCode.html index d7791db4..26d3ad44 100644 --- a/templates/sendGCode.html +++ b/templates/sendGCode.html @@ -7,7 +7,7 @@ {% endblock %} {% block javascript %} - + - - -{% endblock %} + + +{% endblock %} \ No newline at end of file diff --git a/templates/setSprockets_mobile.html b/templates/setSprockets_mobile.html index 4a0f99a5..8b911a07 100644 --- a/templates/setSprockets_mobile.html +++ b/templates/setSprockets_mobile.html @@ -130,7 +130,9 @@

Right Motor

{% endblock %} {% block javascript %} - - - + + {% endblock %} diff --git a/templates/setZaxis.html b/templates/setZaxis.html index 5dd2b91c..c1437bac 100644 --- a/templates/setZaxis.html +++ b/templates/setZaxis.html @@ -52,7 +52,13 @@
Step 2: Move Z to Limits and save position
{% endblock %} {% block javascript %} - - - + + {% endblock %} diff --git a/templates/setZaxis_mobile.html b/templates/setZaxis_mobile.html index 78fee67e..15cd47b0 100644 --- a/templates/setZaxis_mobile.html +++ b/templates/setZaxis_mobile.html @@ -44,10 +44,14 @@
Step 2: Move Z to Limits and save position
{% endblock %} - - {% block javascript %} - - - + + {% endblock %} diff --git a/templates/settings.html b/templates/settings.html index 94cd7639..61b685e7 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -13,7 +13,7 @@

{{sett
{% if setting.key =="COMport" %}
- {% if ports|length == 0 %}
{% elif setting.type=="options" %} - {% for option in setting.options %}

@@ -60,6 +60,9 @@

{{sett {% endblock %} {% block javascript %} - - + + {% endblock %} diff --git a/templates/triangularCalibration.html b/templates/triangularCalibration.html index 1a3a0f49..c8eb5785 100644 --- a/templates/triangularCalibration.html +++ b/templates/triangularCalibration.html @@ -49,8 +49,9 @@

Parameters
{% block javascript %} - + {% endblock %} diff --git a/templates/zaxis.html b/templates/zaxis.html index 024e8c45..a4be2fb5 100644 --- a/templates/zaxis.html +++ b/templates/zaxis.html @@ -34,7 +34,13 @@ {% endblock %} {% block javascript %} - - - + + {% endblock %} diff --git a/templates/zaxis_mobile.html b/templates/zaxis_mobile.html index a22c2179..a9434a54 100644 --- a/templates/zaxis_mobile.html +++ b/templates/zaxis_mobile.html @@ -36,7 +36,13 @@ {% endblock %} {% block javascript %} - - - + + {% endblock %}