Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

video: Add button with files count badge to access stored videos modal #827

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 46 additions & 2 deletions src/components/mini-widgets/MiniVideoRecorder.vue
@@ -1,7 +1,8 @@
<template>
<div
ref="recorderWidget"
class="flex justify-around px-2 py-1 text-center rounded-lg h-9 w-28 align-center bg-slate-800/60"
class="flex justify-around px-2 py-1 text-center rounded-lg h-9 align-center bg-slate-800/60"
:class="{ 'w-48': numberOfVideosOnDB > 0, 'w-32': numberOfVideosOnDB <= 0 }"
>
<div
v-if="!isProcessingVideo"
Expand Down Expand Up @@ -31,6 +32,14 @@
<div v-else-if="isProcessingVideo" class="w-16 text-justify text-slate-100">
<div class="text-center text-xs text-white select-none flex-nowrap">Processing video...</div>
</div>
<div v-if="numberOfVideosOnDB > 0" class="flex justify-center w-8">
<v-divider vertical class="h-6" />
<v-badge color="info" :content="numberOfVideosOnDB" :dot="isOutside || isVideoLibraryDialogOpen"
><v-icon class="w-6 h-6 text-slate-100 ml-3" @click="isVideoLibraryDialogOpen = true">
mdi-video-box
</v-icon></v-badge
>
</div>
</div>
<v-dialog v-model="isStreamSelectDialogOpen" width="auto">
<div class="p-6 m-5 bg-white rounded-md">
Expand Down Expand Up @@ -67,19 +76,23 @@
</div>
</div>
</v-dialog>
<v-dialog v-model="isVideoLibraryDialogOpen" width="auto">
<ConfigurationVideoView as-video-library />
</v-dialog>
</template>

<script setup lang="ts">
import { useMouseInElement, useTimestamp } from '@vueuse/core'
import { intervalToDuration } from 'date-fns'
import { storeToRefs } from 'pinia'
import Swal from 'sweetalert2'
import { computed, onBeforeMount, onBeforeUnmount, ref, toRefs, watch } from 'vue'
import { computed, onBeforeMount, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'

import { isEqual } from '@/libs/utils'
import { useVideoStore } from '@/stores/video'
import { useWidgetManagerStore } from '@/stores/widgetManager'
import type { MiniWidget } from '@/types/miniWidgets'
import ConfigurationVideoView from '@/views/ConfigurationVideoView.vue'

const widgetStore = useWidgetManagerStore()
const videoStore = useVideoStore()
Expand All @@ -97,10 +110,16 @@ const { namesAvailableStreams } = storeToRefs(videoStore)
const recorderWidget = ref()
const { isOutside } = useMouseInElement(recorderWidget)
const isStreamSelectDialogOpen = ref(false)
const isVideoLibraryDialogOpen = ref(false)
const isLoadingStream = ref(false)
const timeNow = useTimestamp({ interval: 100 })
const mediaStream = ref<MediaStream | undefined>()
const isProcessingVideo = ref(false)
const numberOfVideosOnDB = ref(0)

onMounted(async () => {
await fetchNumebrOfTempVideos()
})

onBeforeMount(async () => {
// Set initial widget options if they don't exist
Expand All @@ -117,6 +136,12 @@ watch(nameSelectedStream, () => {
mediaStream.value = undefined
})

// Fetch temporary video data from the storage
const fetchNumebrOfTempVideos = async (): Promise<void> => {
const size = await videoStore.videoStoringDB.length()
numberOfVideosOnDB.value = size
}

// eslint-disable-next-line jsdoc/require-jsdoc
function assertStreamIsSelectedAndAvailable(
selectedStream: undefined | string
Expand Down Expand Up @@ -232,6 +257,16 @@ watch(
() => videoStore.areThereVideosProcessing,
(newValue) => {
isProcessingVideo.value = newValue
fetchNumebrOfTempVideos()
}
)

watch(
() => isVideoLibraryDialogOpen.value,
async (newValue) => {
if (newValue === false) {
await fetchNumebrOfTempVideos()
}
}
)

Expand Down Expand Up @@ -295,4 +330,13 @@ watch(isRecording, () => {
background-color: #475569;
box-shadow: 0px 0px 3px 3px #475569;
}

.close-icon {
position: absolute;
top: 5px;
right: 5px;
cursor: pointer;
font-size: 20px;
color: #999;
}
</style>
63 changes: 42 additions & 21 deletions src/views/ConfigurationVideoView.vue
@@ -1,8 +1,9 @@
<template>
<BaseConfigurationView>
<template #title>Video configuration</template>
<template #title>{{ isVideoLibraryOnly ? 'Video Storage' : 'Video configuration' }}</template>
<template #content>
<div
v-if="!isVideoLibraryOnly"
class="flex flex-col items-center px-5 py-3 m-5 font-medium text-center border rounded-md text-grey-darken-1 bg-grey-lighten-5 w-[40%]"
>
<p class="font-bold">
Expand All @@ -18,7 +19,7 @@
</p>
</div>

<div class="flex w-[30rem] flex-wrap">
<div v-if="!isVideoLibraryOnly" class="flex w-[30rem] flex-wrap">
<v-combobox
v-model="allowedIceIps"
multiple
Expand Down Expand Up @@ -60,7 +61,11 @@
</p>
</div>

<div v-if="availableVideosAndLogs?.isEmpty()" class="max-w-[50%] bg-slate-100 rounded-md p-6 border">
<div
v-if="availableVideosAndLogs?.isEmpty()"
:class="{ 'mb-4': isVideoLibraryOnly, 'mb-0': !isVideoLibraryOnly }"
class="max-w-[50%] bg-slate-100 rounded-md p-6 border"
>
<p class="mb-4 text-2xl font-semibold text-center text-slate-500">No videos available.</p>
<p class="text-center text-slate-400">
Use the MiniVideoRecorder widget to record some videos and them come back here to download or discard those.
Expand All @@ -78,7 +83,8 @@
show-select
loading-text="Loading... Please wait"
:loading="availableVideosAndLogs === undefined"
class="max-w-[90%] bg-slate-100/30 rounded-lg p-6 border"
class="max-w-[90%] bg-slate-100/30 rounded-lg p-3 border"
:class="temporaryDbSize === 0 ? 'mb-10' : 'mb-0'"
>
<template #item.size="{ value }">
{{ formatBytes(value) }}
Expand Down Expand Up @@ -110,22 +116,23 @@
</Transition>
</template>
</v-data-table>

<div
v-if="temporaryDbSize > 0"
v-tooltip.bottom="'Remove video files used during the recording. This will not affect already saved videos.'"
class="flex flex-col items-center justify-center p-4 m-4 transition-all rounded-md cursor-pointer bg-slate-600 text-slate-50 hover:bg-slate-500/80"
@click="clearTemporaryVideoFiles()"
>
<span class="text-lg font-medium">Clear temporary video storage</span>
<span class="text-sm text-slate-300/90">Current size: {{ formatBytes(temporaryDbSize) }}</span>
</div>
<div
v-if="temporaryDbSize > 0"
class="flex flex-col items-center justify-center p-4 m-4 transition-all rounded-md cursor-pointer bg-slate-600 text-slate-50 hover:bg-slate-500/80"
@click="videoStore.downloadTempVideoDB()"
>
<span class="text-lg font-medium">Download temporary video chunks</span>
<div class="flex flex-row">
<div
v-if="temporaryDbSize > 0"
v-tooltip.bottom="'Remove video files used during the recording. This will not affect already saved videos.'"
class="flex flex-col items-center justify-center px-4 py-2 mx-4 mb-6 mt-8 transition-all rounded-md cursor-pointer bg-slate-600 text-slate-50 hover:bg-slate-500/80"
@click="clearTemporaryVideoFiles()"
>
<span class="text-md font-medium">Clear temporary video storage</span>
<span class="text-sm text-slate-300/90">Current size: {{ formatBytes(temporaryDbSize) }}</span>
</div>
<div
v-if="temporaryDbSize > 0"
class="flex flex-col items-center justify-center px-4 py-2 mx-4 mb-6 mt-8 transition-all rounded-md cursor-pointer bg-slate-600 text-slate-50 hover:bg-slate-500/80"
@click="videoStore.downloadTempVideoDB()"
>
<span class="text-md font-medium">Download temporary video chunks</span>
</div>
</div>
</template>
</BaseConfigurationView>
Expand All @@ -134,7 +141,7 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import Swal from 'sweetalert2'
import { computed, onMounted, ref, watch } from 'vue'
import { computed, onMounted, ref, watch, watchEffect } from 'vue'
import type { VDataTable } from 'vuetify/components'

import Button from '@/components/Button.vue'
Expand All @@ -146,6 +153,20 @@ import BaseConfigurationView from './BaseConfigurationView.vue'
const videoStore = useVideoStore()
const { allowedIceIps, availableIceIps } = storeToRefs(videoStore)

// Define dialog as video library only
const props = defineProps<{
/**
*
*/
asVideoLibrary?: boolean
}>()

const isVideoLibraryOnly = ref(props.asVideoLibrary)

watchEffect(() => {
isVideoLibraryOnly.value = props.asVideoLibrary ?? false
})

// List available videos and telemetry logs to be downloaded
/* eslint-disable jsdoc/require-jsdoc */
interface VideoStorageFile {
Expand Down