diff --git a/docs/api/connection.rst b/docs/api/connection.rst index 2f90fae3..27feb765 100644 --- a/docs/api/connection.rst +++ b/docs/api/connection.rst @@ -11,7 +11,7 @@ Connection handling Get connection settings ======================= -.. http:get:: /api/control/connection +.. http:get:: /api/connection Retrieve the current connection settings, including information regarding the available baudrates and serial ports and the current connection state. @@ -54,7 +54,7 @@ Get connection settings Issue a connection command ========================== -.. http:post:: /api/control/connection +.. http:post:: /api/connection Issue a connection command. Currently available command are: diff --git a/docs/api/fileops.rst b/docs/api/fileops.rst index c782c4c0..51c75d75 100644 --- a/docs/api/fileops.rst +++ b/docs/api/fileops.rst @@ -36,23 +36,25 @@ Retrieve all files "files": [ { "name": "whistle_v2.gcode", - "bytes": 1468987, - "size": "1.4MB", - "date": "2013-05-21 23:15", + "size": 1468987, + "date": 1378847754, "origin": "local", "refs": { "resource": "http://example.com/api/files/local/whistle_v2.gcode", "download": "http://example.com/downloads/files/local/whistle_v2.gcode" }, "gcodeAnalysis": { - "estimatedPrintTime": "00:31:40", - "filament": "0.79m" + "estimatedPrintTime": 1188, + "filament": { + "length": 810, + "volume": 5.36 + } }, "print": { "failure": 4, "success": 23, "last": { - "date": "2013-11-18 18:00", + "date": 1387144346, "success": true } } @@ -70,7 +72,7 @@ Retrieve all files :statuscode 200: No error -.. _sec-api-fileops-retrievespecific: +.. _sec-api-fileops-retrievelocation: Retrieve files from specific location ===================================== @@ -100,23 +102,25 @@ Retrieve files from specific location "files": [ { "name": "whistle_v2.gcode", - "bytes": 1468987, - "size": "1.4MB" - "date": "2013-05-21 23:15", + "size": 1468987, + "date": 1378847754, "origin": "local", "refs": { "resource": "http://example.com/api/files/local/whistle_v2.gcode", "download": "http://example.com/downloads/files/local/whistle_v2.gcode" }, "gcodeAnalysis": { - "estimatedPrintTime": "00:31:40", - "filament": "0.79m" + "estimatedPrintTime": 1188, + "filament": { + "length": 810, + "volume": 5.36 + } }, "print": { "failure": 4, "success": 23, "last": { - "date": "2013-11-18 18:00", + "date": 1387144346, "success": true } } @@ -259,23 +263,25 @@ Retrieve a specific file's information { "name": "whistle_v2.gcode", - "bytes": 1468987, - "size": "1.4MB" - "date": "2013-05-21 23:15", + "size": 1468987, + "date": 1378847754, "origin": "local", "refs": { "resource": "http://example.com/api/files/local/whistle_v2.gcode", "download": "http://example.com/downloads/files/local/whistle_v2.gcode" }, "gcodeAnalysis": { - "estimatedPrintTime": "00:31:40", - "filament": "0.79m" + "estimatedPrintTime": 1188, + "filament": { + "length": 810, + "volume": 5.36 + } }, "print": { "failure": 4, "success": 23, "last": { - "date": "2013-11-18 18:00", + "date": 1387144346, "success": true } } @@ -383,7 +389,7 @@ Retrieve response * - ``free`` - 0..1 - String - - The amount of disk space available in the local disk space (refers to OctoPrint's ``uploads`` folder). Only + - The amount of disk space in bytes available in the local disk space (refers to OctoPrint's ``uploads`` folder). Only returned if file list was requested for origin ``local`` or all origins. .. _sec-api-fileops-datamodel-uploadresponse: @@ -438,18 +444,14 @@ File information - 1 - String - The name of the file - * - ``bytes`` + * - ``size`` - 0..1 - Number - The size of the file in bytes. Only available for ``local`` files. - * - ``size`` - - 0..1 - - String - - The size of the file in a human readable format. Only available for ``local`` files. * - ``date`` - 0..1 - - String representing a date and time in the format ``YYYY-MM-DD HH:mm`` - - The date and time this files was uploaded. Only available for ``local`` files. + - Unix timestamp + - The timestamp when this file was uploaded. Only available for ``local`` files. * - ``origin`` - 1 - String, either ``local`` or ``sdcard`` @@ -457,7 +459,7 @@ File information printer's SD card (if available) * - ``refs`` - 0..1 - - :ref:`` + - :ref:`sec-api-fileops-datamodel-ref` - References relevant to this file * - ``gcodeAnalysis`` - 0..1 @@ -482,14 +484,21 @@ GCODE analysis information - Type - Description * - ``estimatedPrintTime`` - - 1 - - String representing a duration in the format ``HH:mm:ss`` - - The estimated print time of the file + - 0..1 + - Integer + - The estimated print time of the file, in seconds * - ``filament`` - - 1 - - String - - The estimated usage of filament (length in meters and volume in cubic centimeters) in a human readable format. - Example: ``1.89m / 11.90cm³`` + - 0..1 + - Object + - The estimated usage of filament + * - ``filament.length`` + - 0..1 + - Integer + - The length of filament used, in mm + * - ``filament.volume`` + - 0..1 + - Float + - The volume of filament used, in cm³ .. _sec-api-fileops-datamodel-prints: @@ -519,8 +528,8 @@ Print information - Information regarding the last print on record for the file * - ``last.date`` - 1 - - String representing a date and time in the format ``YYYY-MM-DD HH:mm`` - - Date and time when the file was printed last + - Unix timestamp + - Timestamp when this file was printed last * - ``last.success`` - 1 - Boolean diff --git a/docs/api/index.rst b/docs/api/index.rst index e7808141..8d10a202 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -11,5 +11,5 @@ API Documentation fileops.rst connection.rst printer.rst - jobs.rst + job.rst diff --git a/docs/api/job.rst b/docs/api/job.rst new file mode 100644 index 00000000..f16cdd3e --- /dev/null +++ b/docs/api/job.rst @@ -0,0 +1,248 @@ +.. _sec-api-jobs: + +************** +Job operations +************** + +.. contents:: + +.. _sec-api-jobs-command: + +Issue a job command +=================== + +.. http:post:: /api/job + + Job commands allow starting, pausing and cancelling print jobs. Available commands are: + + start + Starts the print of the currently selected file. For selecting a file, see :ref:`Issue a file command `. + If a print job is already active, a :http:statuscode:`409` will be returned. + + restart + Restart the print of the currently selected file from the beginning. There must be an active print job for this to work + and the print job must currently be paused. If either is not the case, a :http:statuscode:`409` will be returned. + + pause + Pauses/unpauses the current print job. If no print job is active (either paused or printing), a :http:statuscode:`409` + will be returned. + + cancel + Cancels the current print job. If no print job is active (either paused or printing), a :http:statuscode:`409` + will be returned. + + Upon success, a status code of :http:statuscode:`204` and an empty body is returned. + + **Example Start Request** + + .. sourcecode:: http + + POST /api/control/job HTTP/1.1 + Host: example.com + Content-Type: application/json + X-Api-Key: abcdef... + + { + "command": "start" + } + + **Example Restart Request** + + .. sourcecode:: http + + POST /api/control/job HTTP/1.1 + Host: example.com + Content-Type: application/json + X-Api-Key: abcdef... + + { + "command": "restart" + } + + **Example Pause Request** + + .. sourcecode:: http + + POST /api/control/job HTTP/1.1 + Host: example.com + Content-Type: application/json + X-Api-Key: abcdef... + + { + "command": "pause" + } + + **Example Cancel Request** + + .. sourcecode:: http + + POST /api/control/job HTTP/1.1 + Host: example.com + Content-Type: application/json + X-Api-Key: abcdef... + + { + "command": "cancel" + } + + :json string command: The command to issue, either ``start``, ``restart``, ``pause`` or ``cancel`` + :statuscode 204: No error + :statuscode 409: If the printer is not operational or the current print job state does not match the preconditions + for the command. + +.. _sec-api-job-information: + +Retrieve information about the current job +========================================== + +.. http:get:: /api/job + + Retrieve information about the current job (if there is one). + + Returns a :http:statuscode:`200` with a :ref:`sec-api-job-datamodel-response` in the body. + + **Example Request** + + .. sourcecode:: http + + GET /api/job HTTP/1.1 + Host: example.com + + **Example Response** + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "job": { + "file": { + "name": "whistle_v2.gcode", + "origin": "local", + "size": 1468987, + "date": 1378847754 + }, + "estimatedPrintTime": 8811, + "filament": { + "length": 810, + "volume": 5.36 + } + }, + "progress": { + "completion": 23, + "filepos": 337942, + "printTime": 276, + "printTimeLeft": 912 + } + } + + :statuscode 200: No error + +.. _sec-api-job-datamodel: + +Datamodel +========= + +.. _sec-api-job-datamodel-response: + +Job information response +------------------------ + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``job`` + - 1 + - :ref:`sec-api-job-datamodel-job` + - Information regarding the target of the current print job + * - ``progress`` + - 1 + - :ref:`sec-api-job-datamodel-progress` + - Information regarding the progress of the current print job + +.. _sec-api-job-datamodel-job: + +Job information +--------------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``file`` + - 1 + - Object + - The file that is the target of the current print job + * - ``file.name`` + - 1 + - String + - The file's name + * - ``file.origin`` + - 1 + - String, either ``local`` or ``sdcard`` + - The file's origin, either ``local`` or ``sdcard`` + * - ``file.size`` + - 0..1 + - Integer + - The file's size, in bytes. Only available for files stored locally. + * - ``file.date`` + - 0..1 + - Unix timestamp + - The file's upload date. Only available for files stored locally. + * - ``estimatedPrintTime`` + - 0..1 + - Integer + - The estimated print time for the file, in seconds. + * - ``filament`` + - 0..1 + - Object + - Information regarding the estimated filament usage of the print job + * - ``filament.length`` + - 0..1 + - Integer + - Length of filament used, in mm + * - ``filament.volume`` + - 0..1 + - Float + - Volume of filament used, in cm³ + +.. _sec-api-job-datamodel-progress: + +Progress information +-------------------- + +.. list-table:: + :widths: 15 5 10 30 + :header-rows: 1 + + * - Name + - Multiplicity + - Type + - Description + * - ``completion`` + - 1 + - Integer + - Percentage of completion of the current print job + * - ``filepos`` + - 1 + - Integer + - Current position in the file being printed, in bytes from the beginning + * - ``printTime`` + - 1 + - Integer + - Time already spent printing, in seconds + * - ``printTimeLeft`` + - 1 + - Integer + - Estimate of time left to print, in seconds + diff --git a/docs/api/jobs.rst b/docs/api/jobs.rst deleted file mode 100644 index f990af6a..00000000 --- a/docs/api/jobs.rst +++ /dev/null @@ -1,91 +0,0 @@ -.. _sec-api-jobs: - -*********** -Job Control -*********** - -.. contents:: - -.. _sec-api-jobs-command: - -Issue a job command -=================== - -.. http:post:: /api/control/job - - Job commands allow starting, pausing and cancelling print jobs. Available commands are: - - start - Starts the print of the currently selected file. For selecting a file, see :ref:`Issue a file command `. - If a print job is already active, a :http:statuscode:`409` will be returned. - - restart - Restart the print of the currently selected file from the beginning. There must be an active print job for this to work - and the print job must currently be paused. If either is not the case, a :http:statuscode:`409` will be returned. - - pause - Pauses/unpauses the current print job. If no print job is active (either paused or printing), a :http:statuscode:`409` - will be returned. - - cancel - Cancels the current print job. If no print job is active (either paused or printing), a :http:statuscode:`409` - will be returned. - - Upon success, a status code of :http:statuscode:`204` and an empty body is returned. - - **Example Start Request** - - .. sourcecode:: http - - POST /api/control/job HTTP/1.1 - Host: example.com - Content-Type: application/json - X-Api-Key: abcdef... - - { - "command": "start" - } - - **Example Restart Request** - - .. sourcecode:: http - - POST /api/control/job HTTP/1.1 - Host: example.com - Content-Type: application/json - X-Api-Key: abcdef... - - { - "command": "restart" - } - - **Example Pause Request** - - .. sourcecode:: http - - POST /api/control/job HTTP/1.1 - Host: example.com - Content-Type: application/json - X-Api-Key: abcdef... - - { - "command": "pause" - } - - **Example Cancel Request** - - .. sourcecode:: http - - POST /api/control/job HTTP/1.1 - Host: example.com - Content-Type: application/json - X-Api-Key: abcdef... - - { - "command": "cancel" - } - - :json string command: The command to issue, either ``start``, ``restart``, ``pause`` or ``cancel`` - :statuscode 204: No error - :statuscode 409: If the printer is not operational or the current print job state does not match the preconditions - for the command. diff --git a/docs/api/printer.rst b/docs/api/printer.rst index 94c1c732..77ab8462 100644 --- a/docs/api/printer.rst +++ b/docs/api/printer.rst @@ -1,8 +1,8 @@ .. _sec-api-printer: -*************** -Printer Control -*************** +****************** +Printer operations +****************** .. contents:: @@ -25,7 +25,7 @@ SD card Issue a print head command ========================== -.. http:post:: /api/control/printer/printhead +.. http:post:: /api/printer/printhead Print head commands allow jogging and homing the print head in all three axes. Available commands are: @@ -52,7 +52,7 @@ Issue a print head command .. sourcecode:: http - POST /api/control/printer/printhead HTTP/1.1 + POST /api/printer/printhead HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -70,7 +70,7 @@ Issue a print head command .. sourcecode:: http - POST /api/control/printer/printhead HTTP/1.1 + POST /api/printer/printhead HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -95,7 +95,7 @@ Issue a print head command Issue a heater command ====================== -.. http:post:: /api/control/printer/heater +.. http:post:: /api/printer/heater Heater commands allow setting the temperature and temperature offsets for the printer's hotend and bed. Available commands are: @@ -127,7 +127,7 @@ Issue a heater command .. sourcecode:: http - POST /api/control/printer/heater HTTP/1.1 + POST /api/printer/heater HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -146,7 +146,7 @@ Issue a heater command .. sourcecode:: http - POST /api/control/printer/heater HTTP/1.1 + POST /api/printer/heater HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -173,7 +173,7 @@ Issue a heater command Issue a feeder command ====================== -.. http:post:: /api/control/printer/feeder +.. http:post:: /api/printer/feeder Feeder commands allow extrusion/extraction of filament. Available commands are: @@ -193,7 +193,7 @@ Issue a feeder command .. sourcecode:: http - POST /api/control/printer/feeder HTTP/1.1 + POST /api/printer/feeder HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -209,7 +209,7 @@ Issue a feeder command .. sourcecode:: http - POST /api/control/printer/feeder HTTP/1.1 + POST /api/printer/feeder HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -230,7 +230,7 @@ Issue a feeder command Issue a SD command ================== -.. http:post:: /api/control/printer/sd +.. http:post:: /api/printer/sd SD commands allow initialization, refresh and release of the printer's SD card (if available). @@ -239,7 +239,7 @@ Issue a SD command init Initializes the printer's SD card, making it available for use. This also includes an initial retrieval of the list of files currently stored on the SD card, so after issueing that command a :ref:`retrieval of the files - on SD card ` will return a successful result. + on SD card ` will return a successful result. .. note:: If OctoPrint detects the availability of a SD card on the printer during connection, it will automatically attempt @@ -260,7 +260,7 @@ Issue a SD command .. sourcecode:: http - POST /api/control/printer/sd HTTP/1.1 + POST /api/printer/sd HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -273,7 +273,7 @@ Issue a SD command .. sourcecode:: http - POST /api/control/printer/sd HTTP/1.1 + POST /api/printer/sd HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -286,7 +286,7 @@ Issue a SD command .. sourcecode:: http - POST /api/control/printer/sd HTTP/1.1 + POST /api/printer/sd HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -305,7 +305,7 @@ Issue a SD command Retrieve the current SD state ============================= -.. http:get:: /api/control/printer/sd +.. http:get:: /api/printer/sd Retrieves the current state of the printer's SD card. For this request no authentication is needed. @@ -318,7 +318,7 @@ Retrieve the current SD state .. sourcecode:: http - GET /api/control/printer/sd HTTP/1.1 + GET /api/printer/sd HTTP/1.1 Host: example.com **Example Response** diff --git a/src/octoprint/events.py b/src/octoprint/events.py index 064b75f0..f9216b06 100644 --- a/src/octoprint/events.py +++ b/src/octoprint/events.py @@ -254,7 +254,7 @@ class CommandTrigger(GenericEventListener): try: processedCommand = self._processCommand(command, payload) self.executeCommand(processedCommand) - except KeyError: + except KeyError, e: self._logger.warn("There was an error processing one or more placeholders in the following command: %s" % command) def executeCommand(self, command): @@ -292,7 +292,7 @@ class CommandTrigger(GenericEventListener): params["__currentZ"] = str(currentData["currentZ"]) if "job" in currentData.keys() and currentData["job"] is not None: - params["__filename"] = currentData["job"]["filename"] + params["__filename"] = currentData["job"]["file"]["name"] if "progress" in currentData.keys() and currentData["progress"] is not None \ and "progress" in currentData["progress"].keys() and currentData["progress"]["progress"] is not None: params["__progress"] = str(round(currentData["progress"]["progress"] * 100)) diff --git a/src/octoprint/gcodefiles.py b/src/octoprint/gcodefiles.py index 22adc219..c9bed3ae 100644 --- a/src/octoprint/gcodefiles.py +++ b/src/octoprint/gcodefiles.py @@ -1,11 +1,12 @@ # coding=utf-8 +import re + __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' import os import Queue import threading -import datetime import yaml import time import logging @@ -14,6 +15,7 @@ import octoprint.util.gcodeInterpreter as gcodeInterpreter from octoprint.settings import settings from octoprint.events import eventManager, Events +from octoprint.filemanager.destinations import FileDestinations from werkzeug.utils import secure_filename @@ -107,12 +109,16 @@ class GcodeManager: analysisResult = {} dirty = False if gcode.totalMoveTimeMinute: - analysisResult["estimatedPrintTime"] = util.getFormattedTimeDelta(datetime.timedelta(minutes=gcode.totalMoveTimeMinute)) + analysisResult["estimatedPrintTime"] = gcode.totalMoveTimeMinute * 60 dirty = True if gcode.extrusionAmount: - analysisResult["filament"] = "%.2fm" % (gcode.extrusionAmount / 1000) + analysisResult["filament"] = { + "length": gcode.extrusionAmount + } if gcode.calculateVolumeCm3(): - analysisResult["filament"] += " / %.2fcm³" % gcode.calculateVolumeCm3() + analysisResult["filament"].update({ + "volume": gcode.calculateVolumeCm3() + }) dirty = True if dirty: @@ -128,9 +134,58 @@ class GcodeManager: with self._metadataFileAccessMutex: with open(self._metadataFile, "r") as f: self._metadata = yaml.safe_load(f) + if self._metadata is None: self._metadata = {} + # TODO: Remove in a couple of versions (2013-12-21) + self._migrateMetadata() + + def _migrateMetadata(self): + self._logger.info("Migrating metadata if necessary...") + + printTimeRe = r"(\d+):(\d{2}):(\d{2})" + filamentRe = r"(\d*\.\d+)m(\s/\s(\d*\.\d+)cm.)?" + + hoursToSeconds = 60 * 60 + minutesToSeconds = 60 + + updateCount = 0 + for metadata in self._metadata.values(): + if not "gcodeAnalysis" in metadata: + continue + + updated = False + if "estimatedPrintTime" in metadata["gcodeAnalysis"]: + estimatedPrintTime = metadata["gcodeAnalysis"]["estimatedPrintTime"] + if isinstance(estimatedPrintTime, (str, unicode)): + match = re.match(printTimeRe, estimatedPrintTime) + if match: + metadata["gcodeAnalysis"]["estimatedPrintTime"] = int(match.group(1)) * hoursToSeconds + int(match.group(2)) * minutesToSeconds + int(match.group(3)) + self._metadataDirty = True + updated = True + if "filament" in metadata["gcodeAnalysis"]: + filament = metadata["gcodeAnalysis"]["filament"] + if isinstance(filament, (str, unicode)): + match = re.match(filamentRe, filament) + if match: + metadata["gcodeAnalysis"]["filament"] = { + "length": int(float(match.group(1)) * 1000) + } + if match.group(3) is not None: + metadata["gcodeAnalysis"]["filament"].update({ + "volume": float(match.group(3)) + }) + self._metadataDirty = True + updated = True + + if updated: + updateCount += 1 + + self._saveMetadata() + + self._logger.info("Updated %d sets of metadata to new format" % updateCount) + def _saveMetadata(self, force=False): if not self._metadataDirty and not force: return @@ -330,9 +385,9 @@ class GcodeManager: statResult = os.stat(absolutePath) fileData = { "name": filename, - "size": util.getFormattedSize(statResult.st_size), - "bytes": statResult.st_size, - "date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(statResult.st_ctime)) + "size": statResult.st_size, + "origin": FileDestinations.LOCAL, + "date": int(statResult.st_ctime) } # enrich with additional metadata from analysis if available @@ -340,18 +395,18 @@ class GcodeManager: for key in self._metadata[filename].keys(): if key == "prints": val = self._metadata[filename][key] - formattedLast = None + last = None if "last" in val and val["last"] is not None: - formattedLast = { - "date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(val["last"]["date"])), + last = { + "date": val["last"]["date"], "success": val["last"]["success"] } - formattedPrints = { + prints = { "success": val["success"], "failure": val["failure"], - "last": formattedLast + "last": last } - fileData["prints"] = formattedPrints + fileData["prints"] = prints else: fileData[key] = self._metadata[filename][key] diff --git a/src/octoprint/printer.py b/src/octoprint/printer.py index 8f9711df..d53231f1 100644 --- a/src/octoprint/printer.py +++ b/src/octoprint/printer.py @@ -92,8 +92,20 @@ class Printer(): ) self._stateMonitor.reset( state={"state": None, "stateString": self.getStateString(), "flags": self._getStateFlags()}, - jobData={"filename": None, "filesize": None, "estimatedPrintTime": None, "filament": None}, - progress={"progress": None, "filepos": None, "printTime": None, "printTimeLeft": None}, + jobData={ + "file": { + "name": None, + "size": None, + "origin": None, + "date": None + }, + "estimatedPrintTime": None, + "filament": { + "length": None, + "volume": None + } + }, + progress={"completion": None, "filepos": None, "printTime": None, "printTimeLeft": None}, currentZ=None ) @@ -259,11 +271,7 @@ class Printer(): def _setCurrentZ(self, currentZ): self._currentZ = currentZ - - formattedCurrentZ = None - if self._currentZ: - formattedCurrentZ = "%.2f mm" % (self._currentZ) - self._stateMonitor.setCurrentZ(formattedCurrentZ) + self._stateMonitor.setCurrentZ(self._currentZ) def _setState(self, state): self._state = state @@ -282,22 +290,15 @@ class Printer(): self._printTime = printTime self._printTimeLeft = printTimeLeft - formattedPrintTime = None - if (self._printTime): - formattedPrintTime = util.getFormattedTimeDelta(datetime.timedelta(seconds=self._printTime)) - - formattedPrintTimeLeft = None - if (self._printTimeLeft): - formattedPrintTimeLeft = util.getFormattedTimeDelta(datetime.timedelta(minutes=self._printTimeLeft)) - - formattedFilePos = None - if (filepos): - formattedFilePos = util.getFormattedSize(filepos) - - self._stateMonitor.setProgress({"progress": self._progress, "filepos": formattedFilePos, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft}) + self._stateMonitor.setProgress({ + "completion": int(self._progress * 100) if self._progress is not None else None, + "filepos": filepos, + "printTime": int(self._printTime) if self._printTime is not None else None, + "printTimeLeft": int(self._printTimeLeft * 60) if self._printTimeLeft is not None else None + }) def _addTemperatureData(self, temp, bedTemp, targetTemp, bedTargetTemp): - currentTimeUtc = int(time.time() * 1000) + currentTimeUtc = int(time.time()) self._temps["actual"].append((currentTimeUtc, temp)) self._temps["target"].append((currentTimeUtc, targetTemp)) @@ -321,21 +322,14 @@ class Printer(): else: self._selectedFile = None - formattedFilename = None - formattedFilesize = None estimatedPrintTime = None - fileMTime = None + date = None filament = None if filename: - formattedFilename = os.path.basename(filename) - # Use a string for mtime because it could be float and the # javascript needs to exact match if not sd: - fileMTime = str(os.stat(filename).st_mtime) - - if filesize: - formattedFilesize = util.getFormattedSize(filesize) + date = int(os.stat(filename).st_ctime) fileData = self._gcodeManager.getFileData(filename) if fileData is not None and "gcodeAnalysis" in fileData.keys(): @@ -344,7 +338,16 @@ class Printer(): if "filament" in fileData["gcodeAnalysis"].keys(): filament = fileData["gcodeAnalysis"]["filament"] - self._stateMonitor.setJobData({"filename": formattedFilename, "filesize": formattedFilesize, "estimatedPrintTime": estimatedPrintTime, "filament": filament, "sd": sd, "mtime": fileMTime}) + self._stateMonitor.setJobData({ + "file": { + "name": os.path.basename(filename), + "origin": FileDestinations.SDCARD if sd else FileDestinations.LOCAL, + "size": filesize, + "date": date + }, + "estimatedPrintTime": estimatedPrintTime, + "filament": filament, + }) def _sendInitialStateUpdate(self, callback): try: diff --git a/src/octoprint/server/api/__init__.py b/src/octoprint/server/api/__init__.py index c72cbd8b..efc83bfa 100644 --- a/src/octoprint/server/api/__init__.py +++ b/src/octoprint/server/api/__init__.py @@ -19,7 +19,9 @@ from octoprint.settings import settings as s, valid_boolean_trues api = Blueprint("api", __name__) -from . import control as api_control +from . import printer as api_printer +from . import job as api_job +from . import connection as api_connection from . import files as api_files from . import settings as api_settings from . import timelapse as api_timelapse diff --git a/src/octoprint/server/api/connection.py b/src/octoprint/server/api/connection.py new file mode 100644 index 00000000..683c4e9d --- /dev/null +++ b/src/octoprint/server/api/connection.py @@ -0,0 +1,62 @@ +# coding=utf-8 +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + +from flask import request, jsonify, make_response + +from octoprint.settings import settings +from octoprint.printer import getConnectionOptions +from octoprint.server import printer, restricted_access, NO_CONTENT +from octoprint.server.api import api +import octoprint.util as util + + +@api.route("/connection", methods=["GET"]) +def connectionState(): + state, port, baudrate = printer.getCurrentConnection() + current = { + "state": state, + "port": port, + "baudrate": baudrate + } + return jsonify({"current": current, "options": getConnectionOptions()}) + + +@api.route("/connection", methods=["POST"]) +@restricted_access +def connectionCommand(): + valid_commands = { + "connect": ["autoconnect"], + "disconnect": [] + } + + command, data, response = util.getJsonCommandFromRequest(request, valid_commands) + if response is not None: + return response + + if command == "connect": + options = getConnectionOptions() + + port = None + baudrate = None + if "port" in data.keys(): + port = data["port"] + if port not in options["ports"]: + return make_response("Invalid port: %s" % port, 400) + if "baudrate" in data.keys(): + baudrate = data["baudrate"] + if baudrate not in options["baudrates"]: + return make_response("Invalid baudrate: %d" % baudrate, 400) + if "save" in data.keys() and data["save"]: + settings().set(["serial", "port"], port) + settings().setInt(["serial", "baudrate"], baudrate) + if "autoconnect" in data.keys(): + settings().setBoolean(["serial", "autoconnect"], data["autoconnect"]) + settings().save() + printer.connect(port=port, baudrate=baudrate) + elif command == "disconnect": + printer.disconnect() + + return NO_CONTENT + + diff --git a/src/octoprint/server/api/files.py b/src/octoprint/server/api/files.py index 8918601d..23cafad9 100644 --- a/src/octoprint/server/api/files.py +++ b/src/octoprint/server/api/files.py @@ -22,7 +22,7 @@ from octoprint.server.api import api def readGcodeFiles(): files = _getFileList(FileDestinations.LOCAL) files.extend(_getFileList(FileDestinations.SDCARD)) - return jsonify(files=files, free=util.getFormattedSize(util.getFreeBytes(settings().getBaseFolder("uploads")))) + return jsonify(files=files, free=util.getFreeBytes(settings().getBaseFolder("uploads"))) @api.route("/files/", methods=["GET"]) @@ -33,7 +33,7 @@ def readGcodeFilesForOrigin(origin): files = _getFileList(origin) if origin == FileDestinations.LOCAL: - return jsonify(files=files, free=util.getFormattedSize(util.getFreeBytes(settings().getBaseFolder("uploads")))) + return jsonify(files=files, free=util.getFreeBytes(settings().getBaseFolder("uploads"))) else: return jsonify(files=files) @@ -64,7 +64,6 @@ def _getFileList(origin): files = gcodeManager.getAllFileData() for file in files: file.update({ - "origin": FileDestinations.LOCAL, "refs": { "resource": url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=file["name"], _external=True), "download": urlForDownload(FileDestinations.LOCAL, file["name"]) diff --git a/src/octoprint/server/api/job.py b/src/octoprint/server/api/job.py new file mode 100644 index 00000000..ac8f4e54 --- /dev/null +++ b/src/octoprint/server/api/job.py @@ -0,0 +1,57 @@ +# coding=utf-8 +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + +from flask import request, make_response, jsonify + +from octoprint.server import printer, restricted_access, NO_CONTENT +from octoprint.server.api import api +import octoprint.util as util + + +@api.route("/job", methods=["POST"]) +@restricted_access +def controlJob(): + if not printer.isOperational(): + return make_response("Printer is not operational", 409) + + valid_commands = { + "start": [], + "restart": [], + "pause": [], + "cancel": [] + } + + command, data, response = util.getJsonCommandFromRequest(request, valid_commands) + if response is not None: + return response + + activePrintjob = printer.isPrinting() or printer.isPaused() + + if command == "start": + if activePrintjob: + return make_response("Printer already has an active print job, did you mean 'restart'?", 409) + printer.startPrint() + elif command == "restart": + if not printer.isPaused(): + return make_response("Printer does not have an active print job or is not paused", 409) + printer.startPrint() + elif command == "pause": + if not activePrintjob: + return make_response("Printer is neither printing nor paused, 'pause' command cannot be performed", 409) + printer.togglePausePrint() + elif command == "cancel": + if not activePrintjob: + return make_response("Printer is neither printing nor paused, 'cancel' command cannot be performed", 409) + printer.cancelPrint() + return NO_CONTENT + + +@api.route("/job", methods=["GET"]) +def jobState(): + currentData = printer.getCurrentData() + return jsonify({ + "job": currentData["job"], + "progress": currentData["progress"], + "state": currentData["state"]["stateString"] + }) \ No newline at end of file diff --git a/src/octoprint/server/api/control.py b/src/octoprint/server/api/printer.py similarity index 70% rename from src/octoprint/server/api/control.py rename to src/octoprint/server/api/printer.py index b5171849..46e35cbe 100644 --- a/src/octoprint/server/api/control.py +++ b/src/octoprint/server/api/printer.py @@ -11,128 +11,10 @@ from octoprint.server.api import api import octoprint.util as util -#~~ Printer control +#~~ Heater -@api.route("/control/connection", methods=["GET"]) -def connectionState(): - state, port, baudrate = printer.getCurrentConnection() - current = { - "state": state, - "port": port, - "baudrate": baudrate - } - return jsonify({"current": current, "options": getConnectionOptions()}) - - -@api.route("/control/connection", methods=["POST"]) -@restricted_access -def connectionCommand(): - valid_commands = { - "connect": ["autoconnect"], - "disconnect": [] - } - - command, data, response = util.getJsonCommandFromRequest(request, valid_commands) - if response is not None: - return response - - if command == "connect": - options = getConnectionOptions() - - port = None - baudrate = None - if "port" in data.keys(): - port = data["port"] - if port not in options["ports"]: - return make_response("Invalid port: %s" % port, 400) - if "baudrate" in data.keys(): - baudrate = data["baudrate"] - if baudrate not in options["baudrates"]: - return make_response("Invalid baudrate: %d" % baudrate, 400) - if "save" in data.keys() and data["save"]: - settings().set(["serial", "port"], port) - settings().setInt(["serial", "baudrate"], baudrate) - if "autoconnect" in data.keys(): - settings().setBoolean(["serial", "autoconnect"], data["autoconnect"]) - settings().save() - printer.connect(port=port, baudrate=baudrate) - elif command == "disconnect": - printer.disconnect() - - return NO_CONTENT - - -@api.route("/control/printer/command", methods=["POST"]) -@restricted_access -def printerCommand(): - # TODO: document me - if not printer.isOperational(): - return make_response("Printer is not operational", 409) - - if not "application/json" in request.headers["Content-Type"]: - return make_response("Expected content type JSON", 400) - - data = request.json - - parameters = {} - if "parameters" in data.keys(): parameters = data["parameters"] - - commands = [] - if "command" in data.keys(): commands = [data["command"]] - elif "commands" in data.keys(): commands = data["commands"] - - commandsToSend = [] - for command in commands: - commandToSend = command - if len(parameters) > 0: - commandToSend = command % parameters - commandsToSend.append(commandToSend) - - printer.commands(commandsToSend) - - return NO_CONTENT - - -@api.route("/control/job", methods=["POST"]) -@restricted_access -def controlJob(): - if not printer.isOperational(): - return make_response("Printer is not operational", 409) - - valid_commands = { - "start": [], - "restart": [], - "pause": [], - "cancel": [] - } - - command, data, response = util.getJsonCommandFromRequest(request, valid_commands) - if response is not None: - return response - - activePrintjob = printer.isPrinting() or printer.isPaused() - - if command == "start": - if activePrintjob: - return make_response("Printer already has an active print job, did you mean 'restart'?", 409) - printer.startPrint() - elif command == "restart": - if not printer.isPaused(): - return make_response("Printer does not have an active print job or is not paused", 409) - printer.startPrint() - elif command == "pause": - if not activePrintjob: - return make_response("Printer is neither printing nor paused, 'pause' command cannot be performed", 409) - printer.togglePausePrint() - elif command == "cancel": - if not activePrintjob: - return make_response("Printer is neither printing nor paused, 'cancel' command cannot be performed", 409) - printer.cancelPrint() - return NO_CONTENT - - -@api.route("/control/printer/heater", methods=["POST"]) +@api.route("/printer/heater", methods=["POST"]) @restricted_access def controlPrinterHotend(): if not printer.isOperational(): @@ -194,7 +76,10 @@ def controlPrinterHotend(): return NO_CONTENT -@api.route("/control/printer/printhead", methods=["POST"]) +##~~ Print head + + +@api.route("/printer/printhead", methods=["POST"]) @restricted_access def controlPrinterPrinthead(): if not printer.isOperational() or printer.isPrinting(): @@ -244,7 +129,10 @@ def controlPrinterPrinthead(): return NO_CONTENT -@api.route("/control/printer/feeder", methods=["POST"]) +##~~ Feeder + + +@api.route("/printer/feeder", methods=["POST"]) @restricted_access def controlPrinterFeeder(): if not printer.isOperational() or printer.isPrinting(): @@ -270,14 +158,11 @@ def controlPrinterFeeder(): return NO_CONTENT -@api.route("/control/custom", methods=["GET"]) -def getCustomControls(): - # TODO: document me - customControls = settings().get(["controls"]) - return jsonify(controls=customControls) + +##~~ SD Card -@api.route("/control/printer/sd", methods=["POST"]) +@api.route("/printer/sd", methods=["POST"]) @restricted_access def sdCommand(): if not settings().getBoolean(["feature", "sdSupport"]): @@ -304,10 +189,52 @@ def sdCommand(): return NO_CONTENT -@api.route("/control/printer/sd", methods=["GET"]) +@api.route("/printer/sd", methods=["GET"]) def sdState(): if not settings().getBoolean(["feature", "sdSupport"]): return make_response("SD support is disabled", 404) return jsonify(ready=printer.isSdReady()) + +##~~ Commands + + +@api.route("/printer/command", methods=["POST"]) +@restricted_access +def printerCommand(): + # TODO: document me + if not printer.isOperational(): + return make_response("Printer is not operational", 409) + + if not "application/json" in request.headers["Content-Type"]: + return make_response("Expected content type JSON", 400) + + data = request.json + + parameters = {} + if "parameters" in data.keys(): parameters = data["parameters"] + + commands = [] + if "command" in data.keys(): commands = [data["command"]] + elif "commands" in data.keys(): commands = data["commands"] + + commandsToSend = [] + for command in commands: + commandToSend = command + if len(parameters) > 0: + commandToSend = command % parameters + commandsToSend.append(commandToSend) + + printer.commands(commandsToSend) + + return NO_CONTENT + + +@api.route("/printer/command/custom", methods=["GET"]) +def getCustomControls(): + # TODO: document me + customControls = settings().get(["controls"]) + return jsonify(controls=customControls) + + diff --git a/src/octoprint/static/js/app/helpers.js b/src/octoprint/static/js/app/helpers.js index f297266d..9663a419 100644 --- a/src/octoprint/static/js/app/helpers.js +++ b/src/octoprint/static/js/app/helpers.js @@ -277,3 +277,34 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor self._loadCurrentFiltersFromLocalStorage(); self._loadCurrentSortingFromLocalStorage(); } + +function formatSize(bytes) { + var units = ["bytes", "KB", "MB", "GB"]; + for (var i = 0; i < units.length; i++) { + if (bytes < 1024) { + return _.sprintf("%3.1f%s", bytes, units[i]); + } + bytes /= 1024; + } + return _.sprintf("%.1f%s", bytes, "TB"); +} + +function formatDuration(seconds) { + var s = seconds % 60; + var m = (seconds % 3600) / 60; + var h = seconds / 3600; + + return _.sprintf("%02d:%02d:%02d", h, m, s); +} + +function formatDate(unixTimestamp) { + return moment.unix(unixTimestamp).format("YYYY-MM-DD HH:mm"); +} + +function formatFilament(filament) { + var result = _.sprintf("%.02fm", (filament["length"] / 1000)); + if (filament.hasOwnProperty("volume")) { + result += " / " + _.sprintf("%.02fcm³", filament["volume"]); + } + return result; +} diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index 9e0fe6e6..b0963ef9 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -264,6 +264,10 @@ $(function() { //~~ Offline overlay $("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()}); + //~~ Underscore setup + + _.mixin(_.str.exports()); + //~~ knockout.js bindings ko.bindingHandlers.popover = { diff --git a/src/octoprint/static/js/app/viewmodels/connection.js b/src/octoprint/static/js/app/viewmodels/connection.js index c4707945..0bc791a2 100644 --- a/src/octoprint/static/js/app/viewmodels/connection.js +++ b/src/octoprint/static/js/app/viewmodels/connection.js @@ -30,7 +30,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) { self.requestData = function() { $.ajax({ - url: API_BASEURL + "control/connection", + url: API_BASEURL + "connection", method: "GET", dataType: "json", success: function(response) { @@ -100,7 +100,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) { data["save"] = true; $.ajax({ - url: API_BASEURL + "control/connection", + url: API_BASEURL + "connection", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", @@ -112,7 +112,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) { } else { self.requestData(); $.ajax({ - url: API_BASEURL + "control/connection", + url: API_BASEURL + "connection", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", diff --git a/src/octoprint/static/js/app/viewmodels/control.js b/src/octoprint/static/js/app/viewmodels/control.js index 40d0978e..286f299e 100644 --- a/src/octoprint/static/js/app/viewmodels/control.js +++ b/src/octoprint/static/js/app/viewmodels/control.js @@ -43,7 +43,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) { self.requestData = function() { $.ajax({ - url: API_BASEURL + "control/custom", + url: API_BASEURL + "printer/command/custom", method: "GET", dataType: "json", success: function(response) { @@ -90,7 +90,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) { data[axis] = distance * multiplier; $.ajax({ - url: API_BASEURL + "control/printer/printhead", + url: API_BASEURL + "printer/printhead", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", @@ -105,7 +105,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) { } $.ajax({ - url: API_BASEURL + "control/printer/printhead", + url: API_BASEURL + "printer/printhead", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", @@ -127,7 +127,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) { length = 5; $.ajax({ - url: API_BASEURL + "control/printer/feeder", + url: API_BASEURL + "printer/feeder", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", @@ -160,7 +160,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) { return; $.ajax({ - url: API_BASEURL + "control/printer/command", + url: API_BASEURL + "printer/command", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", diff --git a/src/octoprint/static/js/app/viewmodels/files.js b/src/octoprint/static/js/app/viewmodels/files.js index 2be71d1e..ce110556 100644 --- a/src/octoprint/static/js/app/viewmodels/files.js +++ b/src/octoprint/static/js/app/viewmodels/files.js @@ -14,6 +14,11 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) { self.isSdReady = ko.observable(undefined); self.freeSpace = ko.observable(undefined); + self.freeSpaceString = ko.computed(function() { + if (!self.freeSpace()) + return "-"; + return formatSize(self.freeSpace()); + }); // initialize list helper self.listHelper = new ItemListHelper( @@ -174,7 +179,7 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) { self._sendSdCommand = function(command) { $.ajax({ - url: API_BASEURL + "control/printer/sd", + url: API_BASEURL + "printer/sd", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", @@ -183,16 +188,18 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) { } self.getPopoverContent = function(data) { - var output = "

Uploaded: " + data["date"] + "

"; + var output = "

Uploaded: " + formatDate(data["date"]) + "

"; if (data["gcodeAnalysis"]) { output += "

"; - output += "Filament: " + data["gcodeAnalysis"]["filament"] + "
"; - output += "Estimated Print Time: " + data["gcodeAnalysis"]["estimatedPrintTime"]; + if (data["gcodeAnalysis"]["filament"]) { + output += "Filament: " + formatFilament(data["gcodeAnalysis"]["filament"]) + "
"; + } + output += "Estimated Print Time: " + formatDuration(data["gcodeAnalysis"]["estimatedPrintTime"]); output += "

"; } if (data["prints"] && data["prints"]["last"]) { output += "

"; - output += "Last Print: " + data["prints"]["last"]["date"] + ""; + output += "Last Print: " + formatDate(data["prints"]["last"]["date"]) + ""; output += "

"; } return output; diff --git a/src/octoprint/static/js/app/viewmodels/gcode.js b/src/octoprint/static/js/app/viewmodels/gcode.js index 9c0c28f9..8a6845a5 100644 --- a/src/octoprint/static/js/app/viewmodels/gcode.js +++ b/src/octoprint/static/js/app/viewmodels/gcode.js @@ -4,7 +4,7 @@ function GcodeViewModel(loginStateViewModel) { self.loginState = loginStateViewModel; self.loadedFilename = undefined; - self.loadedFileMTime = undefined; + self.loadedFileDate = undefined; self.status = 'idle'; self.enabled = false; @@ -15,18 +15,18 @@ function GcodeViewModel(loginStateViewModel) { GCODE.ui.initHandlers(); } - self.loadFile = function(filename, mtime){ + self.loadFile = function(filename, date){ if (self.status == 'idle' && self.errorCount < 3) { self.status = 'request'; $.ajax({ url: BASEURL + "downloads/files/local/" + filename, - data: { "mtime": mtime }, + data: { "ctime": date }, type: "GET", success: function(response, rstatus) { if(rstatus === 'success'){ self.showGCodeViewer(response, rstatus); - self.loadedFilename=filename; - self.loadedFileMTime=mtime; + self.loadedFilename = filename; + self.loadedFileDate = date; self.status = 'idle'; } }, @@ -55,20 +55,20 @@ function GcodeViewModel(loginStateViewModel) { self._processData = function(data) { if (!self.enabled) return; - if (!data.job.filename) return; + if (!data.job.file || !data.job.file.name) return; - if(self.loadedFilename && self.loadedFilename == data.job.filename && - self.loadedFileMTime == data.job.mtime) { + if(self.loadedFilename && self.loadedFilename == data.job.file.name && + self.loadedFileDate == data.job.file.date) { if (data.state.flags && (data.state.flags.printing || data.state.flags.paused)) { - var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.progress * 100); + var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.completion); if(cmdIndex){ GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd); GCODE.ui.updateLayerInfo(cmdIndex.layer); } } self.errorCount = 0 - } else if (data.job.filename && !data.job.sd) { - self.loadFile(data.job.filename, data.job.mtime); + } else if (data.job.file.name && data.job.file.origin != "sdcard") { + self.loadFile(data.job.file.name, data.job.file.date); } } diff --git a/src/octoprint/static/js/app/viewmodels/printerstate.js b/src/octoprint/static/js/app/viewmodels/printerstate.js index a13d0464..060ccd3a 100644 --- a/src/octoprint/static/js/app/viewmodels/printerstate.js +++ b/src/octoprint/static/js/app/viewmodels/printerstate.js @@ -27,16 +27,36 @@ function PrinterStateViewModel(loginStateViewModel) { self.currentHeight = ko.observable(undefined); + self.estimatedPrintTimeString = ko.computed(function() { + if (!self.estimatedPrintTime()) + return "-"; + return formatDuration(self.estimatedPrintTime()); + }); + self.filamentString = ko.computed(function() { + if (!self.filament()) + return "-"; + return formatFilament(self.filament()); + }); self.byteString = ko.computed(function() { if (!self.filesize()) return "-"; - var filepos = self.filepos() ? self.filepos() : "-"; - return filepos + " / " + self.filesize(); + var filepos = self.filepos() ? formatSize(self.filepos()) : "-"; + return filepos + " / " + formatSize(self.filesize()); }); self.heightString = ko.computed(function() { if (!self.currentHeight()) return "-"; - return self.currentHeight(); + return _.sprintf("%.02fmm", self.currentHeight()); + }); + self.printTimeString = ko.computed(function() { + if (!self.printTime()) + return "-"; + return formatDuration(self.printTime()); + }); + self.printTimeLeftString = ko.computed(function() { + if (!self.printTimeLeft()) + return "-"; + return formatDuration(self.printTimeLeft()); }) self.progressString = ko.computed(function() { if (!self.progress()) @@ -97,16 +117,22 @@ function PrinterStateViewModel(loginStateViewModel) { } self._processJobData = function(data) { - self.filename(data.filename); - self.filesize(data.filesize); + if (data.file) { + self.filename(data.file.name); + self.filesize(data.file.size); + self.sd(data.file.origin == "sdcard"); + } else { + self.filename(undefined); + self.filesize(undefined); + self.sd(undefined); + } self.estimatedPrintTime(data.estimatedPrintTime); self.filament(data.filament); - self.sd(data.sd); } self._processProgressData = function(data) { - if (data.progress) { - self.progress(Math.round(data.progress * 100)); + if (data.completion) { + self.progress(data.completion); } else { self.progress(undefined); } @@ -145,7 +171,7 @@ function PrinterStateViewModel(loginStateViewModel) { self._jobCommand = function(command) { $.ajax({ - url: API_BASEURL + "control/job", + url: API_BASEURL + "job", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", diff --git a/src/octoprint/static/js/app/viewmodels/temperature.js b/src/octoprint/static/js/app/viewmodels/temperature.js index 2bc97899..c962ad61 100644 --- a/src/octoprint/static/js/app/viewmodels/temperature.js +++ b/src/octoprint/static/js/app/viewmodels/temperature.js @@ -126,10 +126,10 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { _.each(data, function(d) { var time = d.currentTime; - self.temperatures.actual.push([time, d.temp]); - self.temperatures.target.push([time, d.targetTemp]); - self.temperatures.actualBed.push([time, d.bedTemp]); - self.temperatures.targetBed.push([time, d.targetBedTemp]); + self.temperatures.actual.push([time * 1000, d.temp]); + self.temperatures.target.push([time * 1000, d.targetTemp]); + self.temperatures.actualBed.push([time * 1000, d.bedTemp]); + self.temperatures.targetBed.push([time * 1000, d.targetBedTemp]); }); self.temperatures.actual = self.temperatures.actual.slice(-300); @@ -141,7 +141,17 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { } self._processTemperatureHistoryData = function(data) { - self.temperatures = data; + var toJsTimestamp = function(d) { + return [d[0] * 1000, d[1]]; + } + + var processedData = { + actual: _.map(data.actual, toJsTimestamp), + target: _.map(data.target, toJsTimestamp), + actualBed: _.map(data.actualBed, toJsTimestamp), + targetBed: _.map(data.targetBed, toJsTimestamp) + }; + self.temperatures = processedData; self.updatePlot(); } @@ -217,7 +227,7 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { data[group][type] = parseInt(temp); $.ajax({ - url: API_BASEURL + "control/printer/heater", + url: API_BASEURL + "printer/heater", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", diff --git a/src/octoprint/static/js/app/viewmodels/terminal.js b/src/octoprint/static/js/app/viewmodels/terminal.js index 3403e8a7..d4bc5b0b 100644 --- a/src/octoprint/static/js/app/viewmodels/terminal.js +++ b/src/octoprint/static/js/app/viewmodels/terminal.js @@ -99,7 +99,7 @@ function TerminalViewModel(loginStateViewModel, settingsViewModel) { if (command) { $.ajax({ - url: API_BASEURL + "control/printer/command", + url: API_BASEURL + "printer/command", type: "POST", dataType: "json", contentType: "application/json; charset=UTF-8", diff --git a/src/octoprint/static/js/lib/moment.min.js b/src/octoprint/static/js/lib/moment.min.js new file mode 100644 index 00000000..568ad05c --- /dev/null +++ b/src/octoprint/static/js/lib/moment.min.js @@ -0,0 +1,6 @@ +//! moment.js +//! version : 2.4.0 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +(function(a){function b(a,b){return function(c){return i(a.call(this,c),b)}}function c(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function d(){}function e(a){u(a),g(this,a)}function f(a){var b=o(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._input=a,this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function g(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function h(a){return 0>a?Math.ceil(a):Math.floor(a)}function i(a,b){for(var c=a+"";c.lengthd;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function n(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Kb[a]||Lb[b]||b}return a}function o(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=n(c),b&&(d[b]=a[c]));return d}function p(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}bb[b]=function(e,f){var g,h,i=bb.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=bb().utc().set(d,a);return i.call(bb.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function r(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function s(a){return t(a)?366:365}function t(a){return 0===a%4&&0!==a%100||0===a%400}function u(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[gb]<0||a._a[gb]>11?gb:a._a[hb]<1||a._a[hb]>r(a._a[fb],a._a[gb])?hb:a._a[ib]<0||a._a[ib]>23?ib:a._a[jb]<0||a._a[jb]>59?jb:a._a[kb]<0||a._a[kb]>59?kb:a._a[lb]<0||a._a[lb]>999?lb:-1,a._pf._overflowDayOfYear&&(fb>b||b>hb)&&(b=hb),a._pf.overflow=b)}function v(a){a._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function w(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function x(a){return a?a.toLowerCase().replace("_","-"):a}function y(a,b){return b.abbr=a,mb[a]||(mb[a]=new d),mb[a].set(b),mb[a]}function z(a){delete mb[a]}function A(a){var b,c,d,e,f=0,g=function(a){if(!mb[a]&&nb)try{require("./lang/"+a)}catch(b){}return mb[a]};if(!a)return bb.fn._lang;if(!k(a)){if(c=g(a))return c;a=[a]}for(;f0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&m(e,d,!0)>=b-1)break;b--}f++}return bb.fn._lang}function B(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function C(a){var b,c,d=a.match(rb);for(b=0,c=d.length;c>b;b++)d[b]=Pb[d[b]]?Pb[d[b]]:B(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function D(a,b){return a.isValid()?(b=E(b,a.lang()),Mb[b]||(Mb[b]=C(b)),Mb[b](a)):a.lang().invalidDate()}function E(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(sb.lastIndex=0;d>=0&&sb.test(a);)a=a.replace(sb,c),sb.lastIndex=0,d-=1;return a}function F(a,b){var c;switch(a){case"DDDD":return vb;case"YYYY":case"GGGG":case"gggg":return wb;case"YYYYY":case"GGGGG":case"ggggg":return xb;case"S":case"SS":case"SSS":case"DDD":return ub;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return zb;case"a":case"A":return A(b._l)._meridiemParse;case"X":return Cb;case"Z":case"ZZ":return Ab;case"T":return Bb;case"SSSS":return yb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"ww":case"W":case"WW":case"e":case"E":return tb;default:return c=new RegExp(N(M(a.replace("\\","")),"i"))}}function G(a){var b=(Ab.exec(a)||[])[0],c=(b+"").match(Hb)||["-",0,0],d=+(60*c[1])+q(c[2]);return"+"===c[0]?-d:d}function H(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[gb]=q(b)-1);break;case"MMM":case"MMMM":d=A(c._l).monthsParse(b),null!=d?e[gb]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[hb]=q(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=q(b));break;case"YY":e[fb]=q(b)+(q(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":e[fb]=q(b);break;case"a":case"A":c._isPm=A(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[ib]=q(b);break;case"m":case"mm":e[jb]=q(b);break;case"s":case"ss":e[kb]=q(b);break;case"S":case"SS":case"SSS":case"SSSS":e[lb]=q(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=G(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function I(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=K(a),a._w&&null==a._a[hb]&&null==a._a[gb]&&(f=function(b){return b?b.length<3?parseInt(b,10)>68?"19"+b:"20"+b:b:null==a._a[fb]?bb().weekYear():a._a[fb]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=X(f(g.GG),g.W||1,g.E,4,1):(i=A(a._l),j=null!=g.d?T(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&js(e)&&(a._pf._overflowDayOfYear=!0),c=S(e,0,a._dayOfYear),a._a[gb]=c.getUTCMonth(),a._a[hb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[ib]+=q((a._tzm||0)/60),l[jb]+=q((a._tzm||0)%60),a._d=(a._useUTC?S:R).apply(null,l)}}function J(a){var b;a._d||(b=o(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],I(a))}function K(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function L(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=A(a._l),h=""+a._i,i=h.length,j=0;for(d=E(a._f,g).match(rb)||[],b=0;b0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Pb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),H(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[ib]<12&&(a._a[ib]+=12),a._isPm===!1&&12===a._a[ib]&&(a._a[ib]=0),I(a),u(a)}function M(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function N(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function O(a){var b,c,d,e,f;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function P(a){var b,c=a._i,d=Db.exec(c);if(d){for(a._pf.iso=!0,b=4;b>0;b--)if(d[b]){a._f=Fb[b-1]+(d[6]||" ");break}for(b=0;4>b;b++)if(Gb[b][1].exec(c)){a._f+=Gb[b][0];break}Ab.exec(c)&&(a._f+="Z"),L(a)}else a._d=new Date(c)}function Q(b){var c=b._i,d=ob.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?P(b):k(c)?(b._a=c.slice(0),I(b)):l(c)?b._d=new Date(+c):"object"==typeof c?J(b):b._d=new Date(c)}function R(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function S(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function T(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function U(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function V(a,b,c){var d=eb(Math.abs(a)/1e3),e=eb(d/60),f=eb(e/60),g=eb(f/24),h=eb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",eb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,U.apply({},i)}function W(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=bb(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function X(a,b,c,d,e){var f,g,h=new Date(Date.UTC(a,0)).getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:s(a-1)+g}}function Y(a){var b=a._i,c=a._f;return"undefined"==typeof a._pf&&v(a),null===b?bb.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=A().preparse(b)),bb.isMoment(b)?(a=g({},b),a._d=new Date(+b._d)):c?k(c)?O(a):L(a):Q(a),new e(a))}function Z(a,b){bb.fn[a]=bb.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),bb.updateOffset(this),this):this._d["get"+c+b]()}}function $(a){bb.duration.fn[a]=function(){return this._data[a]}}function _(a,b){bb.duration.fn["as"+a]=function(){return+this/b}}function ab(a){var b=!1,c=bb;"undefined"==typeof ender&&(this.moment=a?function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)}:bb)}for(var bb,cb,db="2.4.0",eb=Math.round,fb=0,gb=1,hb=2,ib=3,jb=4,kb=5,lb=6,mb={},nb="undefined"!=typeof module&&module.exports,ob=/^\/?Date\((\-?\d+)/i,pb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,qb=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,rb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,sb=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,tb=/\d\d?/,ub=/\d{1,3}/,vb=/\d{3}/,wb=/\d{1,4}/,xb=/[+\-]?\d{1,6}/,yb=/\d+/,zb=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Ab=/Z|[\+\-]\d\d:?\d\d/i,Bb=/T/i,Cb=/[\+\-]?\d+(\.\d{1,3})?/,Db=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d:?\d\d|Z)?)?$/,Eb="YYYY-MM-DDTHH:mm:ssZ",Fb=["YYYY-MM-DD","GGGG-[W]WW","GGGG-[W]WW-E","YYYY-DDD"],Gb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Hb=/([\+\-]|\d\d)/gi,Ib="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Jb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Kb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Lb={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Mb={},Nb="DDD w W M D d".split(" "),Ob="M D H h m s w W".split(" "),Pb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return i(this.year()%100,2)},YYYY:function(){return i(this.year(),4)},YYYYY:function(){return i(this.year(),5)},gg:function(){return i(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return i(this.weekYear(),5)},GG:function(){return i(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return i(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return q(this.milliseconds()/100)},SS:function(){return i(q(this.milliseconds()/10),2)},SSS:function(){return i(this.milliseconds(),3)},SSSS:function(){return i(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+":"+i(q(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(10*a/6),4)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()}},Qb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Nb.length;)cb=Nb.pop(),Pb[cb+"o"]=c(Pb[cb],cb);for(;Ob.length;)cb=Ob.pop(),Pb[cb+cb]=b(Pb[cb],2);for(Pb.DDDD=b(Pb.DDD,3),g(d.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=bb.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=bb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return W(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),bb=function(b,c,d,e){return"boolean"==typeof d&&(e=d,d=a),Y({_i:b,_f:c,_l:d,_strict:e,_isUTC:!1})},bb.utc=function(b,c,d,e){var f;return"boolean"==typeof d&&(e=d,d=a),f=Y({_useUTC:!0,_isUTC:!0,_l:d,_i:b,_f:c,_strict:e}).utc()},bb.unix=function(a){return bb(1e3*a)},bb.duration=function(a,b){var c,d,e,g=bb.isDuration(a),h="number"==typeof a,i=g?a._input:h?{}:a,j=null;return h?b?i[b]=a:i.milliseconds=a:(j=pb.exec(a))?(c="-"===j[1]?-1:1,i={y:0,d:q(j[hb])*c,h:q(j[ib])*c,m:q(j[jb])*c,s:q(j[kb])*c,ms:q(j[lb])*c}):(j=qb.exec(a))&&(c="-"===j[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},i={y:e(j[2]),M:e(j[3]),d:e(j[4]),h:e(j[5]),m:e(j[6]),s:e(j[7]),w:e(j[8])}),d=new f(i),g&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},bb.version=db,bb.defaultFormat=Eb,bb.updateOffset=function(){},bb.lang=function(a,b){var c;return a?(b?y(x(a),b):null===b?(z(a),a="en"):mb[a]||A(a),c=bb.duration.fn._lang=bb.fn._lang=A(a),c._abbr):bb.fn._lang._abbr},bb.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),A(a)},bb.isMoment=function(a){return a instanceof e},bb.isDuration=function(a){return a instanceof f},cb=Qb.length-1;cb>=0;--cb)p(Qb[cb]);for(bb.normalizeUnits=function(a){return n(a)},bb.invalid=function(a){var b=bb.utc(0/0);return null!=a?g(b._pf,a):b._pf.userInvalidated=!0,b},bb.parseZone=function(a){return bb(a).parseZone()},g(bb.fn=e.prototype,{clone:function(){return bb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){return D(bb(this).utc(),"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds()]},isValid:function(){return w(this)},isDSTShifted:function(){return this._a?this.isValid()&&m(this._a,(this._isUTC?bb.utc(this._a):bb(this._a)).toArray())>0:!1},parsingFlags:function(){return g({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=D(this,a||bb.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,-1),this},diff:function(a,b,c){var d,e,f=this._isUTC?bb(a).zone(this._offset||0):bb(a).local(),g=6e4*(this.zone()-f.zone());return b=n(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-bb(this).startOf("month")-(f-bb(f).startOf("month")))/d,e-=6e4*(this.zone()-bb(this).startOf("month").zone()-(f.zone()-bb(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:h(e)},from:function(a,b){return bb.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(bb(),a)},calendar:function(){var a=this.diff(bb().zone(this.zone()).startOf("day"),"days",!0),b=-6>a?"sameElse":-1>a?"lastWeek":0>a?"lastDay":1>a?"sameDay":2>a?"nextDay":7>a?"nextWeek":"sameElse";return this.format(this.lang().calendar(b,this))},isLeapYear:function(){return t(this.year())},isDST:function(){return this.zone()+bb(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+bb(a).startOf(b)},isSame:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)===+bb(a).startOf(b)},min:function(a){return a=bb.apply(null,arguments),this>a?this:a},max:function(a){return a=bb.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=G(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&j(this,bb.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?bb(a).zone():0,0===(this.zone()-a)%60},daysInMonth:function(){return r(this.year(),this.month())},dayOfYear:function(a){var b=eb((bb(this).startOf("day")-bb(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},weekYear:function(a){var b=W(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=W(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=W(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=n(a),this[a]()},set:function(a,b){return a=n(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=A(b),this)}}),cb=0;cbu;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;ae||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={},i=null==r?j.identity:k(r);return A(t,function(r,a){var o=i.call(e,r,a,t);n(u,o,r)}),u}};j.groupBy=F(function(n,t,r){(j.has(n,t)?n[t]:n[t]=[]).push(r)}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=null==r?j.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o;return function(){i=this,u=arguments,a=new Date;var c=function(){var l=new Date-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u)))},l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u)),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=w||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); +//# sourceMappingURL=underscore-min.map \ No newline at end of file diff --git a/src/octoprint/static/js/lib/underscore.js b/src/octoprint/static/js/lib/underscore.js deleted file mode 100644 index c1d9d3ae..00000000 --- a/src/octoprint/static/js/lib/underscore.js +++ /dev/null @@ -1 +0,0 @@ -(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); \ No newline at end of file diff --git a/src/octoprint/static/js/lib/underscore.string.min.js b/src/octoprint/static/js/lib/underscore.string.min.js new file mode 100644 index 00000000..5248a6ec --- /dev/null +++ b/src/octoprint/static/js/lib/underscore.string.min.js @@ -0,0 +1 @@ +!function(e,t){"use strict";var n=t.prototype.trim,r=t.prototype.trimRight,i=t.prototype.trimLeft,s=function(e){return e*1||0},o=function(e,t){if(t<1)return"";var n="";while(t>0)t&1&&(n+=e),t>>=1,e+=e;return n},u=[].slice,a=function(e){return e==null?"\\s":e.source?e.source:"["+p.escapeRegExp(e)+"]"},f={lt:"<",gt:">",quot:'"',apos:"'",amp:"&"},l={};for(var c in f)l[f[c]]=c;var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o,r=function(){return r.cache.hasOwnProperty(arguments[0])||(r.cache[arguments[0]]=r.parse(arguments[0])),r.format.call(null,r.cache[arguments[0]],arguments)};return r.format=function(r,i){var s=1,o=r.length,u="",a,f=[],l,c,p,d,v,m;for(l=0;l=0?"+"+a:a,v=p[4]?p[4]=="0"?"0":p[4].charAt(1):" ",m=p[6]-t(a).length,d=p[6]?n(v,m):"",f.push(p[5]?a+d:d+a)}}return f.join("")},r.cache={},r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push("%");else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw new Error("[_.sprintf] huh?");if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1]);while((o=o.substring(u[0].length))!=="")if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw new Error("[_.sprintf] mixing positional and named placeholders is not (yet) supported");r.push(n)}t=t.substring(n[0].length)}return r},r}(),p={VERSION:"2.3.0",isBlank:function(e){return e==null&&(e=""),/^\s*$/.test(e)},stripTags:function(e){return e==null?"":t(e).replace(/<\/?[^>]+>/g,"")},capitalize:function(e){return e=e==null?"":t(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){return e==null?[]:(e=t(e),n=~~n,n>0?e.match(new RegExp(".{1,"+n+"}","g")):[e])},clean:function(e){return p.strip(e).replace(/\s+/g," ")},count:function(e,n){return e==null||n==null?0:t(e).split(n).length-1},chars:function(e){return e==null?[]:t(e).split("")},swapCase:function(e){return e==null?"":t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return e==null?"":t(e).replace(/[&<>"']/g,function(e){return"&"+l[e]+";"})},unescapeHTML:function(e){return e==null?"":t(e).replace(/\&([^;]+);/g,function(e,n){var r;return n in f?f[n]:(r=n.match(/^#x([\da-fA-F]+)$/))?t.fromCharCode(parseInt(r[1],16)):(r=n.match(/^#(\d+)$/))?t.fromCharCode(~~r[1]):e})},escapeRegExp:function(e){return e==null?"":t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},splice:function(e,t,n,r){var i=p.chars(e);return i.splice(~~t,~~n,r),i.join("")},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){return n===""?!0:e==null?!1:t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();return t==null&&(t=""),e.join(t)},lines:function(e){return e==null?[]:t(e).split("\n")},reverse:function(e){return p.chars(e).reverse().join("")},startsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(0,n.length)===n)},endsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(e.length-n.length)===n)},succ:function(e){return e==null?"":(e=t(e),e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return e==null?"":t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,"-$1").replace(/[-_\s]+/g,"-").toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g," ")).replace(/\s/g,"")},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,"").replace(/_/g," "))},trim:function(e,r){return e==null?"":!r&&n?n.call(e):(r=a(r),t(e).replace(new RegExp("^"+r+"+|"+r+"+$","g"),""))},ltrim:function(e,n){return e==null?"":!n&&i?i.call(e):(n=a(n),t(e).replace(new RegExp("^"+n+"+"),""))},rtrim:function(e,n){return e==null?"":!n&&r?r.call(e):(n=a(n),t(e).replace(new RegExp(n+"+$"),""))},truncate:function(e,n,r){return e==null?"":(e=t(e),r=r||"...",n=~~n,e.length>n?e.slice(0,n)+r:e)},prune:function(e,n,r){if(e==null)return"";e=t(e),n=~~n,r=r!=null?t(r):"...";if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?"A":" "},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,""):s=p.rtrim(s.slice(0,s.length-1)),(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){return p.isBlank(e)?[]:p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?"":t(e),n=~~n;var s=0;r?r.length>1&&(r=r.charAt(0)):r=" ";switch(i){case"right":return s=n-e.length,e+o(r,s);case"both":return s=n-e.length,o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:return s=n-e.length,o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,"right")},lrpad:function(e,t,n){return p.pad(e,t,n,"both")},sprintf:h,vsprintf:function(e,t){return t.unshift(e),h.apply(null,t)},toNumber:function(e,n){if(e==null||e=="")return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return"";e=e.toFixed(~~t),r=r||",";var i=e.split("."),s=i[0],o=i[1]?(n||".")+i[1]:"";return s.replace(/(\d)(?=(?:\d{3})+$)/g,"$1"+r)+o},strRight:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.lastIndexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return"";e+="",t=t!=null?""+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||", ",n=n||" and ";var i=e.slice(),s=i.pop();return e.length>2&&r&&(n=p.rtrim(t)+n),i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);return e[3]=!0,p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return"";var n="ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź",r="aaaaaaaaceeeeeiiiilnoooooouuuunczz",i=new RegExp(a(n),"g");return e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||"-"}),p.dasherize(e.replace(/[^\w\s-]/g,""))},surround:function(e,t){return[t,e,t].join("")},quote:function(e){return p.surround(e,'"')},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return"";n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[--n]=e);return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e),n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++)o&&u?e.charAt(u-1)===n.charAt(o-1)?s=i:s=Math.min(r[u],r[u-1],i)+1:s=o+u,i=r[u],r[u]=s;return r.pop()}};p.strip=p.trim,p.lstrip=p.ltrim,p.rstrip=p.rtrim,p.center=p.lrpad,p.rjust=p.lpad,p.ljust=p.rpad,p.contains=p.include,p.q=p.quote,typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(module.exports=p),exports._s=p):typeof define=="function"&&define.amd?define("underscore.string",[],function(){return p}):(e._=e._||{},e._.string=e._.str=p)}(this,String); \ No newline at end of file diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2 index 16856d0c..e6a5e5a5 100644 --- a/src/octoprint/templates/index.jinja2 +++ b/src/octoprint/templates/index.jinja2 @@ -115,12 +115,12 @@
Machine State:
File:  (SD)
- Filament:
- Estimated Print Time:
+ Filament:
+ Estimated Print Time:
Timelapse:
Height:
- Print Time:
- Print Time Left:
+ Print Time:
+ Print Time Left:
Printed:
@@ -183,7 +183,7 @@ - +  |  |  @@ -191,7 +191,7 @@
- Free: + Free: