Files
battle-caster-v1/src/components/ScreenManager/RemoteScreen.vue
2024-11-24 17:34:41 +00:00

206 lines
5.3 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div
class="remote-screen"
:class="{ 'remote-screen--dragover': isDragOver }"
:style="{ aspectRatio: `${imageSink?.resolution?.width ?? 1}/${imageSink?.resolution?.height ?? 1}` }"
@drop="onDrop"
@dragover.prevent
@dragleave="isDragOver = false"
@dragenter="isDragOver = true"
@wheel.prevent="onWheel"
@mousedown="onMouseDown"
>
<div class="remote-screen__info">
{{ screen.name }} ({{ imageSink?.resolution?.width ?? '?' }}x{{ imageSink?.resolution?.height ?? '?' }})
<span style="position:absolute;right:0.25rem;"></span>
</div>
<div class="remote-screen__loading" v-if="isLoading"><span></span></div>
<div class="remote-screen__connect" v-if="!screen.isConnected">
<button @click="screen.connect()">Connect</button>
</div>
<canvas
ref="canvas"
style="max-width: 100%;"
:width="imageSink?.resolution?.width / 4"
:height="imageSink?.resolution?.height / 4"
:style="{
// transform: `scale(${scale / 100}) translate(${(x)}%, ${(y)}%)`,
}"
/>
</div>
</template>
<script setup lang="ts">
import {computed, inject, ref, watch} from 'vue';
import {ImageSink, RemoteDevice} from '../../model/Remote/types';
import {MountedFsSymbol} from '../../model/injectionSymbols';
import {VirtualFile} from '../../model/FileSystem/types';
import {useLoading} from '../../helpers/withLoading';
import {useMouseDragging} from '../../helpers/mouseDragging';
import {MapLayerDrawable} from '../Map/object';
const props = defineProps<{
screen: RemoteDevice;
}>();
const imageSink = computed(() => props.screen.imageSink as ImageSink);
const fileSystem = inject(MountedFsSymbol)!;
const canvas = ref<HTMLCanvasElement>();
const isDragOver = ref(false);
const scale = ref(100);
const {isLoading, withLoading} = useLoading();
const {onMouseDown, x, y} = useMouseDragging()
function onWheel(event: WheelEvent) {
scale.value = Math.max(10, Math.min(1000, scale.value + event.deltaY / 100));
console.log('scroll', event.deltaY, scale.value);
}
watch(() => [x.value, y.value, scale.value], () => {
imageSink.value?.transform?.({
x: x.value,
y: y.value,
scale: scale.value / 100,
});
})
watch(() => imageSink.value?.display, ()=> {
if (imageSink.value?.display) {
imageSink.value?.display.connectTarget(canvas.value!);
}
});
async function drawImage(blob: Blob) {
if (blob.type.startsWith('image/')) {
const url = URL.createObjectURL(blob);
const img = new Image();
img.onload = () => {
const ctx = canvas.value!.getContext('2d')!;
canvas.value!.width = img.width;
canvas.value!.height = img.height;
ctx.drawImage(img, 0, 0);
}
img.src = url;
} else if (blob.type.startsWith('video/')) {
const url = URL.createObjectURL(blob);
const video = document.createElement('video');
video.muted = true;
video.autoplay = true;
video.src = url;
video.oncanplaythrough = () => {
const ctx = canvas.value!.getContext('2d')!;
canvas.value!.width = video.videoWidth;
canvas.value!.height = video.videoHeight;
ctx.drawImage(video, 0, 0);
video.pause();
}
}
}
async function loadFile(meta: VirtualFile, file: File) {
if (imageSink.value?.display) {
const map = new MapLayerDrawable(file, meta.map?.width);
await map.preloadFile();
imageSink.value.display.mapLayer = map
console.log(imageSink.value?.display);
}
// await withLoading(async () => {
// await Promise.all([
// imageSink.value?.transferFile(meta, file),
// drawImage(file),
// ])
// await imageSink.value?.show(meta);
// })
}
async function onDrop(event: DragEvent) {
event.preventDefault();
isDragOver.value = false;
const file = event.dataTransfer?.files?.[0];
if (file) {
const meta = {
type: 'file',
source: 'drop',
path: [] as string[],
name: file.name,
mimeType: file.type
} as const;
await loadFile(meta, file)
} else if (event.dataTransfer?.getData('application/json')) {
const meta = JSON.parse(event.dataTransfer?.getData('application/json'));
if (meta.type === 'file') {
const file = await fileSystem.download(meta);
await loadFile(meta, file);
}
}
console.log('files', event.dataTransfer?.files);
console.log('drop', event.dataTransfer?.getData('application/json'));
}
</script>
<style>
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.remote-screen {
position: relative;
display: inline-block;
border: 2px solid white;
border-radius: 4px;
background-color: black;
aspect-ratio: 16 / 9;
overflow: hidden;
max-height: 100%;
}
.remote-screen__info {
position: absolute;
top: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
padding: 4px;
text-align: center;
}
.remote-screen__loading {
pointer-events: none;
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
color: white;
background-color: rgba(0, 0, 0, 0.5);
span {
transform-origin: 44% 54%;
animation: spin 1000ms linear infinite;
}
}
.remote-screen__connect {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
padding: 4px;
text-align: center;
}
.remote-screen--dragover {
border-color: orange;
}
</style>