Add sidebar to gallery for filtering and bulk actions

This commit is contained in:
Julian Stirling 2026-06-25 17:00:03 +01:00
parent 2dcd386661
commit 8d201dd1a9
5 changed files with 154 additions and 66 deletions

View file

@ -198,6 +198,63 @@
} }
} }
.ofm-tab {
--ofm-navbar-height: 50px;
}
.ofm-tab-navbar {
position: sticky;
top: 0;
z-index: 1000;
display: flex;
justify-content: flex-end;
height: var(--ofm-navbar-height);
border-width: 0 0 1px;
border-style: solid;
border-color: rgba(180, 180, 180, 0.25);
padding-right: 20px;
background: #fff;
}
.hook-inverse() {
.ofm-tab-navbar {
background: #252525;
}
}
/*
* Sidebar
*/
.ofm-container-with-sidebar
{
display: grid;
grid-template-columns: 1fr 0;
height: 100%;
}
.ofm-container-with-sidebar.sidebar-open {
grid-template-columns: 1fr 250px;
}
.ofm-sidebar {
padding: 10px;
overflow: hidden;
border-left: none;
display: none;
background:#f5f5f5;
}
.ofm-sidebar.open {
border-left: 1px solid rgba(180, 180, 180, 0.25);
display: block;
}
.hook-inverse() {
.ofm-sidebar{
background: #282626;
}
}
/* /*
* Links * Links
*/ */

View file

@ -267,7 +267,6 @@ export default {
#container-left { #container-left {
overflow: auto; overflow: auto;
scrollbar-gutter: stable;
background-color: rgba(180, 180, 180, 0.025); background-color: rgba(180, 180, 180, 0.025);
width: 100%; width: 100%;
height: 100%; height: 100%;

View file

@ -104,7 +104,8 @@ export default {
.multi-select { .multi-select {
position: relative; position: relative;
width: 250px; width: 100%;
max-width: 250px;
} }
.dropdown-top { .dropdown-top {

View file

@ -2,7 +2,7 @@
<div <div
v-if="!(requireConnection && !ready)" v-if="!(requireConnection && !ready)"
:hidden="currentTab != tabID" :hidden="currentTab != tabID"
class="uk-width-expand uk-height-1-1" class="ofm-tab uk-width-expand uk-height-1-1"
> >
<slot></slot> <slot></slot>
</div> </div>

View file

@ -1,10 +1,63 @@
<template> <template>
<div ref="galleryDisplay" class="gallery-display uk-padding uk-padding-remove-top"> <div ref="galleryDisplay" class="gallery-display">
<!-- Gallery nav bar --> <!-- Gallery nav bar -->
<nav class="gallery-navbar uk-navbar-container uk-navbar-transparent" uk-navbar="mode: click"> <nav class="ofm-tab-navbar">
<!-- Right side buttons --> <!-- Right side buttons -->
<div class="uk-navbar-right"> <div class="uk-navbar-right">
<div class="uk-grid"> <div class="uk-grid">
<div>
<button
class="uk-button uk-button-default uk-width-1-1 gallery-button"
type="button"
@click="refreshGallery()"
>
Refresh
</button>
</div>
<div>
<button
class="sidebar-toggle uk-button uk-button-default uk-width-1-1 gallery-button"
type="button"
@click="sidebarOpen = !sidebarOpen"
>
<div class="sidebar-toggle-text" :class="{ open: sidebarOpen }"></div>
</button>
</div>
</div>
</div>
</nav>
<gallery-modal ref="viewerModal" :selected-item="selectedItem" :base-uri="baseUri" />
<!-- Gallery -->
<div
v-if="ready"
class="ofm-container-with-sidebar"
:class="{ 'sidebar-open': sidebarOpen }"
uk-lightbox="toggle: .lightbox-link"
>
<!-- Gallery capture cards -->
<main class="gallery-main">
<div class="gallery-grid uk-grid-match" uk-grid>
<div v-if="noItems">
<h2>Nothing to show</h2>
<p>There is no captured data to show.</p>
</div>
<div v-for="itemData in paginatedItems" :key="itemData.id">
<gallery-card
:item-data="itemData"
@viewer-requested="showItem"
@update-requested="refreshGallery"
/>
</div>
</div>
<PaginateLinks
:total-pages="totalPages"
:current-page="currentPage"
@change-page="changePage"
/>
</main>
<aside id="sidebar" class="ofm-sidebar" :class="{ open: sidebarOpen }">
<div class="gallery-button"> <div class="gallery-button">
<multi-select-dropdown <multi-select-dropdown
v-model="selectedCardTypes" v-model="selectedCardTypes"
@ -12,19 +65,12 @@
title="Filter Gallery" title="Filter Gallery"
/> />
</div> </div>
<div class="gallery-button"> <div
<action-button v-for="bulkAction in bulkActions"
class="uk-width-1-1" :key="'action' + bulkAction.thing + bulkAction.action"
thing="smart_scan" class="gallery-button"
action="stitch_all_scans" >
submit-label="Stitch All Remaining" <server-specified-action-button :action-data="bulkAction" @error="modalError" />
:can-terminate="true"
:button-primary="false"
:modal-progress="true"
:requires-confirmation="true"
:confirmation-message="'<h3>Stitch all unstitched scans?</h3><br>Depending on the number and size of scans, this may be slow, and your microscope should not be used during the stitching.'"
@error="modalError"
/>
</div> </div>
<div class="gallery-button"> <div class="gallery-button">
<action-button <action-button
@ -42,50 +88,16 @@
@error="modalError" @error="modalError"
/> />
</div> </div>
<div> </aside>
<button
class="uk-button uk-button-default uk-width-1-1 gallery-button"
type="button"
@click="refreshGallery()"
>
Refresh
</button>
</div> </div>
</div> </div>
</div>
</nav>
<gallery-modal ref="viewerModal" :selected-item="selectedItem" :base-uri="baseUri" />
<!-- Gallery -->
<div v-if="ready" class="uk-padding-remove-top" uk-lightbox="toggle: .lightbox-link">
<!-- Gallery capture cards -->
<div class="gallery-grid uk-grid-match" uk-grid>
<div v-if="noItems">
<h2>Nothing to show</h2>
<p>There is no captured data to show.</p>
</div>
<div v-for="itemData in paginatedItems" :key="itemData.id">
<gallery-card
:item-data="itemData"
@viewer-requested="showItem"
@update-requested="refreshGallery"
/>
</div>
</div>
</div>
<PaginateLinks
:total-pages="totalPages"
:current-page="currentPage"
@change-page="changePage"
/>
</div>
</template> </template>
<script> <script>
import PaginateLinks from "@/components/genericComponents/paginateLinks.vue"; import PaginateLinks from "@/components/genericComponents/paginateLinks.vue";
import MultiSelectDropdown from "@/components/genericComponents/multiSelectDropdown.vue"; import MultiSelectDropdown from "@/components/genericComponents/multiSelectDropdown.vue";
import actionButton from "../labThingsComponents/actionButton.vue"; import actionButton from "@/components/labThingsComponents/actionButton.vue";
import ServerSpecifiedActionButton from "@/components/labThingsComponents/serverSpecifiedActionButton.vue";
import galleryCard from "./galleryComponents/galleryCard.vue"; import galleryCard from "./galleryComponents/galleryCard.vue";
import galleryModal from "./galleryComponents/galleryViewer.vue"; import galleryModal from "./galleryComponents/galleryViewer.vue";
import { eventBus } from "../../eventBus.js"; import { eventBus } from "../../eventBus.js";
@ -102,6 +114,7 @@ export default {
galleryModal, galleryModal,
PaginateLinks, PaginateLinks,
MultiSelectDropdown, MultiSelectDropdown,
ServerSpecifiedActionButton,
}, },
emits: ["scrollTop"], emits: ["scrollTop"],
@ -115,6 +128,8 @@ export default {
itemsPerPage: 18, itemsPerPage: 18,
selectedCardTypes: [], selectedCardTypes: [],
allCardTypes: [], allCardTypes: [],
bulkActions: [],
sidebarOpen: false,
}; };
}, },
@ -167,6 +182,7 @@ export default {
); );
this.allCardTypes = await this.readThingProperty("gallery", "card_types"); this.allCardTypes = await this.readThingProperty("gallery", "card_types");
this.selectedCardTypes = this.allCardTypes; this.selectedCardTypes = this.allCardTypes;
this.bulkActions = await this.readThingProperty("gallery", "bulk_actions");
// Update on mount (does nothing if not connected) // Update on mount (does nothing if not connected)
await this.refreshGallery(); await this.refreshGallery();
// A global signal listener to perform a gallery refresh // A global signal listener to perform a gallery refresh
@ -239,19 +255,34 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.gallery-navbar { .gallery-display {
border-width: 0 0 1px; height: 100%;
border-style: solid; overflow-y: hidden;
border-color: rgba(180, 180, 180, 0.25);
} }
.gallery-navbar, .gallery-main {
.gallery-folder-heading { height: calc(100% - var(--ofm-navbar-height));
margin-bottom: 30px; box-sizing: border-box;
overflow-y: auto;
padding-left: 20px;
padding-top: 20px;
} }
.gallery-button { .gallery-button {
margin-top: 5px; margin-top: 5px;
margin-bottom: 2px; margin-bottom: 2px;
} }
.sidebar-toggle {
width: 40px;
}
.sidebar-toggle-text {
font-size: 24px;
transition: transform 0.25s ease;
}
.sidebar-toggle-text.open {
transform: rotate(180deg);
}
</style> </style>