2019-09-20 15:46:05 -04:00
|
|
|
<template>
|
2021-11-04 23:29:27 -04:00
|
|
|
<div class="card h-100">
|
2021-11-04 23:28:21 -04:00
|
|
|
<h3 class="card-header" :data-color="color">
|
|
|
|
{{ name || 'Unknown' }}
|
|
|
|
</h3>
|
2022-01-04 21:25:10 -05:00
|
|
|
<video muted class="card-img webcam" controls autoplay ref="video"></video>
|
2021-11-03 16:01:24 -04:00
|
|
|
<div class="card-body" v-if="status">
|
2019-09-20 15:46:05 -04:00
|
|
|
<div>{{ status.state.text }}</div>
|
2021-11-04 23:50:56 -04:00
|
|
|
<div>
|
|
|
|
Job File Name:
|
|
|
|
<span class="font-monospace">{{ status.job.file.name || 'None' }}</span>
|
|
|
|
</div>
|
2020-09-25 18:55:58 -04:00
|
|
|
<div v-if="status.progress.completion">
|
2021-11-04 23:49:54 -04:00
|
|
|
<div class="d-flex">
|
2020-09-25 18:55:58 -04:00
|
|
|
Job Completion:
|
2021-11-04 23:49:54 -04:00
|
|
|
<div class="progress flex-fill ms-2" style="height: 2em">
|
|
|
|
<div
|
|
|
|
class="progress-bar"
|
|
|
|
:class="{
|
|
|
|
'progress-bar-striped': status.state.flags.printing,
|
|
|
|
'progress-bar-animated': status.state.flags.printing,
|
|
|
|
}"
|
|
|
|
role="progressbar"
|
|
|
|
:style="{ width: status.progress.completion + '%' }"
|
|
|
|
:aria-valuenow="status.progress.completion.toFixed(2)"
|
|
|
|
aria-valuemin="0"
|
|
|
|
aria-valuemax="100"
|
|
|
|
>
|
|
|
|
{{ status.progress.completion.toFixed(2) }}%
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-09-25 18:55:58 -04:00
|
|
|
</div>
|
|
|
|
<div>
|
2021-11-04 23:53:10 -04:00
|
|
|
Job Time: {{ formatDuration(status.progress.printTime) }} elapsed<span
|
|
|
|
v-show="status.progress.printTimeLeft"
|
|
|
|
>, {{ formatDuration(status.progress.printTimeLeft) }} left
|
|
|
|
</span>
|
2020-09-25 18:55:58 -04:00
|
|
|
</div>
|
2019-09-20 15:46:05 -04:00
|
|
|
</div>
|
|
|
|
<div>User: {{ status.job.user || '-' }}</div>
|
|
|
|
</div>
|
2021-11-03 16:01:24 -04:00
|
|
|
<div class="card-footer">Last updated {{ lastUpdateString }}</div>
|
2019-09-20 15:46:05 -04:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2021-10-11 21:28:12 -04:00
|
|
|
<script setup lang="ts">
|
2021-11-08 20:17:42 -05:00
|
|
|
import Hls from 'hls.js';
|
|
|
|
import { computed, onMounted, Ref, ref, watchEffect } from 'vue';
|
2020-09-25 18:55:58 -04:00
|
|
|
import prettyMilliseconds from 'pretty-ms';
|
2019-09-20 15:46:05 -04:00
|
|
|
|
2021-11-04 23:28:21 -04:00
|
|
|
import { CurrentOrHistoryPayload } from '../types/octoprint';
|
2021-11-18 19:45:51 -05:00
|
|
|
import { OctoprintColor } from '../types/messages';
|
2019-09-20 15:46:05 -04:00
|
|
|
|
2022-10-24 18:29:15 -04:00
|
|
|
export interface Props {
|
2021-10-13 23:25:28 -04:00
|
|
|
slug: string;
|
|
|
|
name?: string;
|
2021-11-03 16:01:24 -04:00
|
|
|
lastUpdate: Date;
|
|
|
|
now: Date;
|
2021-11-04 23:28:21 -04:00
|
|
|
status: CurrentOrHistoryPayload | null;
|
|
|
|
color: OctoprintColor | null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type PrinterInfo = Omit<Props, 'slug' | 'now'>;
|
|
|
|
|
|
|
|
const props = defineProps<Props>();
|
2021-11-08 20:17:42 -05:00
|
|
|
const video: Ref<HTMLMediaElement | null> = ref(null);
|
|
|
|
|
|
|
|
const hls: Ref<Hls | null> = ref(null);
|
|
|
|
|
|
|
|
if (Hls.isSupported()) {
|
|
|
|
hls.value = new Hls({
|
|
|
|
liveDurationInfinity: true,
|
|
|
|
backBufferLength: 30,
|
|
|
|
manifestLoadingTimeOut: 1000,
|
|
|
|
manifestLoadingMaxRetry: 30,
|
|
|
|
manifestLoadingRetryDelay: 500,
|
|
|
|
//debug: true,
|
|
|
|
});
|
|
|
|
hls.value.on(Hls.Events.MEDIA_ATTACHED, () => {
|
|
|
|
hls.value!.loadSource(`/webcam/${props.slug}.m3u8`);
|
|
|
|
hls.value!.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
|
|
|
|
video.value?.play();
|
|
|
|
console.log(
|
|
|
|
'manifest loaded, found ' + data.levels.length + ' quality level'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
hls.value.on(Hls.Events.ERROR, (event, data) => {
|
|
|
|
console.log(data);
|
|
|
|
});
|
|
|
|
}
|
2020-09-25 18:55:58 -04:00
|
|
|
|
2021-10-11 21:28:12 -04:00
|
|
|
function formatDuration(seconds: number): string {
|
|
|
|
return prettyMilliseconds(seconds * 1000);
|
2019-09-20 15:46:05 -04:00
|
|
|
}
|
2021-11-03 16:01:24 -04:00
|
|
|
|
|
|
|
const lastUpdateString = computed(() => {
|
|
|
|
if (props.status !== null) {
|
|
|
|
const elapsed = props.now.getTime() - props.lastUpdate.getTime();
|
|
|
|
if (elapsed < 10_000) {
|
|
|
|
return 'seconds ago';
|
|
|
|
} else if (elapsed < 60_000) {
|
|
|
|
return 'less than a minute ago';
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
prettyMilliseconds(elapsed, { compact: true, verbose: true }) + ' ago'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2021-11-04 23:28:21 -04:00
|
|
|
|
2021-11-08 20:17:42 -05:00
|
|
|
watchEffect(() => {
|
|
|
|
console.log(video.value, hls.value);
|
|
|
|
if (hls.value && video.value) {
|
|
|
|
// if hls and video element are valid, bind them together
|
|
|
|
hls.value.attachMedia(video.value);
|
|
|
|
console.log('video and hls.js are now bound together !');
|
|
|
|
}
|
|
|
|
});
|
2019-09-20 15:46:05 -04:00
|
|
|
</script>
|
2021-11-04 23:28:21 -04:00
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
$bs-colors: ('red', 'orange', 'yellow', 'green', 'blue', 'white');
|
|
|
|
|
|
|
|
.card-header[data-color] {
|
|
|
|
background-image: var(--bs-gradient);
|
|
|
|
|
|
|
|
@each $color in $bs-colors {
|
|
|
|
&[data-color='#{$color}'] {
|
|
|
|
background-color: var(--bs-#{$color});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&[data-color='violet'] {
|
|
|
|
background-color: var(--bs-purple);
|
|
|
|
}
|
|
|
|
|
|
|
|
&[data-color='black'] {
|
|
|
|
background-color: var(--bs-purple);
|
|
|
|
}
|
|
|
|
|
|
|
|
&[data-color='blue'],
|
|
|
|
&[data-color='green'],
|
|
|
|
&[data-color='violet'],
|
|
|
|
&[data-color='black'] {
|
|
|
|
color: var(--bs-light);
|
|
|
|
}
|
|
|
|
}
|
2021-11-18 19:45:51 -05:00
|
|
|
|
|
|
|
.webcam {
|
|
|
|
background-color: black;
|
|
|
|
}
|
2021-11-04 23:28:21 -04:00
|
|
|
</style>
|