From c3eefa5c1d83d673d7940f0967b691e4afa70da2 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 10 Mar 2020 17:38:44 +0000 Subject: [PATCH 01/13] First test of LabThings Gevent-based server --- openflexure_microscope/api/app.py | 9 ++++++++- poetry.lock | 4 ++-- pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index e1991132..ef8301b1 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -167,5 +167,12 @@ def cleanup(): atexit.register(cleanup) +# if __name__ == "__main__": +# app.run(host="0.0.0.0", port="5000", threaded=True, debug=True, use_reloader=False) + +# Start the app if __name__ == "__main__": - app.run(host="0.0.0.0", port="5000", threaded=True, debug=True, use_reloader=False) + from labthings.server.wsgi import Server + + server = Server(app) + server.run(host="0.0.0.0", port=5000, debug=True) diff --git a/poetry.lock b/poetry.lock index 07cee8f4..46bd50f4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -281,7 +281,7 @@ marshmallow = "^3.4.0" webargs = "^5.5.3" [package.source] -reference = "028e21cb5b53cc8feeea3af7a0579fde67a54daf" +reference = "2abec0a200e3dbeb8afc8549342328be2357578e" type = "git" url = "https://github.com/labthings/python-labthings.git" [[package]] @@ -700,7 +700,7 @@ version = "1.11.2" rpi = ["picamera", "RPi.GPIO"] [metadata] -content-hash = "ad29f5c7d3a9ac55697f3d74f7b1c992cb2617039cbcab23caa0221f8ed4718a" +content-hash = "4058356cd5db8d9653dd5f7204d691ca07a108936b76136d7332d6eba83029b6" python-versions = "^3.6" [metadata.files] diff --git a/pyproject.toml b/pyproject.toml index ee526317..098a9c85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ picamera = { git = "https://github.com/rwb27/picamera.git", branch = "master", o "RPi.GPIO" = { version = "^0.6.5", optional = true } pyserial = "^3.4" # Used for sangaboard (basic_serial_instrument) until we move to sangaboard pip library -labthings = { git = "https://github.com/labthings/python-labthings.git", branch = "master"} # TODO: Pin to a fixed version before 2.0.0 stable +labthings = { git = "https://github.com/labthings/python-labthings.git", branch = "sockets-testing"} # TODO: Pin to a fixed version before 2.0.0 stable python-dateutil = "^2.8" psutil = "^5.6.7" # Autostorage extension From 1a27a8e4f354442f78a0de6334cd06276422612a Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Fri, 13 Mar 2020 15:56:38 +0000 Subject: [PATCH 02/13] Pin LabThings to 0.2.0 PyPI release --- poetry.lock | 35 ++++++++++++++++++++--------------- pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index 07cee8f4..c1f800e8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -265,25 +265,21 @@ i18n = ["Babel (>=0.8)"] [[package]] category = "main" -description = "" +description = "Python implementation of LabThings, based on the Flask microframework" name = "labthings" optional = false -python-versions = "^3.6" +python-versions = ">=3.6,<4.0" version = "0.2.0" [package.dependencies] -Flask = "^1.1.1" -apispec = "^3.2.0" -flask-cors = "^3.0.8" -gevent = "^1.4.0" -gevent-websocket = "^0.10.1" -marshmallow = "^3.4.0" -webargs = "^5.5.3" +Flask = ">=1.1.1,<2.0.0" +apispec = ">=3.2.0,<4.0.0" +flask-cors = ">=3.0.8,<4.0.0" +gevent = ">=1.4.0,<2.0.0" +gevent-websocket = ">=0.10.1,<0.11.0" +marshmallow = ">=3.4.0,<4.0.0" +webargs = ">=5.5.3,<6.0.0" -[package.source] -reference = "028e21cb5b53cc8feeea3af7a0579fde67a54daf" -type = "git" -url = "https://github.com/labthings/python-labthings.git" [[package]] category = "dev" description = "A fast and thorough lazy object proxy." @@ -359,6 +355,7 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "79177fa7f28d6d5c6eab5ba814b420b1785b6b24" type = "git" url = "https://github.com/rwb27/picamera.git" + [[package]] category = "main" description = "Python Imaging Library (Fork)" @@ -700,7 +697,7 @@ version = "1.11.2" rpi = ["picamera", "RPi.GPIO"] [metadata] -content-hash = "ad29f5c7d3a9ac55697f3d74f7b1c992cb2617039cbcab23caa0221f8ed4718a" +content-hash = "3a3a3bd4ffd7e0fe4c4c418a1b191c33afd375e6f06bb298189fd84ff75ee4cc" python-versions = "^3.6" [metadata.files] @@ -863,7 +860,10 @@ jinja2 = [ {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"}, {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"}, ] -labthings = [] +labthings = [ + {file = "labthings-0.2.0-py3-none-any.whl", hash = "sha256:e2235c0db45c7134403bd78b2aa55ae5aa1bfee677735c9674f52bfcb605e998"}, + {file = "labthings-0.2.0.tar.gz", hash = "sha256:f77391bbb02676ac7c28de59c425c4dc8d6de63031f2a2db8bffd26bc691cb5e"}, +] lazy-object-proxy = [ {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, @@ -915,6 +915,11 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] marshmallow = [ diff --git a/pyproject.toml b/pyproject.toml index ee526317..975b03bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,9 @@ picamera = { git = "https://github.com/rwb27/picamera.git", branch = "master", o "RPi.GPIO" = { version = "^0.6.5", optional = true } pyserial = "^3.4" # Used for sangaboard (basic_serial_instrument) until we move to sangaboard pip library -labthings = { git = "https://github.com/labthings/python-labthings.git", branch = "master"} # TODO: Pin to a fixed version before 2.0.0 stable python-dateutil = "^2.8" psutil = "^5.6.7" # Autostorage extension +labthings = "0.2.0" [tool.poetry.extras] rpi = ["picamera", "RPi.GPIO"] From 6092e8ab5d3d605b90c76af2e124e5b640d96e50 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 15 Mar 2020 16:30:19 +0000 Subject: [PATCH 03/13] Rearranged for clarity --- openflexure_microscope/camera/base.py | 119 +++++++++++++------------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index e3da4131..fa7fd014 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -181,65 +181,6 @@ class BaseCamera(metaclass=ABCMeta): def rebuild_captures(self): self.images = build_captures_from_exif(self.paths["default"]) - # START AND STOP WORKER THREAD - - def start_worker(self, timeout: int = 5) -> bool: - """Start the background camera thread if it isn't running yet.""" - timeout_time = time.time() + timeout - - self.last_access = time.time() - self.stop = False - - if not self.stream_active: - # start background frame thread - self.thread = threading.Thread(target=self._thread) - self.thread.daemon = True - self.thread.start() - - # wait until frames are available - logging.info("Waiting for frames") - while self.get_frame() is None: - if time.time() > timeout_time: - raise TimeoutError("Timeout waiting for frames.") - else: - time.sleep(0.1) - return True - - def stop_worker(self, timeout: int = 5) -> bool: - """Flag worker thread for stop. Waits for thread close or timeout.""" - logging.debug("Stopping worker thread") - timeout_time = time.time() + timeout - - if self.stream_active: - self.stop = True - self.thread.join() # Wait for stream thread to exit - logging.debug("Waiting for stream thread to exit.") - - while self.stream_active: - if time.time() > timeout_time: - logging.debug("Timeout waiting for worker thread close.") - raise TimeoutError("Timeout waiting for worker thread close.") - else: - time.sleep(0.1) - return True - - # HANDLE STREAM FRAMES - - def get_frame(self): - """Return the current camera frame.""" - self.last_access = time.time() - - # wait for a signal from the camera thread - self.event.wait() - self.event.clear() - - return self.frame - - @abstractmethod - def frames(self): - """Create generator that returns frames from the camera.""" - pass - # RETURNING CAPTURES @property @@ -355,6 +296,65 @@ class BaseCamera(metaclass=ABCMeta): return output + # START AND STOP WORKER THREAD + + def start_worker(self, timeout: int = 5) -> bool: + """Start the background camera thread if it isn't running yet.""" + timeout_time = time.time() + timeout + + self.last_access = time.time() + self.stop = False + + if not self.stream_active: + # start background frame thread + self.thread = threading.Thread(target=self._thread) + self.thread.daemon = True + self.thread.start() + + # wait until frames are available + logging.info("Waiting for frames") + while self.get_frame() is None: + if time.time() > timeout_time: + raise TimeoutError("Timeout waiting for frames.") + else: + time.sleep(0.1) + return True + + def stop_worker(self, timeout: int = 5) -> bool: + """Flag worker thread for stop. Waits for thread close or timeout.""" + logging.debug("Stopping worker thread") + timeout_time = time.time() + timeout + + if self.stream_active: + self.stop = True + self.thread.join() # Wait for stream thread to exit + logging.debug("Waiting for stream thread to exit.") + + while self.stream_active: + if time.time() > timeout_time: + logging.debug("Timeout waiting for worker thread close.") + raise TimeoutError("Timeout waiting for worker thread close.") + else: + time.sleep(0.1) + return True + + # HANDLE STREAM FRAMES + + def get_frame(self): + """Return the current camera frame.""" + self.last_access = time.time() + + # wait for a signal from the camera thread + self.event.wait() + self.event.clear() + + return self.frame + + @abstractmethod + def frames(self): + """Create generator that returns frames from the camera.""" + pass + # WORKER THREAD def _thread(self): @@ -365,6 +365,7 @@ class BaseCamera(metaclass=ABCMeta): self.stream_active = True for frame in self.frames_iterator: + print(f"Generated new frame in mjpeg thread at {time.time()}") self.frame = frame self.event.set() # send signal to clients time.sleep(0) From 414be0405ecb09f4f732422c129e92dcdd59ae6a Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 15 Mar 2020 16:30:34 +0000 Subject: [PATCH 04/13] Added a note about concurrency --- openflexure_microscope/api/v2/views/streams.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openflexure_microscope/api/v2/views/streams.py b/openflexure_microscope/api/v2/views/streams.py index 4382e2ab..73ffd258 100644 --- a/openflexure_microscope/api/v2/views/streams.py +++ b/openflexure_microscope/api/v2/views/streams.py @@ -18,7 +18,16 @@ class MjpegStream(View): @doc_response(200, mimetype="multipart/x-mixed-replace") def get(self): """ - MJPEG stream from the microscope camera + MJPEG stream from the microscope camera. + + Note: While the code actually getting frame data from a camera and storing it in + camera.frame runs in a thread, the gen(microscope.camera) generator does not. + This response is therefore blocking. The generator just yields the most recent + frame from the camera object, passed to the Flask response, and then repeats until + the connection is closed. + + Without monkey patching with gevent, or using a native threaded server, the stream + will block all proceeding requests. """ microscope = find_component("org.openflexure.microscope") # Restart stream worker thread From d098af8858458e5a2ef68a3e51fd42f447f7051c Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 15 Mar 2020 17:04:40 +0000 Subject: [PATCH 05/13] Use gevent sleep in mock camera --- openflexure_microscope/camera/mock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openflexure_microscope/camera/mock.py b/openflexure_microscope/camera/mock.py index 322bae58..65e65a32 100644 --- a/openflexure_microscope/camera/mock.py +++ b/openflexure_microscope/camera/mock.py @@ -10,6 +10,7 @@ import time import numpy as np from PIL import Image, ImageFont, ImageDraw from datetime import datetime +import gevent import logging @@ -224,7 +225,7 @@ class MissingCamera(BaseCamera): # While the iterator is not closed try: while True: - time.sleep(1) # Only serve frames at 1fps + gevent.sleep(1) # Only serve frames at 1fps # Reset stream self.stream.seek(0) self.stream.truncate() From 2bc02b3844cf87ccfd5d6cd299ad9c49a770576a Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 15 Mar 2020 17:06:05 +0000 Subject: [PATCH 06/13] Sync frames using gevent events --- openflexure_microscope/camera/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index fa7fd014..955b7117 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -3,6 +3,7 @@ import time import os import shutil import threading +import gevent.event import datetime import logging @@ -61,7 +62,7 @@ class CameraEvent(object): # this is a new client # add an entry for it in the self.events dict # each entry has two elements, a threading.Event() and a timestamp - self.events[ident] = [threading.Event(), time.time()] + self.events[ident] = [gevent.event.Event(), time.time()] return self.events[ident][0].wait(timeout) def set(self): From c6570143885ac5eba85cd8370101b8149ba5fb86 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Sun, 15 Mar 2020 17:06:26 +0000 Subject: [PATCH 07/13] Run gevent monkeypatch --- openflexure_microscope/api/app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index ef8301b1..efb53919 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -1,4 +1,8 @@ #!/usr/bin/env python +from gevent import monkey + +# Patch most system modules. Leave threads untouched so we can still use them normally if needed. +monkey.patch_all(thread=False) import time import atexit @@ -175,4 +179,4 @@ if __name__ == "__main__": from labthings.server.wsgi import Server server = Server(app) - server.run(host="0.0.0.0", port=5000, debug=True) + server.run(host="0.0.0.0", port=5000, debug=False) From 697fa875663ead408acae40ba44c48db599f8d38 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 17 Mar 2020 14:20:07 +0000 Subject: [PATCH 08/13] Removed debug print --- openflexure_microscope/camera/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index 955b7117..e203f44f 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -366,7 +366,6 @@ class BaseCamera(metaclass=ABCMeta): self.stream_active = True for frame in self.frames_iterator: - print(f"Generated new frame in mjpeg thread at {time.time()}") self.frame = frame self.event.set() # send signal to clients time.sleep(0) From b7c3f7b9e136b0d57f0e0192bef97d6c9c80b844 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 17 Mar 2020 14:23:52 +0000 Subject: [PATCH 09/13] Added viewPanel GUI property --- docs/source/extensions/ev_gui.rst | 4 ++++ openflexure_microscope/api/default_extensions/autostorage.py | 1 + 2 files changed, 5 insertions(+) diff --git a/docs/source/extensions/ev_gui.rst b/docs/source/extensions/ev_gui.rst index 4d639fc0..c5d64211 100644 --- a/docs/source/extensions/ev_gui.rst +++ b/docs/source/extensions/ev_gui.rst @@ -53,6 +53,10 @@ Each form is described by a JSON object, with the following properties: ``schema`` - List of dictionaries. Each dictionary element describes a form component. +``viewPanel`` *(optional)* - Content to display to the right of the extension form. Either ``stream`` (default), ``gallery``, or ``settings``. + +``emitOnResponse`` *(optional)* - OpenFlexure eV event to emit when a response is recieved from the extension (generally avoid unless you know you need this.) + Component level +++++++++++++++ diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index 6b92bea0..1d1d98a8 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -248,6 +248,7 @@ def dynamic_form(): "isTask": False, "route": "/location-from-title", "emitOnResponse": "globalUpdateCaptures", + "viewPanel": "gallery", "submitLabel": "Set path", "schema": [ { From 5dfc27ed0353f3acd77e618c1d45acda214d6cd3 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 17 Mar 2020 17:14:29 +0000 Subject: [PATCH 10/13] Fixed broken delete argument (capture_id instead of id) --- openflexure_microscope/api/v2/views/captures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openflexure_microscope/api/v2/views/captures.py b/openflexure_microscope/api/v2/views/captures.py index 27a4a636..2deb72b6 100644 --- a/openflexure_microscope/api/v2/views/captures.py +++ b/openflexure_microscope/api/v2/views/captures.py @@ -207,7 +207,7 @@ class CaptureTags(View): return jsonify(capture_obj.tags) - def delete(self, capture_id): + def delete(self, id): """ Delete tags from a single image capture """ From 6b14ea83959d7eab237cc1a9bc4314df061fb3ba Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 18 Mar 2020 17:51:55 +0000 Subject: [PATCH 11/13] Moved viewPanel into top-level of extension GUI --- docs/source/extensions/ev_gui.rst | 4 ++-- openflexure_microscope/api/default_extensions/autostorage.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/extensions/ev_gui.rst b/docs/source/extensions/ev_gui.rst index c5d64211..79ca7a9d 100644 --- a/docs/source/extensions/ev_gui.rst +++ b/docs/source/extensions/ev_gui.rst @@ -32,6 +32,8 @@ The root of your ``gui`` dictionary expects 2 properties: ``icon`` - The name of a Material Design icon to use for your plugin +``viewPanel`` *(optional)* - Content to display to the right of the extension form. Either ``stream`` (default), ``gallery``, or ``settings``. + ``forms`` - An array of forms as described below Form level @@ -53,8 +55,6 @@ Each form is described by a JSON object, with the following properties: ``schema`` - List of dictionaries. Each dictionary element describes a form component. -``viewPanel`` *(optional)* - Content to display to the right of the extension form. Either ``stream`` (default), ``gallery``, or ``settings``. - ``emitOnResponse`` *(optional)* - OpenFlexure eV event to emit when a response is recieved from the extension (generally avoid unless you know you need this.) Component level diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index 1d1d98a8..4ce6c28c 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -241,6 +241,7 @@ def dynamic_form(): return { "icon": "sd_storage", "title": "Storage", + "viewPanel": "gallery", "forms": [ { "name": "Autostorage", @@ -248,7 +249,6 @@ def dynamic_form(): "isTask": False, "route": "/location-from-title", "emitOnResponse": "globalUpdateCaptures", - "viewPanel": "gallery", "submitLabel": "Set path", "schema": [ { From 85b43c307d2882531a0c5e0ede0c261434706a14 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 18 Mar 2020 17:52:28 +0000 Subject: [PATCH 12/13] Added example Web Component extension --- .../api/default_extensions/__init__.py | 2 + .../custom_element/__init__.py | 57 +++++++++++++++++++ .../static/my-custom-element.min.js | 7 +++ 3 files changed, 66 insertions(+) create mode 100644 openflexure_microscope/api/example_extensions/custom_element/__init__.py create mode 100644 openflexure_microscope/api/example_extensions/custom_element/static/my-custom-element.min.js diff --git a/openflexure_microscope/api/default_extensions/__init__.py b/openflexure_microscope/api/default_extensions/__init__.py index 577f10ba..0591ab60 100644 --- a/openflexure_microscope/api/default_extensions/__init__.py +++ b/openflexure_microscope/api/default_extensions/__init__.py @@ -13,3 +13,5 @@ except Exception as e: logging.error( f"Exception loading builtin extension picamera_autocalibrate: \n{traceback.format_exc()}" ) + +from ..example_extensions.custom_element import customelement_extension_v2 diff --git a/openflexure_microscope/api/example_extensions/custom_element/__init__.py b/openflexure_microscope/api/example_extensions/custom_element/__init__.py new file mode 100644 index 00000000..9fdbadcf --- /dev/null +++ b/openflexure_microscope/api/example_extensions/custom_element/__init__.py @@ -0,0 +1,57 @@ +from labthings.server.extensions import BaseExtension +from labthings.server.view import View +from labthings.server.decorators import ThingProperty, PropertySchema, use_args +from labthings.server import fields +from labthings.server.find import find_component +from labthings.core.utilities import path_relative_to + +from openflexure_microscope.paths import settings_file_path, check_rw +from openflexure_microscope.config import OpenflexureSettingsFile +from openflexure_microscope.camera.base import BASE_CAPTURE_PATH +from openflexure_microscope.camera.capture import build_captures_from_exif + +from openflexure_microscope.api.utilities.gui import build_gui + +from flask import abort, url_for +import logging +import os +import psutil + +from sys import platform + + +class CustomElementExtension(BaseExtension): + def __init__(self): + BaseExtension.__init__( + self, + "org.openflexure.customelement", + description="Testing HTML components in an extension", + static_folder=path_relative_to(__file__, "static"), + ) + + # Register the on_microscope function to run when the microscope is attached + self.on_component("org.openflexure.microscope", self.on_microscope) + + def on_microscope(self, microscope_obj): + """Function to automatically call when the parent LabThing has a microscope attached.""" + logging.debug(f"CustomElementExtension found microscope {microscope_obj}") + + +customelement_extension_v2 = CustomElementExtension() + + +def wc_func(): + return { + "href": customelement_extension_v2.static_file_url("my-custom-element.min.js"), + "name": "my-custom-element", + } + + +def gui_func(): + return {"icon": "pets", "title": "Element", "viewPanel": "stream", "wc": wc_func()} + + +customelement_extension_v2.add_meta("wc", wc_func) +customelement_extension_v2.add_meta( + "gui", build_gui(gui_func, customelement_extension_v2) +) diff --git a/openflexure_microscope/api/example_extensions/custom_element/static/my-custom-element.min.js b/openflexure_microscope/api/example_extensions/custom_element/static/my-custom-element.min.js new file mode 100644 index 00000000..5b5f1402 --- /dev/null +++ b/openflexure_microscope/api/example_extensions/custom_element/static/my-custom-element.min.js @@ -0,0 +1,7 @@ +(function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"===typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t["default"]}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s="5a74")})({"0a06":function(t,e,n){"use strict";var r=n("c532"),o=n("30b5"),i=n("f6b4"),a=n("5270"),s=n("4a7b");function c(t){this.defaults=t,this.interceptors={request:new i,response:new i}}c.prototype.request=function(t){"string"===typeof t?(t=arguments[1]||{},t.url=arguments[0]):t=t||{},t=s(this.defaults,t),t.method?t.method=t.method.toLowerCase():this.defaults.method?t.method=this.defaults.method.toLowerCase():t.method="get";var e=[a,void 0],n=Promise.resolve(t);this.interceptors.request.forEach((function(t){e.unshift(t.fulfilled,t.rejected)})),this.interceptors.response.forEach((function(t){e.push(t.fulfilled,t.rejected)}));while(e.length)n=n.then(e.shift(),e.shift());return n},c.prototype.getUri=function(t){return t=s(this.defaults,t),o(t.url,t.params,t.paramsSerializer).replace(/^\?/,"")},r.forEach(["delete","get","head","options"],(function(t){c.prototype[t]=function(e,n){return this.request(r.merge(n||{},{method:t,url:e}))}})),r.forEach(["post","put","patch"],(function(t){c.prototype[t]=function(e,n,o){return this.request(r.merge(o||{},{method:t,url:e,data:n}))}})),t.exports=c},"0df6":function(t,e,n){"use strict";t.exports=function(t){return function(e){return t.apply(null,e)}}},"19da":function(t,e,n){"use strict";n.r(e);var r=n("9c81"),o=n.n(r);for(var i in r)"default"!==i&&function(t){n.d(e,t,(function(){return r[t]}))}(i);e["default"]=o.a},"1d2b":function(t,e,n){"use strict";t.exports=function(t,e){return function(){for(var n=new Array(arguments.length),r=0;r=200&&t<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};r.forEach(["delete","get","head"],(function(t){c.headers[t]={}})),r.forEach(["post","put","patch"],(function(t){c.headers[t]=r.merge(i)})),t.exports=c}).call(this,n("4362"))},"24fb":function(t,e,n){"use strict";function r(t,e){var n=t[1]||"",r=t[3];if(!r)return n;if(e&&"function"===typeof btoa){var i=o(r),a=r.sources.map((function(t){return"/*# sourceURL=".concat(r.sourceRoot||"").concat(t," */")}));return[n].concat(a).concat([i]).join("\n")}return[n].join("\n")}function o(t){var e=btoa(unescape(encodeURIComponent(JSON.stringify(t)))),n="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(e);return"/*# ".concat(n," */")}t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var n=r(e,t);return e[2]?"@media ".concat(e[2]," {").concat(n,"}"):n})).join("")},e.i=function(t,n,r){"string"===typeof t&&(t=[[null,t,""]]);var o={};if(r)for(var i=0;i=0&&Math.floor(e)===e&&isFinite(t)}function d(t){return o(t)&&"function"===typeof t.then&&"function"===typeof t.catch}function h(t){return null==t?"":Array.isArray(t)||f(t)&&t.toString===u?JSON.stringify(t,null,2):String(t)}function v(t){var e=parseFloat(t);return isNaN(e)?t:e}function m(t,e){for(var n=Object.create(null),r=t.split(","),o=0;o-1)return t.splice(n,1)}}var _=Object.prototype.hasOwnProperty;function b(t,e){return _.call(t,e)}function w(t){var e=Object.create(null);return function(n){var r=e[n];return r||(e[n]=t(n))}}var C=/-(\w)/g,x=w((function(t){return t.replace(C,(function(t,e){return e?e.toUpperCase():""}))})),A=w((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),$=/\B([A-Z])/g,O=w((function(t){return t.replace($,"-$1").toLowerCase()}));function S(t,e){function n(n){var r=arguments.length;return r?r>1?t.apply(e,arguments):t.call(e,n):t.call(e)}return n._length=t.length,n}function k(t,e){return t.bind(e)}var E=Function.prototype.bind?k:S;function j(t,e){e=e||0;var n=t.length-e,r=new Array(n);while(n--)r[n]=t[n+e];return r}function T(t,e){for(var n in e)t[n]=e[n];return t}function N(t){for(var e={},n=0;n0,nt=Q&&Q.indexOf("edge/")>0,rt=(Q&&Q.indexOf("android"),Q&&/iphone|ipad|ipod|ios/.test(Q)||"ios"===Y),ot=(Q&&/chrome\/\d+/.test(Q),Q&&/phantomjs/.test(Q),Q&&Q.match(/firefox\/(\d+)/)),it={}.watch,at=!1;if(G)try{var st={};Object.defineProperty(st,"passive",{get:function(){at=!0}}),window.addEventListener("test-passive",null,st)}catch(xa){}var ct=function(){return void 0===K&&(K=!G&&!Z&&"undefined"!==typeof t&&(t["process"]&&"server"===t["process"].env.VUE_ENV)),K},ut=G&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function ft(t){return"function"===typeof t&&/native code/.test(t.toString())}var lt,pt="undefined"!==typeof Symbol&&ft(Symbol)&&"undefined"!==typeof Reflect&&ft(Reflect.ownKeys);lt="undefined"!==typeof Set&&ft(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var dt=L,ht=0,vt=function(){this.id=ht++,this.subs=[]};vt.prototype.addSub=function(t){this.subs.push(t)},vt.prototype.removeSub=function(t){g(this.subs,t)},vt.prototype.depend=function(){vt.target&&vt.target.addDep(this)},vt.prototype.notify=function(){var t=this.subs.slice();for(var e=0,n=t.length;e-1)if(i&&!b(o,"default"))a=!1;else if(""===a||a===O(t)){var c=te(String,o.type);(c<0||s0&&(a=Se(a,(e||"")+"_"+n),Oe(a[0])&&Oe(u)&&(f[c]=Ct(u.text+a[0].text),a.shift()),f.push.apply(f,a)):s(a)?Oe(u)?f[c]=Ct(u.text+a):""!==a&&f.push(Ct(a)):Oe(a)&&Oe(u)?f[c]=Ct(u.text+a.text):(i(t._isVList)&&o(a.tag)&&r(a.key)&&o(e)&&(a.key="__vlist"+e+"_"+n+"__"),f.push(a)));return f}function ke(t){var e=t.$options.provide;e&&(t._provided="function"===typeof e?e.call(t):e)}function Ee(t){var e=je(t.$options.inject,t);e&&(Et(!1),Object.keys(e).forEach((function(n){Rt(t,n,e[n])})),Et(!0))}function je(t,e){if(t){for(var n=Object.create(null),r=pt?Reflect.ownKeys(t):Object.keys(t),o=0;o0,a=t?!!t.$stable:!i,s=t&&t.$key;if(t){if(t._normalized)return t._normalized;if(a&&r&&r!==n&&s===r.$key&&!i&&!r.$hasNormal)return r;for(var c in o={},t)t[c]&&"$"!==c[0]&&(o[c]=Re(e,c,t[c]))}else o={};for(var u in e)u in o||(o[u]=De(e,u));return t&&Object.isExtensible(t)&&(t._normalized=o),q(o,"$stable",a),q(o,"$key",s),q(o,"$hasNormal",i),o}function Re(t,e,n){var r=function(){var t=arguments.length?n.apply(null,arguments):n({});return t=t&&"object"===typeof t&&!Array.isArray(t)?[t]:$e(t),t&&(0===t.length||1===t.length&&t[0].isComment)?void 0:t};return n.proxy&&Object.defineProperty(t,e,{get:r,enumerable:!0,configurable:!0}),r}function De(t,e){return function(){return t[e]}}function Pe(t,e){var n,r,i,a,s;if(Array.isArray(t)||"string"===typeof t)for(n=new Array(t.length),r=0,i=t.length;r1?j(n):n;for(var r=j(arguments,1),o='event handler for "'+t+'"',i=0,a=n.length;idocument.createEvent("Event").timeStamp&&(Kn=function(){return Jn.now()})}function Gn(){var t,e;for(Xn=Kn(),Vn=!0,Fn.sort((function(t,e){return t.id-e.id})),qn=0;qnqn&&Fn[n].id>t.id)n--;Fn.splice(n+1,0,t)}else Fn.push(t);zn||(zn=!0,he(Gn))}}var er=0,nr=function(t,e,n,r,o){this.vm=t,o&&(t._watcher=this),t._watchers.push(this),r?(this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.before=r.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=n,this.id=++er,this.active=!0,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new lt,this.newDepIds=new lt,this.expression="","function"===typeof e?this.getter=e:(this.getter=X(e),this.getter||(this.getter=L)),this.value=this.lazy?void 0:this.get()};nr.prototype.get=function(){var t;yt(this);var e=this.vm;try{t=this.getter.call(e,e)}catch(xa){if(!this.user)throw xa;ee(xa,e,'getter for watcher "'+this.expression+'"')}finally{this.deep&&me(t),gt(),this.cleanupDeps()}return t},nr.prototype.addDep=function(t){var e=t.id;this.newDepIds.has(e)||(this.newDepIds.add(e),this.newDeps.push(t),this.depIds.has(e)||t.addSub(this))},nr.prototype.cleanupDeps=function(){var t=this.deps.length;while(t--){var e=this.deps[t];this.newDepIds.has(e.id)||e.removeSub(this)}var n=this.depIds;this.depIds=this.newDepIds,this.newDepIds=n,this.newDepIds.clear(),n=this.deps,this.deps=this.newDeps,this.newDeps=n,this.newDeps.length=0},nr.prototype.update=function(){this.lazy?this.dirty=!0:this.sync?this.run():tr(this)},nr.prototype.run=function(){if(this.active){var t=this.get();if(t!==this.value||c(t)||this.deep){var e=this.value;if(this.value=t,this.user)try{this.cb.call(this.vm,t,e)}catch(xa){ee(xa,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,t,e)}}},nr.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},nr.prototype.depend=function(){var t=this.deps.length;while(t--)this.deps[t].depend()},nr.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||g(this.vm._watchers,this);var t=this.deps.length;while(t--)this.deps[t].removeSub(this);this.active=!1}};var rr={enumerable:!0,configurable:!0,get:L,set:L};function or(t,e,n){rr.get=function(){return this[e][n]},rr.set=function(t){this[e][n]=t},Object.defineProperty(t,n,rr)}function ir(t){t._watchers=[];var e=t.$options;e.props&&ar(t,e.props),e.methods&&hr(t,e.methods),e.data?sr(t):Lt(t._data={},!0),e.computed&&fr(t,e.computed),e.watch&&e.watch!==it&&vr(t,e.watch)}function ar(t,e){var n=t.$options.propsData||{},r=t._props={},o=t.$options._propKeys=[],i=!t.$parent;i||Et(!1);var a=function(i){o.push(i);var a=Gt(i,e,n,t);Rt(r,i,a),i in t||or(t,"_props",i)};for(var s in e)a(s);Et(!0)}function sr(t){var e=t.$options.data;e=t._data="function"===typeof e?cr(e,t):e||{},f(e)||(e={});var n=Object.keys(e),r=t.$options.props,o=(t.$options.methods,n.length);while(o--){var i=n[o];0,r&&b(r,i)||V(i)||or(t,"_data",i)}Lt(e,!0)}function cr(t,e){yt();try{return t.call(e,e)}catch(xa){return ee(xa,e,"data()"),{}}finally{gt()}}var ur={lazy:!0};function fr(t,e){var n=t._computedWatchers=Object.create(null),r=ct();for(var o in e){var i=e[o],a="function"===typeof i?i:i.get;0,r||(n[o]=new nr(t,a||L,L,ur)),o in t||lr(t,o,i)}}function lr(t,e,n){var r=!ct();"function"===typeof n?(rr.get=r?pr(e):dr(n),rr.set=L):(rr.get=n.get?r&&!1!==n.cache?pr(e):dr(n.get):L,rr.set=n.set||L),Object.defineProperty(t,e,rr)}function pr(t){return function(){var e=this._computedWatchers&&this._computedWatchers[t];if(e)return e.dirty&&e.evaluate(),vt.target&&e.depend(),e.value}}function dr(t){return function(){return t.call(this,this)}}function hr(t,e){t.$options.props;for(var n in e)t[n]="function"!==typeof e[n]?L:E(e[n],t)}function vr(t,e){for(var n in e){var r=e[n];if(Array.isArray(r))for(var o=0;o-1)return this;var n=j(arguments,1);return n.unshift(this),"function"===typeof t.install?t.install.apply(t,n):"function"===typeof t&&t.apply(null,n),e.push(t),this}}function $r(t){t.mixin=function(t){return this.options=Kt(this.options,t),this}}function Or(t){t.cid=0;var e=1;t.extend=function(t){t=t||{};var n=this,r=n.cid,o=t._Ctor||(t._Ctor={});if(o[r])return o[r];var i=t.name||n.options.name;var a=function(t){this._init(t)};return a.prototype=Object.create(n.prototype),a.prototype.constructor=a,a.cid=e++,a.options=Kt(n.options,t),a["super"]=n,a.options.props&&Sr(a),a.options.computed&&kr(a),a.extend=n.extend,a.mixin=n.mixin,a.use=n.use,F.forEach((function(t){a[t]=n[t]})),i&&(a.options.components[i]=a),a.superOptions=n.options,a.extendOptions=t,a.sealedOptions=T({},a.options),o[r]=a,a}}function Sr(t){var e=t.options.props;for(var n in e)or(t.prototype,"_props",n)}function kr(t){var e=t.options.computed;for(var n in e)lr(t.prototype,n,e[n])}function Er(t){F.forEach((function(e){t[e]=function(t,n){return n?("component"===e&&f(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),"directive"===e&&"function"===typeof n&&(n={bind:n,update:n}),this.options[e+"s"][t]=n,n):this.options[e+"s"][t]}}))}function jr(t){return t&&(t.Ctor.options.name||t.tag)}function Tr(t,e){return Array.isArray(t)?t.indexOf(e)>-1:"string"===typeof t?t.split(",").indexOf(e)>-1:!!l(t)&&t.test(e)}function Nr(t,e){var n=t.cache,r=t.keys,o=t._vnode;for(var i in n){var a=n[i];if(a){var s=jr(a.componentOptions);s&&!e(s)&&Lr(n,i,r,o)}}}function Lr(t,e,n,r){var o=t[e];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),t[e]=null,g(n,e)}_r(xr),yr(xr),En(xr),Ln(xr),gn(xr);var Rr=[String,RegExp,Array],Dr={name:"keep-alive",abstract:!0,props:{include:Rr,exclude:Rr,max:[String,Number]},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var t in this.cache)Lr(this.cache,t,this.keys)},mounted:function(){var t=this;this.$watch("include",(function(e){Nr(t,(function(t){return Tr(e,t)}))})),this.$watch("exclude",(function(e){Nr(t,(function(t){return!Tr(e,t)}))}))},render:function(){var t=this.$slots.default,e=xn(t),n=e&&e.componentOptions;if(n){var r=jr(n),o=this,i=o.include,a=o.exclude;if(i&&(!r||!Tr(i,r))||a&&r&&Tr(a,r))return e;var s=this,c=s.cache,u=s.keys,f=null==e.key?n.Ctor.cid+(n.tag?"::"+n.tag:""):e.key;c[f]?(e.componentInstance=c[f].componentInstance,g(u,f),u.push(f)):(c[f]=e,u.push(f),this.max&&u.length>parseInt(this.max)&&Lr(c,u[0],u,this._vnode)),e.data.keepAlive=!0}return e||t&&t[0]}},Pr={KeepAlive:Dr};function Ir(t){var e={get:function(){return H}};Object.defineProperty(t,"config",e),t.util={warn:dt,extend:T,mergeOptions:Kt,defineReactive:Rt},t.set=Dt,t.delete=Pt,t.nextTick=he,t.observable=function(t){return Lt(t),t},t.options=Object.create(null),F.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,T(t.options.components,Pr),Ar(t),$r(t),Or(t),Er(t)}Ir(xr),Object.defineProperty(xr.prototype,"$isServer",{get:ct}),Object.defineProperty(xr.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(xr,"FunctionalRenderContext",{value:Ze}),xr.version="2.6.11";var Mr=m("style,class"),Ur=m("input,textarea,option,select,progress"),Fr=function(t,e,n){return"value"===n&&Ur(t)&&"button"!==e||"selected"===n&&"option"===t||"checked"===n&&"input"===t||"muted"===n&&"video"===t},Br=m("contenteditable,draggable,spellcheck"),Hr=m("events,caret,typing,plaintext-only"),zr=function(t,e){return Kr(e)||"false"===e?"false":"contenteditable"===t&&Hr(e)?e:"true"},Vr=m("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),qr="http://www.w3.org/1999/xlink",Wr=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},Xr=function(t){return Wr(t)?t.slice(6,t.length):""},Kr=function(t){return null==t||!1===t};function Jr(t){var e=t.data,n=t,r=t;while(o(r.componentInstance))r=r.componentInstance._vnode,r&&r.data&&(e=Gr(r.data,e));while(o(n=n.parent))n&&n.data&&(e=Gr(e,n.data));return Zr(e.staticClass,e.class)}function Gr(t,e){return{staticClass:Yr(t.staticClass,e.staticClass),class:o(t.class)?[t.class,e.class]:e.class}}function Zr(t,e){return o(t)||o(e)?Yr(t,Qr(e)):""}function Yr(t,e){return t?e?t+" "+e:t:e||""}function Qr(t){return Array.isArray(t)?to(t):c(t)?eo(t):"string"===typeof t?t:""}function to(t){for(var e,n="",r=0,i=t.length;r-1?so[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:so[t]=/HTMLUnknownElement/.test(e.toString())}var uo=m("text,number,password,search,email,tel,url");function fo(t){if("string"===typeof t){var e=document.querySelector(t);return e||document.createElement("div")}return t}function lo(t,e){var n=document.createElement(t);return"select"!==t?n:(e.data&&e.data.attrs&&void 0!==e.data.attrs.multiple&&n.setAttribute("multiple","multiple"),n)}function po(t,e){return document.createElementNS(no[t],e)}function ho(t){return document.createTextNode(t)}function vo(t){return document.createComment(t)}function mo(t,e,n){t.insertBefore(e,n)}function yo(t,e){t.removeChild(e)}function go(t,e){t.appendChild(e)}function _o(t){return t.parentNode}function bo(t){return t.nextSibling}function wo(t){return t.tagName}function Co(t,e){t.textContent=e}function xo(t,e){t.setAttribute(e,"")}var Ao=Object.freeze({createElement:lo,createElementNS:po,createTextNode:ho,createComment:vo,insertBefore:mo,removeChild:yo,appendChild:go,parentNode:_o,nextSibling:bo,tagName:wo,setTextContent:Co,setStyleScope:xo}),$o={create:function(t,e){Oo(e)},update:function(t,e){t.data.ref!==e.data.ref&&(Oo(t,!0),Oo(e))},destroy:function(t){Oo(t,!0)}};function Oo(t,e){var n=t.data.ref;if(o(n)){var r=t.context,i=t.componentInstance||t.elm,a=r.$refs;e?Array.isArray(a[n])?g(a[n],i):a[n]===i&&(a[n]=void 0):t.data.refInFor?Array.isArray(a[n])?a[n].indexOf(i)<0&&a[n].push(i):a[n]=[i]:a[n]=i}}var So=new _t("",{},[]),ko=["create","activate","update","remove","destroy"];function Eo(t,e){return t.key===e.key&&(t.tag===e.tag&&t.isComment===e.isComment&&o(t.data)===o(e.data)&&jo(t,e)||i(t.isAsyncPlaceholder)&&t.asyncFactory===e.asyncFactory&&r(e.asyncFactory.error))}function jo(t,e){if("input"!==t.tag)return!0;var n,r=o(n=t.data)&&o(n=n.attrs)&&n.type,i=o(n=e.data)&&o(n=n.attrs)&&n.type;return r===i||uo(r)&&uo(i)}function To(t,e,n){var r,i,a={};for(r=e;r<=n;++r)i=t[r].key,o(i)&&(a[i]=r);return a}function No(t){var e,n,a={},c=t.modules,u=t.nodeOps;for(e=0;ev?(l=r(n[g+1])?null:n[g+1].elm,x(t,l,n,h,g,i)):h>g&&$(e,p,v)}function k(t,e,n,r){for(var i=n;i-1?zo(t,e,n):Vr(e)?Kr(n)?t.removeAttribute(e):(n="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,n)):Br(e)?t.setAttribute(e,zr(e,n)):Wr(e)?Kr(n)?t.removeAttributeNS(qr,Xr(e)):t.setAttributeNS(qr,e,n):zo(t,e,n)}function zo(t,e,n){if(Kr(n))t.removeAttribute(e);else{if(tt&&!et&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==n&&!t.__ieph){var r=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",r)};t.addEventListener("input",r),t.__ieph=!0}t.setAttribute(e,n)}}var Vo={create:Bo,update:Bo};function qo(t,e){var n=e.elm,i=e.data,a=t.data;if(!(r(i.staticClass)&&r(i.class)&&(r(a)||r(a.staticClass)&&r(a.class)))){var s=Jr(e),c=n._transitionClasses;o(c)&&(s=Yr(s,Qr(c))),s!==n._prevClass&&(n.setAttribute("class",s),n._prevClass=s)}}var Wo,Xo={create:qo,update:qo},Ko="__r",Jo="__c";function Go(t){if(o(t[Ko])){var e=tt?"change":"input";t[e]=[].concat(t[Ko],t[e]||[]),delete t[Ko]}o(t[Jo])&&(t.change=[].concat(t[Jo],t.change||[]),delete t[Jo])}function Zo(t,e,n){var r=Wo;return function o(){var i=e.apply(null,arguments);null!==i&&ti(t,o,n,r)}}var Yo=ae&&!(ot&&Number(ot[1])<=53);function Qo(t,e,n,r){if(Yo){var o=Xn,i=e;e=i._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=o||t.timeStamp<=0||t.target.ownerDocument!==document)return i.apply(this,arguments)}}Wo.addEventListener(t,e,at?{capture:n,passive:r}:n)}function ti(t,e,n,r){(r||Wo).removeEventListener(t,e._wrapper||e,n)}function ei(t,e){if(!r(t.data.on)||!r(e.data.on)){var n=e.data.on||{},o=t.data.on||{};Wo=e.elm,Go(n),be(n,o,Qo,ti,Zo,e.context),Wo=void 0}}var ni,ri={create:ei,update:ei};function oi(t,e){if(!r(t.data.domProps)||!r(e.data.domProps)){var n,i,a=e.elm,s=t.data.domProps||{},c=e.data.domProps||{};for(n in o(c.__ob__)&&(c=e.data.domProps=T({},c)),s)n in c||(a[n]="");for(n in c){if(i=c[n],"textContent"===n||"innerHTML"===n){if(e.children&&(e.children.length=0),i===s[n])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===n&&"PROGRESS"!==a.tagName){a._value=i;var u=r(i)?"":String(i);ii(a,u)&&(a.value=u)}else if("innerHTML"===n&&oo(a.tagName)&&r(a.innerHTML)){ni=ni||document.createElement("div"),ni.innerHTML=""+i+"";var f=ni.firstChild;while(a.firstChild)a.removeChild(a.firstChild);while(f.firstChild)a.appendChild(f.firstChild)}else if(i!==s[n])try{a[n]=i}catch(xa){}}}}function ii(t,e){return!t.composing&&("OPTION"===t.tagName||ai(t,e)||si(t,e))}function ai(t,e){var n=!0;try{n=document.activeElement!==t}catch(xa){}return n&&t.value!==e}function si(t,e){var n=t.value,r=t._vModifiers;if(o(r)){if(r.number)return v(n)!==v(e);if(r.trim)return n.trim()!==e.trim()}return n!==e}var ci={create:oi,update:oi},ui=w((function(t){var e={},n=/;(?![^(]*\))/g,r=/:(.+)/;return t.split(n).forEach((function(t){if(t){var n=t.split(r);n.length>1&&(e[n[0].trim()]=n[1].trim())}})),e}));function fi(t){var e=li(t.style);return t.staticStyle?T(t.staticStyle,e):e}function li(t){return Array.isArray(t)?N(t):"string"===typeof t?ui(t):t}function pi(t,e){var n,r={};if(e){var o=t;while(o.componentInstance)o=o.componentInstance._vnode,o&&o.data&&(n=fi(o.data))&&T(r,n)}(n=fi(t.data))&&T(r,n);var i=t;while(i=i.parent)i.data&&(n=fi(i.data))&&T(r,n);return r}var di,hi=/^--/,vi=/\s*!important$/,mi=function(t,e,n){if(hi.test(e))t.style.setProperty(e,n);else if(vi.test(n))t.style.setProperty(O(e),n.replace(vi,""),"important");else{var r=gi(e);if(Array.isArray(n))for(var o=0,i=n.length;o-1?e.split(wi).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=" "+(t.getAttribute("class")||"")+" ";n.indexOf(" "+e+" ")<0&&t.setAttribute("class",(n+e).trim())}}function xi(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split(wi).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{var n=" "+(t.getAttribute("class")||"")+" ",r=" "+e+" ";while(n.indexOf(r)>=0)n=n.replace(r," ");n=n.trim(),n?t.setAttribute("class",n):t.removeAttribute("class")}}function Ai(t){if(t){if("object"===typeof t){var e={};return!1!==t.css&&T(e,$i(t.name||"v")),T(e,t),e}return"string"===typeof t?$i(t):void 0}}var $i=w((function(t){return{enterClass:t+"-enter",enterToClass:t+"-enter-to",enterActiveClass:t+"-enter-active",leaveClass:t+"-leave",leaveToClass:t+"-leave-to",leaveActiveClass:t+"-leave-active"}})),Oi=G&&!et,Si="transition",ki="animation",Ei="transition",ji="transitionend",Ti="animation",Ni="animationend";Oi&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(Ei="WebkitTransition",ji="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Ti="WebkitAnimation",Ni="webkitAnimationEnd"));var Li=G?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function Ri(t){Li((function(){Li(t)}))}function Di(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),Ci(t,e))}function Pi(t,e){t._transitionClasses&&g(t._transitionClasses,e),xi(t,e)}function Ii(t,e,n){var r=Ui(t,e),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=o===Si?ji:Ni,c=0,u=function(){t.removeEventListener(s,f),n()},f=function(e){e.target===t&&++c>=a&&u()};setTimeout((function(){c0&&(n=Si,f=a,l=i.length):e===ki?u>0&&(n=ki,f=u,l=c.length):(f=Math.max(a,u),n=f>0?a>u?Si:ki:null,l=n?n===Si?i.length:c.length:0);var p=n===Si&&Mi.test(r[Ei+"Property"]);return{type:n,timeout:f,propCount:l,hasTransform:p}}function Fi(t,e){while(t.length1}function Wi(t,e){!0!==e.data.show&&Hi(e)}var Xi=G?{create:Wi,activate:Wi,remove:function(t,e){!0!==t.data.show?zi(t,e):e()}}:{},Ki=[Vo,Xo,ri,ci,bi,Xi],Ji=Ki.concat(Fo),Gi=No({nodeOps:Ao,modules:Ji});et&&document.addEventListener("selectionchange",(function(){var t=document.activeElement;t&&t.vmodel&&oa(t,"input")}));var Zi={inserted:function(t,e,n,r){"select"===n.tag?(r.elm&&!r.elm._vOptions?we(n,"postpatch",(function(){Zi.componentUpdated(t,e,n)})):Yi(t,e,n.context),t._vOptions=[].map.call(t.options,ea)):("textarea"===n.tag||uo(t.type))&&(t._vModifiers=e.modifiers,e.modifiers.lazy||(t.addEventListener("compositionstart",na),t.addEventListener("compositionend",ra),t.addEventListener("change",ra),et&&(t.vmodel=!0)))},componentUpdated:function(t,e,n){if("select"===n.tag){Yi(t,e,n.context);var r=t._vOptions,o=t._vOptions=[].map.call(t.options,ea);if(o.some((function(t,e){return!P(t,r[e])}))){var i=t.multiple?e.value.some((function(t){return ta(t,o)})):e.value!==e.oldValue&&ta(e.value,o);i&&oa(t,"change")}}}};function Yi(t,e,n){Qi(t,e,n),(tt||nt)&&setTimeout((function(){Qi(t,e,n)}),0)}function Qi(t,e,n){var r=e.value,o=t.multiple;if(!o||Array.isArray(r)){for(var i,a,s=0,c=t.options.length;s-1,a.selected!==i&&(a.selected=i);else if(P(ea(a),r))return void(t.selectedIndex!==s&&(t.selectedIndex=s));o||(t.selectedIndex=-1)}}function ta(t,e){return e.every((function(e){return!P(e,t)}))}function ea(t){return"_value"in t?t._value:t.value}function na(t){t.target.composing=!0}function ra(t){t.target.composing&&(t.target.composing=!1,oa(t.target,"input"))}function oa(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function ia(t){return!t.componentInstance||t.data&&t.data.transition?t:ia(t.componentInstance._vnode)}var aa={bind:function(t,e,n){var r=e.value;n=ia(n);var o=n.data&&n.data.transition,i=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;r&&o?(n.data.show=!0,Hi(n,(function(){t.style.display=i}))):t.style.display=r?i:"none"},update:function(t,e,n){var r=e.value,o=e.oldValue;if(!r!==!o){n=ia(n);var i=n.data&&n.data.transition;i?(n.data.show=!0,r?Hi(n,(function(){t.style.display=t.__vOriginalDisplay})):zi(n,(function(){t.style.display="none"}))):t.style.display=r?t.__vOriginalDisplay:"none"}},unbind:function(t,e,n,r,o){o||(t.style.display=t.__vOriginalDisplay)}},sa={model:Zi,show:aa},ca={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function ua(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?ua(xn(e.children)):t}function fa(t){var e={},n=t.$options;for(var r in n.propsData)e[r]=t[r];var o=n._parentListeners;for(var i in o)e[x(i)]=o[i];return e}function la(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}function pa(t){while(t=t.parent)if(t.data.transition)return!0}function da(t,e){return e.key===t.key&&e.tag===t.tag}var ha=function(t){return t.tag||Cn(t)},va=function(t){return"show"===t.name},ma={name:"transition",props:ca,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(ha),n.length)){0;var r=this.mode;0;var o=n[0];if(pa(this.$vnode))return o;var i=ua(o);if(!i)return o;if(this._leaving)return la(t,o);var a="__transition-"+this._uid+"-";i.key=null==i.key?i.isComment?a+"comment":a+i.tag:s(i.key)?0===String(i.key).indexOf(a)?i.key:a+i.key:i.key;var c=(i.data||(i.data={})).transition=fa(this),u=this._vnode,f=ua(u);if(i.data.directives&&i.data.directives.some(va)&&(i.data.show=!0),f&&f.data&&!da(i,f)&&!Cn(f)&&(!f.componentInstance||!f.componentInstance._vnode.isComment)){var l=f.data.transition=T({},c);if("out-in"===r)return this._leaving=!0,we(l,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),la(t,o);if("in-out"===r){if(Cn(i))return u;var p,d=function(){p()};we(c,"afterEnter",d),we(c,"enterCancelled",d),we(l,"delayLeave",(function(t){p=t}))}}return o}}},ya=T({tag:String,moveClass:String},ca);delete ya.mode;var ga={props:ya,beforeMount:function(){var t=this,e=this._update;this._update=function(n,r){var o=Tn(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,o(),e.call(t,n,r)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,o=this.$slots.default||[],i=this.children=[],a=fa(this),s=0;st.replace(i,(t,e)=>e?e.toUpperCase():""),s=/\B([A-Z])/g,c=t=>t.replace(s,"-$1").toLowerCase();function u(t){const e={};return t.forEach(t=>{e[t]=void 0}),e}function f(t,e,n){t[e]=[].concat(t[e]||[]),t[e].unshift(n)}function l(t,e){if(t){const n=t.$options[e]||[];n.forEach(e=>{e.call(t)})}}function p(t,e){return new CustomEvent(t,{bubbles:!1,cancelable:!1,detail:e})}const d=t=>/function Boolean/.test(String(t)),h=t=>/function Number/.test(String(t));function v(t,e,{type:n}={}){if(d(n))return"true"===t||"false"===t?"true"===t:""===t||t===e||null!=t;if(h(n)){const e=parseFloat(t,10);return isNaN(e)?t:e}return t}function m(t,e){const n=[];for(let r=0,o=e.length;r(t[e]=u[n[r]],t),{}),f(e,"beforeCreate",(function(){const t=this.$emit;this.$emit=(e,...n)=>(this.$root.$options.customElement.dispatchEvent(p(e,n)),t.call(this,e,...n))})),f(e,"created",(function(){o.forEach(t=>{this.$root.props[t]=this[t]})})),o.forEach(t=>{Object.defineProperty(y.prototype,t,{get(){return this._wrapper.props[t]},set(e){this._wrapper.props[t]=e},enumerable:!1,configurable:!0})}),s=!0}function h(t,e){const n=a(e),r=t.hasAttribute(e)?t.getAttribute(e):void 0;t._wrapper.props[n]=v(r,e,i[n])}class y extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"});const n=this._wrapper=new t({name:"shadow-root",customElement:this,shadowRoot:this.shadowRoot,data(){return{props:{},slotChildren:[]}},render(t){return t(e,{ref:"inner",props:this.props},this.slotChildren)}}),r=new MutationObserver(t=>{let e=!1;for(let n=0;n{t.props=u(o),r.forEach(t=>{h(this,t)})};s?n():e().then(t=>{(t.__esModule||"Module"===t[Symbol.toStringTag])&&(t=t.default),d(t),n()}),t.slotChildren=Object.freeze(m(t.$createElement,this.childNodes)),t.$mount(),this.shadowRoot.appendChild(t.$el)}}disconnectedCallback(){l(this.vueComponent,"deactivated")}}return n||d(e),y}var b=_;n("24fb"),n("35d6");function w(t,e,n,r,o,i,a,s){var c,u="function"===typeof t?t.options:t;if(e&&(u.render=e,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(c=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(a)},u._ssrRegister=c):o&&(c=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),c)if(u.functional){u._injectStyles=c;var f=u.render;u.render=function(t,e){return c.call(e),f(t,e)}}else{var l=u.beforeCreate;u.beforeCreate=l?[].concat(l,c):[c]}return{exports:t,options:u}}var C=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"my-component-class"},[n("h1",[t._v("My Vue Web Component")]),n("div",[t._v("Base URL: "+t._s(t.componentBaseURL))]),n("p",[t._v("Host microscope name: "+t._s(t.hostDeviceName))]),n("p",[t._v(t._s(t.message))]),n("br"),n("p",[t._v("This cheeky little counter has all of its logic contained in a server-side component:")]),n("button",{attrs:{type:"button"},on:{click:function(e){return t.decrement()}}},[t._v("-")]),n("span",[t._v(t._s(t.value))]),n("button",{attrs:{type:"button"},on:{click:function(e){return t.increment()}}},[t._v("+")])])},x=[],A=n("bc3a"),$=n.n(A),O={props:{componentBaseURL:{required:!1,default:null,type:String}},data:function(){return{value:0,message:"",hostDeviceName:null}},mounted(){this.componentBaseURL?$.a.get("".concat(this.componentBaseURL)).then(t=>{console.log(t.data),this.hostDeviceName=t.data.title}).catch((function(t){console.log("Error reading test json, probabl because of cors or something")})):this.message="No componentBaseURL given"},methods:{decrement:function(){this.value=this.value-1},increment:function(){this.value=this.value+1}}},S=O;function k(t){var e=n("19da");e.__inject__&&e.__inject__(t)}var E=w(S,C,x,!1,k,"646757d2",null,!0),j=E.exports;window.customElements.define("my-custom-element",b(o["a"],j))},"78c8":function(t,e,n){var r=n("24fb");e=r(!1),e.push([t.i,".my-component-class[data-v-646757d2]{text-align:center}",""]),t.exports=e},"7a77":function(t,e,n){"use strict";function r(t){this.message=t}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,t.exports=r},"7aac":function(t,e,n){"use strict";var r=n("c532");t.exports=r.isStandardBrowserEnv()?function(){return{write:function(t,e,n,o,i,a){var s=[];s.push(t+"="+encodeURIComponent(e)),r.isNumber(n)&&s.push("expires="+new Date(n).toGMTString()),r.isString(o)&&s.push("path="+o),r.isString(i)&&s.push("domain="+i),!0===a&&s.push("secure"),document.cookie=s.join("; ")},read:function(t){var e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove:function(t){this.write(t,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},"83b9":function(t,e,n){"use strict";var r=n("d925"),o=n("e683");t.exports=function(t,e){return t&&!r(e)?o(t,e):e}},"8df4":function(t,e,n){"use strict";var r=n("7a77");function o(t){if("function"!==typeof t)throw new TypeError("executor must be a function.");var e;this.promise=new Promise((function(t){e=t}));var n=this;t((function(t){n.reason||(n.reason=new r(t),e(n.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.source=function(){var t,e=new o((function(e){t=e}));return{token:e,cancel:t}},t.exports=o},"9c81":function(t,e,n){var r=n("78c8");"string"===typeof r&&(r=[[t.i,r,""]]),r.locals&&(t.exports=r.locals);var o=n("35d6").default;t.exports.__inject__=function(t){o("b9d96038",r,t)}},b50d:function(t,e,n){"use strict";var r=n("c532"),o=n("467f"),i=n("30b5"),a=n("83b9"),s=n("c345"),c=n("3934"),u=n("2d83");t.exports=function(t){return new Promise((function(e,f){var l=t.data,p=t.headers;r.isFormData(l)&&delete p["Content-Type"];var d=new XMLHttpRequest;if(t.auth){var h=t.auth.username||"",v=t.auth.password||"";p.Authorization="Basic "+btoa(h+":"+v)}var m=a(t.baseURL,t.url);if(d.open(t.method.toUpperCase(),i(m,t.params,t.paramsSerializer),!0),d.timeout=t.timeout,d.onreadystatechange=function(){if(d&&4===d.readyState&&(0!==d.status||d.responseURL&&0===d.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in d?s(d.getAllResponseHeaders()):null,r=t.responseType&&"text"!==t.responseType?d.response:d.responseText,i={data:r,status:d.status,statusText:d.statusText,headers:n,config:t,request:d};o(e,f,i),d=null}},d.onabort=function(){d&&(f(u("Request aborted",t,"ECONNABORTED",d)),d=null)},d.onerror=function(){f(u("Network Error",t,null,d)),d=null},d.ontimeout=function(){var e="timeout of "+t.timeout+"ms exceeded";t.timeoutErrorMessage&&(e=t.timeoutErrorMessage),f(u(e,t,"ECONNABORTED",d)),d=null},r.isStandardBrowserEnv()){var y=n("7aac"),g=(t.withCredentials||c(m))&&t.xsrfCookieName?y.read(t.xsrfCookieName):void 0;g&&(p[t.xsrfHeaderName]=g)}if("setRequestHeader"in d&&r.forEach(p,(function(t,e){"undefined"===typeof l&&"content-type"===e.toLowerCase()?delete p[e]:d.setRequestHeader(e,t)})),r.isUndefined(t.withCredentials)||(d.withCredentials=!!t.withCredentials),t.responseType)try{d.responseType=t.responseType}catch(_){if("json"!==t.responseType)throw _}"function"===typeof t.onDownloadProgress&&d.addEventListener("progress",t.onDownloadProgress),"function"===typeof t.onUploadProgress&&d.upload&&d.upload.addEventListener("progress",t.onUploadProgress),t.cancelToken&&t.cancelToken.promise.then((function(t){d&&(d.abort(),f(t),d=null)})),void 0===l&&(l=null),d.send(l)}))}},bc3a:function(t,e,n){t.exports=n("cee4")},c345:function(t,e,n){"use strict";var r=n("c532"),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];t.exports=function(t){var e,n,i,a={};return t?(r.forEach(t.split("\n"),(function(t){if(i=t.indexOf(":"),e=r.trim(t.substr(0,i)).toLowerCase(),n=r.trim(t.substr(i+1)),e){if(a[e]&&o.indexOf(e)>=0)return;a[e]="set-cookie"===e?(a[e]?a[e]:[]).concat([n]):a[e]?a[e]+", "+n:n}})),a):a}},c401:function(t,e,n){"use strict";var r=n("c532");t.exports=function(t,e,n){return r.forEach(n,(function(n){t=n(t,e)})),t}},c532:function(t,e,n){"use strict";var r=n("1d2b"),o=Object.prototype.toString;function i(t){return"[object Array]"===o.call(t)}function a(t){return"undefined"===typeof t}function s(t){return null!==t&&!a(t)&&null!==t.constructor&&!a(t.constructor)&&"function"===typeof t.constructor.isBuffer&&t.constructor.isBuffer(t)}function c(t){return"[object ArrayBuffer]"===o.call(t)}function u(t){return"undefined"!==typeof FormData&&t instanceof FormData}function f(t){var e;return e="undefined"!==typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer&&t.buffer instanceof ArrayBuffer,e}function l(t){return"string"===typeof t}function p(t){return"number"===typeof t}function d(t){return null!==t&&"object"===typeof t}function h(t){return"[object Date]"===o.call(t)}function v(t){return"[object File]"===o.call(t)}function m(t){return"[object Blob]"===o.call(t)}function y(t){return"[object Function]"===o.call(t)}function g(t){return d(t)&&y(t.pipe)}function _(t){return"undefined"!==typeof URLSearchParams&&t instanceof URLSearchParams}function b(t){return t.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"===typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!==typeof window&&"undefined"!==typeof document)}function C(t,e){if(null!==t&&"undefined"!==typeof t)if("object"!==typeof t&&(t=[t]),i(t))for(var n=0,r=t.length;n=0;r--){var o=t[r];"."===o?t.splice(r,1):".."===o?(t.splice(r,1),n++):n&&(t.splice(r,1),n--)}if(e)for(;n--;n)t.unshift("..");return t}function r(t){"string"!==typeof t&&(t+="");var e,n=0,r=-1,o=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!o){n=e+1;break}}else-1===r&&(o=!1,r=e+1);return-1===r?"":t.slice(n,r)}function o(t,e){if(t.filter)return t.filter(e);for(var n=[],r=0;r=-1&&!r;i--){var a=i>=0?arguments[i]:t.cwd();if("string"!==typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(e=a+"/"+e,r="/"===a.charAt(0))}return e=n(o(e.split("/"),(function(t){return!!t})),!r).join("/"),(r?"/":"")+e||"."},e.normalize=function(t){var r=e.isAbsolute(t),a="/"===i(t,-1);return t=n(o(t.split("/"),(function(t){return!!t})),!r).join("/"),t||r||(t="."),t&&a&&(t+="/"),(r?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(o(t,(function(t,e){if("string"!==typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,n){function r(t){for(var e=0;e=0;n--)if(""!==t[n])break;return e>n?[]:t.slice(e,n-e+1)}t=e.resolve(t).substr(1),n=e.resolve(n).substr(1);for(var o=r(t.split("/")),i=r(n.split("/")),a=Math.min(o.length,i.length),s=a,c=0;c=1;--i)if(e=t.charCodeAt(i),47===e){if(!o){r=i;break}}else o=!1;return-1===r?n?"/":".":n&&1===r?"/":t.slice(0,r)},e.basename=function(t,e){var n=r(t);return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},e.extname=function(t){"string"!==typeof t&&(t+="");for(var e=-1,n=0,r=-1,o=!0,i=0,a=t.length-1;a>=0;--a){var s=t.charCodeAt(a);if(47!==s)-1===r&&(o=!1,r=a+1),46===s?-1===e?e=a:1!==i&&(i=1):-1!==e&&(i=-1);else if(!o){n=a+1;break}}return-1===e||-1===r||0===i||1===i&&e===r-1&&e===n+1?"":t.slice(e,r)};var i="b"==="ab".substr(-1)?function(t,e,n){return t.substr(e,n)}:function(t,e,n){return e<0&&(e=t.length+e),t.substr(e,n)}}).call(this,n("4362"))},e683:function(t,e,n){"use strict";t.exports=function(t,e){return e?t.replace(/\/+$/,"")+"/"+e.replace(/^\/+/,""):t}},f6b4:function(t,e,n){"use strict";var r=n("c532");function o(){this.handlers=[]}o.prototype.use=function(t,e){return this.handlers.push({fulfilled:t,rejected:e}),this.handlers.length-1},o.prototype.eject=function(t){this.handlers[t]&&(this.handlers[t]=null)},o.prototype.forEach=function(t){r.forEach(this.handlers,(function(e){null!==e&&t(e)}))},t.exports=o},f6fd:function(t,e){(function(t){var e="currentScript",n=t.getElementsByTagName("script");e in t||Object.defineProperty(t,e,{get:function(){try{throw new Error}catch(r){var t,e=(/.*at [^\(]*\((.*):.+:.+\)$/gi.exec(r.stack)||[!1])[1];for(t in n)if(n[t].src==e||"interactive"==n[t].readyState)return n[t];return null}}})})(document)}}); \ No newline at end of file From 7304d6534e29fadb158f97566030b04c93e33cf0 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Mon, 23 Mar 2020 17:02:29 +0000 Subject: [PATCH 13/13] Added LabThings type to OFM app --- openflexure_microscope/api/app.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index efb53919..60e79639 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -64,6 +64,7 @@ app, labthing = create_app( prefix="/api/v2", title=f"OpenFlexure Microscope {api_microscope.name}", description="Test LabThing-based API for OpenFlexure Microscope", + types=["org.openflexure.microscope"], version=pkg_resources.get_distribution("openflexure_microscope").version, flask_kwargs={"static_url_path": ""}, ) @@ -171,9 +172,6 @@ def cleanup(): atexit.register(cleanup) -# if __name__ == "__main__": -# app.run(host="0.0.0.0", port="5000", threaded=True, debug=True, use_reloader=False) - # Start the app if __name__ == "__main__": from labthings.server.wsgi import Server