sounds, better files
This commit is contained in:
192
src/components/ScreenManager/RemoteScreen.vue
Normal file
192
src/components/ScreenManager/RemoteScreen.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<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%;"
|
||||
: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';
|
||||
|
||||
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,
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
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: Blob) {
|
||||
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>
|
||||
Reference in New Issue
Block a user