diff --git a/src/openflexure_microscope_server/things/camera/__init__.py b/src/openflexure_microscope_server/things/camera/__init__.py index 5a39f11c..0d61bb23 100644 --- a/src/openflexure_microscope_server/things/camera/__init__.py +++ b/src/openflexure_microscope_server/things/camera/__init__.py @@ -332,6 +332,13 @@ class BaseCamera(OFMThing, ABC): self.logger.info(f"Deleting: {capture}") os.remove(capture) + def get_gallery_bulk_actions(self) -> list[ActionButton]: + """Return the bulk gallery actions for cameras. + + By default there are no bulk actions. + """ + return [] + @lt.endpoint( "delete", "capture/{name}", diff --git a/src/openflexure_microscope_server/things/gallery.py b/src/openflexure_microscope_server/things/gallery.py index 23f4f1bd..35b0aa32 100644 --- a/src/openflexure_microscope_server/things/gallery.py +++ b/src/openflexure_microscope_server/things/gallery.py @@ -12,6 +12,7 @@ from pydantic import BaseModel import labthings_fastapi as lt from openflexure_microscope_server.things import OFMThing +from openflexure_microscope_server.ui import ActionButton @runtime_checkable @@ -38,6 +39,8 @@ class GalleryCompatibleThing(Protocol): def delete_all_gallery_items(self) -> None: ... # noqa: D102 + def get_gallery_bulk_actions(self) -> list[ActionButton]: ... # noqa: D102 + class GalleryThing(lt.Thing): """A Thing for communicating with the front end gallery.""" @@ -125,6 +128,19 @@ class GalleryThing(lt.Thing): raise RuntimeError("Cannot access card_types before server has started.") return self._card_types + # Cache result after first call. + _bulk_actions: Optional[list[ActionButton]] = None + + @lt.property + def bulk_actions(self) -> list[ActionButton]: + """All bulk actions.""" + if self._bulk_actions is None: + actions: list[ActionButton] = [] + for thing in self.gallery_providing_things.values(): + actions += thing.get_gallery_bulk_actions() + self._bulk_actions = actions + return self._bulk_actions + @lt.property def list_data(self) -> list[dict[str, Any]]: """List the data from all registered things. diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index d22cb55f..337c8b40 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -32,6 +32,7 @@ import labthings_fastapi as lt from openflexure_microscope_server import scan_directories, stitching from openflexure_microscope_server.things import OFMThing +from openflexure_microscope_server.ui import ActionButton, action_button_for from openflexure_microscope_server.utilities import coerce_thing_selector # Things @@ -199,6 +200,26 @@ class SmartScanThing(OFMThing): self.logger.info(f"Deleting: {scan_name}") self._delete_scan(scan_name) + def get_gallery_bulk_actions(self) -> list[ActionButton]: + """Return the bulk gallery actions for smart scan.""" + # Stitch all scans + return [ + action_button_for( + self, + "stitch_all_scans", + submit_label="Stitch All Unstitched Scans", + can_terminate=True, + button_primary=False, + modal_progress=True, + requires_confirmation=True, + confirmation_message=( + "

Stitch all unstitched scans?

" + "
Depending on the number and size of scans, this may be slow, " + "and your microscope should not be used during the stitching.'" + ), + ) + ] + # Note that the default detector name is set at init. This is over written if # setting is loaded from disk. @lt.setting diff --git a/tests/unit_tests/test_gallery.py b/tests/unit_tests/test_gallery.py index 2a099185..5701beeb 100644 --- a/tests/unit_tests/test_gallery.py +++ b/tests/unit_tests/test_gallery.py @@ -15,6 +15,7 @@ from openflexure_microscope_server.things.gallery import ( GalleryCompatibleThing, GalleryThing, ) +from openflexure_microscope_server.ui import ActionButton from ..shared_utils.lt_test_utils import LabThingsTestEnv @@ -63,6 +64,10 @@ class MinimalGalleryClass: def delete_all_gallery_items(self) -> None: """Mock deleting all the data.""" + def get_gallery_bulk_actions(self) -> list[ActionButton]: + """No bulk actions.""" + return [] + class MinimalGallerySource(OFMThing, MinimalGalleryClass): """Minimal example of a Thing that can show data in the gallery. @@ -94,6 +99,10 @@ class BadGallerySource(OFMThing): def delete_all_gallery_items(self) -> None: """Mock deleting all the data.""" + def get_gallery_bulk_actions(self) -> list[ActionButton]: + """No bulk actions.""" + return [] + class BadMockGalleryData(BaseModel): """Mock gallery data without a card type.""" @@ -119,6 +128,10 @@ class BadGallerySource2(OFMThing): def delete_all_gallery_items(self) -> None: """Mock deleting all the data.""" + def get_gallery_bulk_actions(self) -> list[ActionButton]: + """No bulk actions.""" + return [] + def test_gallery_compatible_protocol(): """Check the gallery compatible protocol detects compatible classes only."""