diff --git a/python/lsst/ts/rubintv/handlers/pages.py b/python/lsst/ts/rubintv/handlers/pages.py index 3a6818e3..b5d7222f 100644 --- a/python/lsst/ts/rubintv/handlers/pages.py +++ b/python/lsst/ts/rubintv/handlers/pages.py @@ -163,7 +163,9 @@ async def get_camera_mosaic_page( location_name: str, camera_name: str, request: Request, + headerless: bool = False, ) -> Response: + logger.info("Getting mosaic page", headerless=headerless) location, camera = await get_location_camera(location_name, camera_name, request) if not camera.mosaic_view_meta: raise HTTPException(404, "No mosaic found for this camera.") @@ -180,6 +182,7 @@ async def get_camera_mosaic_page( "location": location, "camera": camera.model_dump(), "title": title, + "headerless": headerless, }, ) diff --git a/python/lsst/ts/rubintv/models/models.py b/python/lsst/ts/rubintv/models/models.py index 26259e25..12ec83ee 100644 --- a/python/lsst/ts/rubintv/models/models.py +++ b/python/lsst/ts/rubintv/models/models.py @@ -41,6 +41,8 @@ class Channel(BaseModel): label: str = "" per_day: bool = False colour: str = "" + icon: str = "" + text_colour: str = "#000" class HasButton(BaseModel): @@ -63,8 +65,8 @@ class HasButton(BaseModel): True for a shadow, false otherwise. """ - name: str title: str + name: str = "" logo: str = "" text_colour: str = "#000" text_shadow: bool = False @@ -81,10 +83,17 @@ class MosaicViewMeta(BaseModel): The channel name. metaColumns : list[str] A list of metadata columns. + dataType : str + Presently, "image" or "video" are only options. """ channel: str metaColumns: list[str] + dataType: str = "image" + + +class ExtraButton(HasButton): + linkURL: str class Camera(HasButton): @@ -140,6 +149,7 @@ class Camera(HasButton): image_viewer_link: str = "" copy_row_template: str = "" mosaic_view_meta: list[MosaicViewMeta] = [] + extra_buttons: list[ExtraButton] = [] def seq_channels(self) -> list[Channel]: return [c for c in self.channels if not c.per_day] diff --git a/python/lsst/ts/rubintv/models/models_data.yaml b/python/lsst/ts/rubintv/models/models_data.yaml index 92b350d7..08598cf7 100644 --- a/python/lsst/ts/rubintv/models/models_data.yaml +++ b/python/lsst/ts/rubintv/models/models_data.yaml @@ -13,6 +13,13 @@ bucket_configurations: [ slac, base-usdf, tucson-usdf, summit-usdf ] locations: + - name: test + title: TEST + profile_name: rubin-rubintv-data-summit + bucket_name: rubin-rubintv-data-summit + camera_groups: + Test: [ comcam ] + - name: slac title: SLAC profile_name: rubin-rubintv-data-usdf @@ -225,6 +232,35 @@ cameras: - name: psf_shape_azel title: PSF shape AzEl colour: "#6F58E7" + - name: mount + title: Mount torques + colour: "#58b4e7" + + - name: day_movie + title: Whole Day Movie + per_day: True + color: "#83daee" + icon: movies + - name: last_n_movie + title: Last N Images Movie + per_day: True + color: "#83eebe" + icon: movies + + extra_buttons: + - title: Movies View + name: moviesView + # relative url + linkURL: mosaic + logo: AI-astral-movie.jpg + text_colour: "#fff" + + mosaic_view_meta: + - channel: day_movie + metaColumns: [] + - channel: last_n_movie + metaColumns: [] + copy_row_template: "dataId = {\"day_obs\": {dayObs}, \"seq_num\": \ {seqNum}}" image_viewer_link: "http://ccs.lsst.org/FITSInfo/\ diff --git a/python/lsst/ts/rubintv/static/images/logos/AI-astral-movie.jpg b/python/lsst/ts/rubintv/static/images/logos/AI-astral-movie.jpg new file mode 100644 index 00000000..2f9caff3 Binary files /dev/null and b/python/lsst/ts/rubintv/static/images/logos/AI-astral-movie.jpg differ diff --git a/python/lsst/ts/rubintv/templates/_layout.jinja b/python/lsst/ts/rubintv/templates/_layout.jinja index 4163a67f..0eaed855 100644 --- a/python/lsst/ts/rubintv/templates/_layout.jinja +++ b/python/lsst/ts/rubintv/templates/_layout.jinja @@ -34,6 +34,7 @@ window.onpageshow = function (event) { window.APP_DATA = {} window.APP_DATA.locationName = {{ location.name|tojson if location else "" }} window.APP_DATA.siteLocation = {{ site_location|tojson if site_location else "null" }} + window.APP_DATA.imagesURL = "{{ url_for('static', path='images')}}" window.APP_DATA.pathPrefix = {{ path_prefix|tojson if path_prefix else "null" }} diff --git a/python/lsst/ts/rubintv/templates/mosaic.jinja b/python/lsst/ts/rubintv/templates/mosaic.jinja index 7a13a95d..a4912689 100644 --- a/python/lsst/ts/rubintv/templates/mosaic.jinja +++ b/python/lsst/ts/rubintv/templates/mosaic.jinja @@ -1,8 +1,32 @@ {% extends "_camera.jinja" %} {% block header %} + {% if not headerless %} + {{ super() }} + {% endif %} {% endblock header %} +{% block pagesubtitle %} + {% if not headerless %} + Current Mosaic + {% endif %} +{% endblock pagesubtitle %} + +{% block breadcrumb %} + {% if not headerless %} + Home> + {{ location.title }}> + {{ camera.title }}> +

Current Mosaic

+ {% else %} + + {% endif %} +{% endblock breadcrumb %} + {% block content %}
{# React MosiacView root here #} @@ -11,5 +35,4 @@ {% block footer_scripts %} - {{ super() }} {% endblock footer_scripts %} diff --git a/src/js/components/MosaicView.js b/src/js/components/MosaicView.js index aeb4ed56..d15bb22f 100644 --- a/src/js/components/MosaicView.js +++ b/src/js/components/MosaicView.js @@ -97,7 +97,7 @@ function ChannelView({ locationName, camera, view, currentMeta }) { : { dayObs } ) } - - - - -
- ) - } else { - return ( -
-

No image for today

-
- ) +function ChannelMedia({ locationName, camera, event }) { + const { filename, ext } = event + const mediaURL = buildMediaURI(locationName, camera.name, event.channel_name, filename) + switch (ext) { + case 'mp4': + return + case 'jpg': + case 'jpeg': + case 'png': + return + default: + return } } -ChannelImage.propTypes = { +ChannelMedia.propTypes = { locationName: PropTypes.string, camera: cameraType, event: eventType, } +function ChannelImage({mediaURL}) { + const imgSrc = new URL(`event_image/${mediaURL}`, APP_DATA.baseUrl) + return ( +
+ + + +
+ ) +} +ChannelImage.propTypes = { + mediaURL: PropTypes.string, +} + +function ChannelVideo({mediaURL}) { + const videoSrc = new URL(`event_video/${mediaURL}`, APP_DATA.baseUrl) + return ( +
+ + + +
+ ) +} +ChannelVideo.propTypes = { + mediaURL: PropTypes.string, +} + +function ChannelMediaPlaceholder() { + return ( +
+

Nothing today yet

+
+ ) +} + function ChannelMetadata({ view, metadata }) { const { channel, metaColumns: viewColumns, latestEvent: {seq_num: seqNum} } = view + if (viewColumns.length == 0) { + return + } const columns = [...commonColumns, ...viewColumns] const metadatum = metadata[seqNum] || {} return ( @@ -172,5 +202,5 @@ ChannelMetadata.propTypes = { metadata: metadataType, } -const buildImageURI = (locationName, cameraName, channelName, filename) => +const buildMediaURI = (locationName, cameraName, channelName, filename) => `${locationName}/${cameraName}/${channelName}/${filename}` diff --git a/src/js/components/PerDay.js b/src/js/components/PerDay.js index 2b189248..2e3c6425 100644 --- a/src/js/components/PerDay.js +++ b/src/js/components/PerDay.js @@ -1,33 +1,83 @@ -import React, { useState, useEffect } from 'react' -import PropTypes from 'prop-types' -import { cameraType, eventType } from './componentPropTypes' +import React, { useState, useEffect } from "react" +import PropTypes from "prop-types" +import { cameraType, eventType } from "./componentPropTypes" -function PerDayChannels ({ camera, date, perDay }) { +function Button({ clsName, url, bckCol, iconUrl, logoURL, label, date, textColour, textShadow }) { + const style = { + backgroundColor: bckCol, + color: textColour, + backgroundImage: `url(${logoURL})`, + backgroundSize: 'contain', + } + clsName = !!logoURL ? clsName + " button-logo" : clsName + clsName = textShadow ? clsName + " t-shadow" : clsName + + return ( + + {iconUrl && } + {label} + {date && {date}} + + ) +} + +function PerDayChannels({ camera, date, perDay }) { const baseUrl = window.APP_DATA.baseUrl + const imageRoot = window.APP_DATA.imagesURL + const isHistorical = window.APP_DATA.isHistorical const locationName = document.documentElement.dataset.locationname const channels = camera.channels + + const getImageURL = (path) => { + const [base, queriesMaybe] = imageRoot.split("?") + const queries = queriesMaybe ? "?" + queriesMaybe : "" + return new URL(path + queries, base + "/") + } + return ( (perDay && Object.entries(perDay).length > 0) && (