From b1a3ce88770be9260e3bc8abad7a20f943345fb2 Mon Sep 17 00:00:00 2001 From: ugyballoons Date: Fri, 1 Nov 2024 16:32:28 +0000 Subject: [PATCH 01/10] Add basic video framestep controls --- python/lsst/ts/rubintv/models/models.py | 11 +- .../lsst/ts/rubintv/models/models_data.yaml | 7 + src/js/components/MosaicView.js | 199 ++++++++++++++---- src/js/modules/utils.js | 10 + src/js/pages/mosaic-view.js | 1 - src/sass/style.sass | 5 + 6 files changed, 183 insertions(+), 50 deletions(-) diff --git a/python/lsst/ts/rubintv/models/models.py b/python/lsst/ts/rubintv/models/models.py index 12ec83ee..127a44ea 100644 --- a/python/lsst/ts/rubintv/models/models.py +++ b/python/lsst/ts/rubintv/models/models.py @@ -72,6 +72,11 @@ class HasButton(BaseModel): text_shadow: bool = False +class MediaType(str, Enum): + IMAGE: str = "image" + VIDEO: str = "video" + + class MosaicViewMeta(BaseModel): """Populated in the models data YAML file, each MosaicViewMeta object pairs a channel name with a set of metadata columns to display alongside the @@ -83,13 +88,13 @@ class MosaicViewMeta(BaseModel): The channel name. metaColumns : list[str] A list of metadata columns. - dataType : str - Presently, "image" or "video" are only options. + dataType : MediaType + Presently, IMAGE or VIDEO are the only options. """ channel: str metaColumns: list[str] - dataType: str = "image" + mediaType: MediaType = MediaType.IMAGE class ExtraButton(HasButton): diff --git a/python/lsst/ts/rubintv/models/models_data.yaml b/python/lsst/ts/rubintv/models/models_data.yaml index b008cfb3..2a34899c 100644 --- a/python/lsst/ts/rubintv/models/models_data.yaml +++ b/python/lsst/ts/rubintv/models/models_data.yaml @@ -218,6 +218,11 @@ cameras: title: Current Image per_day: True + mosaic_view_meta: + - channel: movies + mediaType: video + metaColumns: [] + - name: comcam title: ComCam online: True @@ -257,8 +262,10 @@ cameras: mosaic_view_meta: - channel: day_movie + mediaType: video metaColumns: [] - channel: last_n_movie + mediaType: video metaColumns: [] copy_row_template: "dataId = {\"day_obs\": {dayObs}, \"seq_num\": \ diff --git a/src/js/components/MosaicView.js b/src/js/components/MosaicView.js index d15bb22f..2dfbff11 100644 --- a/src/js/components/MosaicView.js +++ b/src/js/components/MosaicView.js @@ -6,24 +6,55 @@ import { metadataType, mosaicSingleView, } from "./componentPropTypes" +import { _getById, addStrHashCode } from "../modules/utils" + +const FRAMELENGTH = 0.1 +const BACK = -FRAMELENGTH +const FORWARD = FRAMELENGTH + +// add hashing method to String prototype +addStrHashCode() const commonColumns = ["seqNum"] export default function MosaicView({ locationName, camera }) { const [historicalBusy, setHistoricalBusy] = useState(null) const [currentMeta, setCurrentMeta] = useState({}) - const initialViews = camera.mosaic_view_meta.map((view) => ({ - ...view, - latestEvent: {}, - })) + const isFocusable = checkNeedsFocusability() const [views, setViews] = useState(initialViews) + function initialViews() { + const views = camera.mosaic_view_meta.map((view, index) => ({ + ...view, + latestEvent: {}, + hasFocus: index == 0 ? true : false, + })) + return views + } + + function checkNeedsFocusability() { + // Is there more than one video? + const vids = camera.mosaic_view_meta.filter( + ({ mediaType }) => mediaType === "image" + ) + return vids.length > 1 ? true : false + } + + function setHasFocus(thisView) { + setViews((prevViews) => + prevViews.map((view) => + view.channel === thisView.channel + ? { ...view, hasFocus: true } + : { ...view, hasFocus: false } + ) + ) + } useEffect(() => { window.addEventListener("camera", handleMetadataChange) window.addEventListener("historicalStatus", handleHistoricalStateChange) window.addEventListener("channel", handleChannelEvent) - + function handleMetadataChange(event) { const { data, dataType } = event.detail if (Object.entries(data).length < 1 || dataType !== "metadata") { @@ -58,21 +89,22 @@ export default function MosaicView({ locationName, camera }) { ) window.removeEventListener("channel", handleChannelEvent) } - }) + }, []) return (
@@ -84,59 +116,75 @@ MosaicView.propTypes = { camera: cameraType, } -function ChannelView({ locationName, camera, view, currentMeta }) { +function ChannelView({ + locationName, + camera, + view, + currentMeta, + setHasFocus, + isFocusable, +}) { const channel = camera.channels.find(({ name }) => name === view.channel) if (!channel) { return

Channel {view.channel} not found

} - const { latestEvent: { day_obs: dayObs }} = view + const { + hasFocus, + mediaType, + latestEvent: { day_obs: dayObs }, + } = view + const clsName = ["view", `view-${mediaType}`, hasFocus ? "has-focus" : null] + .join(" ") + .trimEnd() + const clickHandler = isFocusable ? () => setHasFocus(view) : null return ( - <> -

{channel.title} - { dayObs && ( - : { dayObs } - ) } +
  • +

    + {channel.title} + {dayObs && : {dayObs}}

    - - + +
  • ) } ChannelView.propTypes = { locationName: PropTypes.string, camera: cameraType, view: mosaicSingleView, - currentMeta: metadataType -} - -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 + currentMeta: metadataType, +} + +function ChannelMedia({ locationName, camera, event, mediaType }) { + const { filename } = event + if (!filename) return + const mediaURL = buildMediaURI( + locationName, + camera.name, + event.channel_name, + filename + ) + switch (mediaType) { + case "video": + return + case "image": default: - return + return } } ChannelMedia.propTypes = { locationName: PropTypes.string, camera: cameraType, event: eventType, + mediaType: PropTypes.string, } -function ChannelImage({mediaURL}) { +function ChannelImage({ mediaURL }) { const imgSrc = new URL(`event_image/${mediaURL}`, APP_DATA.baseUrl) return (
    @@ -150,15 +198,18 @@ ChannelImage.propTypes = { mediaURL: PropTypes.string, } -function ChannelVideo({mediaURL}) { +function ChannelVideo({ mediaURL }) { const videoSrc = new URL(`event_video/${mediaURL}`, APP_DATA.baseUrl) + const vidID = `v_${mediaURL.hashCode()}` return (
    - + +
    ) } @@ -175,7 +226,11 @@ function ChannelMediaPlaceholder() { } function ChannelMetadata({ view, metadata }) { - const { channel, metaColumns: viewColumns, latestEvent: {seq_num: seqNum} } = view + const { + channel, + metaColumns: viewColumns, + latestEvent: { seq_num: seqNum }, + } = view if (viewColumns.length == 0) { return } @@ -188,7 +243,9 @@ function ChannelMetadata({ view, metadata }) { const value = metadatum[column] ?? "No value set" return ( - {column} + + {column} + {value} ) @@ -204,3 +261,53 @@ ChannelMetadata.propTypes = { const buildMediaURI = (locationName, cameraName, channelName, filename) => `${locationName}/${cameraName}/${channelName}/${filename}` + +function frameStep(vidID, timeDelta) { + console.log('frame delta is: ',timeDelta) + const video = _getById(vidID) + pauseVideo(video) + if (timeDelta < 0 && video.currentTime < 0) { + video.currentTime = 0 + } else if (timeDelta > 0 && video.currentTime > video.duration) { + video.currentTime = video.duration + } else { + video.currentTime = video.currentTime + timeDelta + } +} + +window.onkeydown = videoControl + +function videoControl(e) { + const video = document.querySelector(".view-video.has-focus video") + if (!video) { + return + } + const key = e.code + let timeDelta = 0 + switch(key) { + case "ArrowLeft": + timeDelta = BACK + break + case "ArrowRight": + timeDelta = FORWARD + break + case "Space": + if (video.isPaused) { + console.log('Gonna play again!') + video.play() + } else { + video.pause() + } + } + if (timeDelta) { + frameStep(video.id, timeDelta) + } +} + +function pauseVideo(video) { + if (!video.isPaused) { + video.pause() + return true + } + return false +} diff --git a/src/js/modules/utils.js b/src/js/modules/utils.js index dc0d3490..8aa9864f 100644 --- a/src/js/modules/utils.js +++ b/src/js/modules/utils.js @@ -125,3 +125,13 @@ export function getWebSockURL (name) { const appName = window.location.pathname.split('/')[1] return `${wsProtocol}//${hostname}/${appName}/${name}/` } + +export function addStrHashCode () { + String.prototype.hashCode = function() { + var hash = 0, i = 0, len = this.length + while ( i < len ) { + hash = ((hash << 5) - hash + this.charCodeAt(i++)) << 0 + } + return hash + } +} diff --git a/src/js/pages/mosaic-view.js b/src/js/pages/mosaic-view.js index ea2ec460..87d3e353 100644 --- a/src/js/pages/mosaic-view.js +++ b/src/js/pages/mosaic-view.js @@ -15,7 +15,6 @@ import { _getById } from "../modules/utils" ws.subscribe("service", "channel", locationName, camera.name, view.channel) const channel = camera.channels.find(({name}) => name === view.channel) if (!channel.per_day) { - console.log(`${channel.name} is sequenced`) hasSequencedChannels = true } }) diff --git a/src/sass/style.sass b/src/sass/style.sass index 5745f224..3cd68e0d 100644 --- a/src/sass/style.sass +++ b/src/sass/style.sass @@ -549,6 +549,11 @@ section width: 100% padding: 1em background-color: rgba(0, 0, 0, 0.2) + transition: 0.3s box-shadow + // complicated looking way of highlighting only one + // selected video view in a list of video views + :has(.view-video:not(.has-focus)) .has-focus + box-shadow: 0 0 13px rgba(255,255,255,0.3) .viewImage width: 100% From d63dc7c9c50944ba61ba66e972c4f81eae28585d Mon Sep 17 00:00:00 2001 From: ugyballoons Date: Mon, 4 Nov 2024 16:35:24 +0000 Subject: [PATCH 02/10] Only show controls once video has loaded --- src/js/components/MosaicView.js | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/js/components/MosaicView.js b/src/js/components/MosaicView.js index 2dfbff11..d7df02d4 100644 --- a/src/js/components/MosaicView.js +++ b/src/js/components/MosaicView.js @@ -199,17 +199,24 @@ ChannelImage.propTypes = { } function ChannelVideo({ mediaURL }) { + const [isLoaded, setIsLoaded] = useState(false) + const videoSrc = new URL(`event_video/${mediaURL}`, APP_DATA.baseUrl) const vidID = `v_${mediaURL.hashCode()}` return (
    - - - + { isLoaded && ( +
    + + +
    + ) + }
    ) } @@ -290,14 +297,6 @@ function videoControl(e) { break case "ArrowRight": timeDelta = FORWARD - break - case "Space": - if (video.isPaused) { - console.log('Gonna play again!') - video.play() - } else { - video.pause() - } } if (timeDelta) { frameStep(video.id, timeDelta) @@ -307,7 +306,5 @@ function videoControl(e) { function pauseVideo(video) { if (!video.isPaused) { video.pause() - return true } - return false } From ff283b242d3ef52db038b1d6afb14b31c0570c91 Mon Sep 17 00:00:00 2001 From: ugyballoons Date: Tue, 5 Nov 2024 15:41:22 +0000 Subject: [PATCH 03/10] Add greater specifity to css selector --- src/sass/style.sass | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sass/style.sass b/src/sass/style.sass index 3cd68e0d..b489354f 100644 --- a/src/sass/style.sass +++ b/src/sass/style.sass @@ -544,16 +544,18 @@ section justify-content: space-between gap: 1em align-items: flex-start + // Complicated looking way of highlighting only one + // selected video view in a list of video views. + &:has(.view-video:not(.has-focus)) .has-focus + box-shadow: 0 0 13px rgba(255,255,255,0.3) .view width: 100% padding: 1em background-color: rgba(0, 0, 0, 0.2) transition: 0.3s box-shadow - // complicated looking way of highlighting only one - // selected video view in a list of video views - :has(.view-video:not(.has-focus)) .has-focus - box-shadow: 0 0 13px rgba(255,255,255,0.3) + &.view-video.has-focus + background: #000 .viewImage width: 100% From 327ce6edcfc423af7b718143c5382e558fcae7cc Mon Sep 17 00:00:00 2001 From: ugyballoons Date: Tue, 5 Nov 2024 15:43:33 +0000 Subject: [PATCH 04/10] Refactor String prototype hash function --- src/js/components/MosaicView.js | 9 +++------ src/js/modules/utils.js | 12 +++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/js/components/MosaicView.js b/src/js/components/MosaicView.js index d7df02d4..f282a269 100644 --- a/src/js/components/MosaicView.js +++ b/src/js/components/MosaicView.js @@ -6,15 +6,12 @@ import { metadataType, mosaicSingleView, } from "./componentPropTypes" -import { _getById, addStrHashCode } from "../modules/utils" +import { _getById, getStrHashCode } from "../modules/utils" const FRAMELENGTH = 0.1 const BACK = -FRAMELENGTH const FORWARD = FRAMELENGTH -// add hashing method to String prototype -addStrHashCode() - const commonColumns = ["seqNum"] export default function MosaicView({ locationName, camera }) { @@ -35,7 +32,7 @@ export default function MosaicView({ locationName, camera }) { function checkNeedsFocusability() { // Is there more than one video? const vids = camera.mosaic_view_meta.filter( - ({ mediaType }) => mediaType === "image" + ({ mediaType }) => mediaType === "video" ) return vids.length > 1 ? true : false } @@ -202,7 +199,7 @@ function ChannelVideo({ mediaURL }) { const [isLoaded, setIsLoaded] = useState(false) const videoSrc = new URL(`event_video/${mediaURL}`, APP_DATA.baseUrl) - const vidID = `v_${mediaURL.hashCode()}` + const vidID = `v_${getStrHashCode(mediaURL)}` return (
    diff --git a/src/js/modules/utils.js b/src/js/modules/utils.js index 8aa9864f..747d1be3 100644 --- a/src/js/modules/utils.js +++ b/src/js/modules/utils.js @@ -126,12 +126,10 @@ export function getWebSockURL (name) { return `${wsProtocol}//${hostname}/${appName}/${name}/` } -export function addStrHashCode () { - String.prototype.hashCode = function() { - var hash = 0, i = 0, len = this.length - while ( i < len ) { - hash = ((hash << 5) - hash + this.charCodeAt(i++)) << 0 - } - return hash +export function getStrHashCode (str) { + var hash = 0, i = 0, len = str.length + while ( i < len ) { + hash = ((hash << 5) - hash + str.charCodeAt(i++)) << 0 } + return hash } From f10fb21fd5b11df0f40a5eb4edbb9b9f578e2f4b Mon Sep 17 00:00:00 2001 From: ugyballoons Date: Wed, 6 Nov 2024 13:15:21 +0000 Subject: [PATCH 05/10] Add selector explanation and remove unwanted styling --- src/sass/style.sass | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sass/style.sass b/src/sass/style.sass index b489354f..c510a8a0 100644 --- a/src/sass/style.sass +++ b/src/sass/style.sass @@ -546,6 +546,9 @@ section align-items: flex-start // Complicated looking way of highlighting only one // selected video view in a list of video views. + // Only apply .has-focus if there is at least one + // .view-video element within .views that does + // not have the .has-focus class. &:has(.view-video:not(.has-focus)) .has-focus box-shadow: 0 0 13px rgba(255,255,255,0.3) @@ -554,8 +557,6 @@ section padding: 1em background-color: rgba(0, 0, 0, 0.2) transition: 0.3s box-shadow - &.view-video.has-focus - background: #000 .viewImage width: 100% From b269bcea67ee81a56923093010808b6eb2a8d072 Mon Sep 17 00:00:00 2001 From: ugyballoons Date: Wed, 6 Nov 2024 13:19:47 +0000 Subject: [PATCH 06/10] Refactor media types --- src/js/components/MosaicView.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/js/components/MosaicView.js b/src/js/components/MosaicView.js index f282a269..7e12e1af 100644 --- a/src/js/components/MosaicView.js +++ b/src/js/components/MosaicView.js @@ -12,6 +12,11 @@ const FRAMELENGTH = 0.1 const BACK = -FRAMELENGTH const FORWARD = FRAMELENGTH +const MediaType = { + IMAGE: "image", + VIDEO: "video" +} + const commonColumns = ["seqNum"] export default function MosaicView({ locationName, camera }) { @@ -32,7 +37,7 @@ export default function MosaicView({ locationName, camera }) { function checkNeedsFocusability() { // Is there more than one video? const vids = camera.mosaic_view_meta.filter( - ({ mediaType }) => mediaType === "video" + ({ mediaType }) => mediaType === MediaType.VIDEO ) return vids.length > 1 ? true : false } @@ -167,9 +172,9 @@ function ChannelMedia({ locationName, camera, event, mediaType }) { filename ) switch (mediaType) { - case "video": + case MediaType.VIDEO: return - case "image": + case MediaType.IMAGE: default: return } From 7655b5312c28cc3d5a4fd4bea1af2b55f7c60695 Mon Sep 17 00:00:00 2001 From: ugyballoons Date: Wed, 6 Nov 2024 13:53:31 +0000 Subject: [PATCH 07/10] Rename video view selection attributes Fix only selecting first video if it is first in list. --- .../lsst/ts/rubintv/models/models_data.yaml | 3 + src/js/components/MosaicView.js | 59 +++++++++++-------- src/sass/style.sass | 10 ++-- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/python/lsst/ts/rubintv/models/models_data.yaml b/python/lsst/ts/rubintv/models/models_data.yaml index 2a34899c..8a0c64b8 100644 --- a/python/lsst/ts/rubintv/models/models_data.yaml +++ b/python/lsst/ts/rubintv/models/models_data.yaml @@ -219,6 +219,9 @@ cameras: per_day: True mosaic_view_meta: + - channel: stills + mediaType: image + metaColumns: [] - channel: movies mediaType: video metaColumns: [] diff --git a/src/js/components/MosaicView.js b/src/js/components/MosaicView.js index 7e12e1af..6434c3a4 100644 --- a/src/js/components/MosaicView.js +++ b/src/js/components/MosaicView.js @@ -14,7 +14,7 @@ const FORWARD = FRAMELENGTH const MediaType = { IMAGE: "image", - VIDEO: "video" + VIDEO: "video", } const commonColumns = ["seqNum"] @@ -22,19 +22,24 @@ const commonColumns = ["seqNum"] export default function MosaicView({ locationName, camera }) { const [historicalBusy, setHistoricalBusy] = useState(null) const [currentMeta, setCurrentMeta] = useState({}) - const isFocusable = checkNeedsFocusability() const [views, setViews] = useState(initialViews) function initialViews() { - const views = camera.mosaic_view_meta.map((view, index) => ({ - ...view, - latestEvent: {}, - hasFocus: index == 0 ? true : false, - })) + let videoCount = 0 + const views = camera.mosaic_view_meta.map((view) => { + const isVideo = view.mediaType === MediaType.VIDEO + videoCount += isVideo ? 1 : 0 + return { + ...view, + latestEvent: {}, + // only apply 'selected == true' to first video + selected: isVideo && videoCount == 1 ? true : false, + } + }) return views } - function checkNeedsFocusability() { + function hasMultipleVideos() { // Is there more than one video? const vids = camera.mosaic_view_meta.filter( ({ mediaType }) => mediaType === MediaType.VIDEO @@ -42,12 +47,12 @@ export default function MosaicView({ locationName, camera }) { return vids.length > 1 ? true : false } - function setHasFocus(thisView) { + function selectView(thisView) { setViews((prevViews) => prevViews.map((view) => view.channel === thisView.channel - ? { ...view, hasFocus: true } - : { ...view, hasFocus: false } + ? { ...view, selected: true } + : { ...view, selected: false } ) ) } @@ -103,8 +108,8 @@ export default function MosaicView({ locationName, camera }) { camera={camera} view={view} currentMeta={currentMeta} - setHasFocus={setHasFocus} - isFocusable={isFocusable} + selectView={selectView} + isSelectable={hasMultipleVideos} key={view.channel} /> ) @@ -123,22 +128,22 @@ function ChannelView({ camera, view, currentMeta, - setHasFocus, - isFocusable, + selectView, + isSelectable, }) { const channel = camera.channels.find(({ name }) => name === view.channel) if (!channel) { return

    Channel {view.channel} not found

    } const { - hasFocus, + selected, mediaType, latestEvent: { day_obs: dayObs }, } = view - const clsName = ["view", `view-${mediaType}`, hasFocus ? "has-focus" : null] + const clsName = ["view", `view-${mediaType}`, selected ? "selected" : null] .join(" ") .trimEnd() - const clickHandler = isFocusable ? () => setHasFocus(view) : null + const clickHandler = isSelectable ? () => selectView(view) : null return (
  • @@ -208,17 +213,23 @@ function ChannelVideo({ mediaURL }) { return (
    - - { isLoaded && ( + {isLoaded && (
    - ) - } + )}
    ) } @@ -272,7 +283,7 @@ const buildMediaURI = (locationName, cameraName, channelName, filename) => `${locationName}/${cameraName}/${channelName}/${filename}` function frameStep(vidID, timeDelta) { - console.log('frame delta is: ',timeDelta) + console.log("frame delta is: ", timeDelta) const video = _getById(vidID) pauseVideo(video) if (timeDelta < 0 && video.currentTime < 0) { @@ -293,7 +304,7 @@ function videoControl(e) { } const key = e.code let timeDelta = 0 - switch(key) { + switch (key) { case "ArrowLeft": timeDelta = BACK break diff --git a/src/sass/style.sass b/src/sass/style.sass index c510a8a0..d67548c7 100644 --- a/src/sass/style.sass +++ b/src/sass/style.sass @@ -544,12 +544,12 @@ section justify-content: space-between gap: 1em align-items: flex-start - // Complicated looking way of highlighting only one - // selected video view in a list of video views. - // Only apply .has-focus if there is at least one + // Only videos can be selected. If there is only + // one, there is no need to highlight the selection. + // Only apply .selected if there is at least one // .view-video element within .views that does - // not have the .has-focus class. - &:has(.view-video:not(.has-focus)) .has-focus + // not have the .selected class. + &:has(.view-video:not(.selected)) .selected box-shadow: 0 0 13px rgba(255,255,255,0.3) .view From ee2b23b02921ae808f5bb292591896c84b04f786 Mon Sep 17 00:00:00 2001 From: ugyballoons Date: Wed, 6 Nov 2024 14:02:42 +0000 Subject: [PATCH 08/10] Update propTypes --- src/js/components/MosaicView.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/js/components/MosaicView.js b/src/js/components/MosaicView.js index 6434c3a4..6ff05446 100644 --- a/src/js/components/MosaicView.js +++ b/src/js/components/MosaicView.js @@ -165,6 +165,8 @@ ChannelView.propTypes = { camera: cameraType, view: mosaicSingleView, currentMeta: metadataType, + selectView: PropTypes.func, + isSelectable: PropTypes.bool, } function ChannelMedia({ locationName, camera, event, mediaType }) { @@ -188,7 +190,10 @@ ChannelMedia.propTypes = { locationName: PropTypes.string, camera: cameraType, event: eventType, - mediaType: PropTypes.string, + mediaType: PropTypes.shape({ + IMAGE: PropTypes.string, + VIDEO: PropTypes.string, + }), } function ChannelImage({ mediaURL }) { From e74ca3b4003a2bf030856b61df3422aaf0707d01 Mon Sep 17 00:00:00 2001 From: ugyballoons Date: Wed, 6 Nov 2024 14:30:58 +0000 Subject: [PATCH 09/10] Rename ChannelView to ChannelViewListItem --- src/js/components/MosaicView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/components/MosaicView.js b/src/js/components/MosaicView.js index 6ff05446..a53c3c89 100644 --- a/src/js/components/MosaicView.js +++ b/src/js/components/MosaicView.js @@ -103,7 +103,7 @@ export default function MosaicView({ locationName, camera }) {
      {views.map((view) => { return ( - Date: Thu, 7 Nov 2024 13:07:19 +0000 Subject: [PATCH 10/10] Add MediaType class Also fix boundary logic --- src/js/components/MosaicView.js | 51 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/js/components/MosaicView.js b/src/js/components/MosaicView.js index a53c3c89..2b76103c 100644 --- a/src/js/components/MosaicView.js +++ b/src/js/components/MosaicView.js @@ -12,9 +12,20 @@ const FRAMELENGTH = 0.1 const BACK = -FRAMELENGTH const FORWARD = FRAMELENGTH -const MediaType = { - IMAGE: "image", - VIDEO: "video", +class MediaType { + static IMAGE = { value: "image" } + static VIDEO = { value: "video" } + + static getMediaType(type) { + switch (type) { + case "image": + return this.IMAGE + case "video": + return this.VIDEO + default: + return null + } + } } const commonColumns = ["seqNum"] @@ -27,10 +38,11 @@ export default function MosaicView({ locationName, camera }) { function initialViews() { let videoCount = 0 const views = camera.mosaic_view_meta.map((view) => { - const isVideo = view.mediaType === MediaType.VIDEO + const isVideo = view.mediaType === MediaType.VIDEO.value videoCount += isVideo ? 1 : 0 return { ...view, + mediaType: MediaType.getMediaType(view.mediaType), latestEvent: {}, // only apply 'selected == true' to first video selected: isVideo && videoCount == 1 ? true : false, @@ -109,7 +121,7 @@ export default function MosaicView({ locationName, camera }) { view={view} currentMeta={currentMeta} selectView={selectView} - isSelectable={hasMultipleVideos} + isSelectable={hasMultipleVideos()} key={view.channel} /> ) @@ -140,7 +152,11 @@ function ChannelViewListItem({ mediaType, latestEvent: { day_obs: dayObs }, } = view - const clsName = ["view", `view-${mediaType}`, selected ? "selected" : null] + const clsName = [ + "view", + `view-${mediaType.value}`, + selected ? "selected" : null, + ] .join(" ") .trimEnd() const clickHandler = isSelectable ? () => selectView(view) : null @@ -160,7 +176,7 @@ function ChannelViewListItem({ ) } -ChannelView.propTypes = { +ChannelViewListItem.propTypes = { locationName: PropTypes.string, camera: cameraType, view: mosaicSingleView, @@ -191,8 +207,7 @@ ChannelMedia.propTypes = { camera: cameraType, event: eventType, mediaType: PropTypes.shape({ - IMAGE: PropTypes.string, - VIDEO: PropTypes.string, + value: PropTypes.string, }), } @@ -212,7 +227,6 @@ ChannelImage.propTypes = { function ChannelVideo({ mediaURL }) { const [isLoaded, setIsLoaded] = useState(false) - const videoSrc = new URL(`event_video/${mediaURL}`, APP_DATA.baseUrl) const vidID = `v_${getStrHashCode(mediaURL)}` return ( @@ -288,22 +302,26 @@ const buildMediaURI = (locationName, cameraName, channelName, filename) => `${locationName}/${cameraName}/${channelName}/${filename}` function frameStep(vidID, timeDelta) { - console.log("frame delta is: ", timeDelta) const video = _getById(vidID) pauseVideo(video) - if (timeDelta < 0 && video.currentTime < 0) { + const currentTime = video.currentTime + const duration = video.duration + if (timeDelta < 0 && currentTime + timeDelta < 0) { video.currentTime = 0 - } else if (timeDelta > 0 && video.currentTime > video.duration) { + } else if ( + timeDelta > 0 && + currentTime + timeDelta >= duration - FRAMELENGTH + ) { video.currentTime = video.duration } else { - video.currentTime = video.currentTime + timeDelta + video.currentTime = currentTime + timeDelta } } window.onkeydown = videoControl function videoControl(e) { - const video = document.querySelector(".view-video.has-focus video") + const video = document.querySelector(".view-video.selected video") if (!video) { return } @@ -315,6 +333,7 @@ function videoControl(e) { break case "ArrowRight": timeDelta = FORWARD + break } if (timeDelta) { frameStep(video.id, timeDelta) @@ -322,7 +341,7 @@ function videoControl(e) { } function pauseVideo(video) { - if (!video.isPaused) { + if (!video.paused) { video.pause() } }