cleanup
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@ dist-ssr
|
||||
*.local
|
||||
maps
|
||||
public/maps/*
|
||||
public/soundfx/*
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
||||
@@ -41,14 +41,14 @@
|
||||
<div class="a-card__popover" popover ref="popover">
|
||||
<DeviceVolume
|
||||
class="a-card__popover-volumes"
|
||||
:deviceSource="(device) => node.getDeviceSource(device).gainNode"
|
||||
:deviceSource="(device) => node.getDeviceSource(device as any).gainNode"
|
||||
/>
|
||||
<slot name="popover"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {mergeProps, ref} from 'vue';
|
||||
import {ref, UnwrapNestedRefs} from 'vue';
|
||||
import {SoundGroupNode} from '../../model/Audio/SoundGroupNode';
|
||||
|
||||
import Play from '../Icons/Play.vue';
|
||||
@@ -71,7 +71,7 @@ const emits = defineEmits<{
|
||||
type Supports = 'next' | 'prev' | 'togglePlay' | 'stop' | 'play';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
node: SoundGroupNode<any>
|
||||
node: UnwrapNestedRefs<SoundGroupNode<any>>
|
||||
supports?: Supports[]
|
||||
label?: string,
|
||||
noDetail?: boolean
|
||||
|
||||
@@ -31,7 +31,7 @@ const props = withDefaults(defineProps<{
|
||||
const devicesWithSink = computed(() => {
|
||||
return deviceManager.audioDevices.map((device) => ({
|
||||
device,
|
||||
audioSink: props.deviceSource(device)
|
||||
audioSink: props.deviceSource(device as any)
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -49,9 +49,10 @@ import AudioCard from './AudioCard.vue';
|
||||
import {PlaylistGroupNode} from '../../model/Audio/PlaylistGroupNode';
|
||||
import Play from '../Icons/Play.vue';
|
||||
import {SoundNode} from '../../model/Audio/SoundNode';
|
||||
import {UnwrapNestedRefs} from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
node: PlaylistGroupNode
|
||||
node: UnwrapNestedRefs<PlaylistGroupNode>
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</AudioCard>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {computed} from 'vue';
|
||||
import {computed, UnwrapNestedRefs} from 'vue';
|
||||
import {SfxGroupNode} from '../../model/Audio/SfxGroupNode';
|
||||
import AudioCard from './AudioCard.vue';
|
||||
import {groupBy} from '../../utils/groupBy';
|
||||
@@ -36,7 +36,7 @@ import SfxSound from './SfxSound.vue';
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
node: SfxGroupNode,
|
||||
node: UnwrapNestedRefs<SfxGroupNode>,
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -39,20 +39,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {inject, reactive, ref, watch} from 'vue';
|
||||
import {inject, reactive, watch} from 'vue';
|
||||
import Playlist from './Playlist.vue';
|
||||
import {PlaylistGroupNode} from '../../model/Audio/PlaylistGroupNode';
|
||||
import {AudioContextSymbol, MountedFsSymbol, RemoteDeviceManagerSymbol} from '../../model/injectionSymbols';
|
||||
import {SfxGroupNode} from '../../model/Audio/SfxGroupNode';
|
||||
import Sfx from './Sfx.vue';
|
||||
import Modal from './modal.vue';
|
||||
import {TabSourceNode} from '../../model/Audio/TabSourceNode';
|
||||
import TabSource from './TabSource.vue';
|
||||
import DeviceVolume from './DeviceVolume.vue';
|
||||
import {AudioNodeManager} from '../../model/Audio/AudioNodesManager';
|
||||
import {debounce} from '../../utils/debounce';
|
||||
import IconPlus from '../Icons/IconPlus.vue';
|
||||
import {SoundNode} from '../../model/Audio/SoundNode';
|
||||
|
||||
const fs = inject(MountedFsSymbol)!;
|
||||
const audioContext = inject(AudioContextSymbol) as AudioContext;
|
||||
@@ -94,15 +91,16 @@ function addNode(name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const deleteNode = (node: SoundNode) => {
|
||||
const deleteNode = (node: any) => {
|
||||
node.stop?.();
|
||||
node.disconnect();
|
||||
// @ts-ignore
|
||||
nodes.splice(nodes.indexOf(node), 1);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
watch(() => nodes, debounce((newNodes) => {
|
||||
console.log('NEW_NODES', newNodes);
|
||||
// @ts-ignore
|
||||
audioNodesManager.store(newNodes);
|
||||
}, 500), {
|
||||
deep: true,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {UnwrapNestedRefs} from 'vue';
|
||||
import {TabSourceNode} from '../../model/Audio/TabSourceNode';
|
||||
import AudioCard from './AudioCard.vue';
|
||||
|
||||
@@ -20,7 +21,7 @@ const emit = defineEmits<{
|
||||
'delete': [],
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
node: TabSourceNode,
|
||||
defineProps<{
|
||||
node: UnwrapNestedRefs<TabSourceNode>,
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -29,7 +29,7 @@ function onCancel(event: Event) {
|
||||
}
|
||||
}
|
||||
|
||||
function onClose(event: Event) {
|
||||
function onClose() {
|
||||
emit('update:open', false);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ function switchMain(section: string){
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
//@ts-ignore
|
||||
window.addEventListener('reveal-screen', (event: CustomEvent) => {
|
||||
switchMain('map');
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
name: string
|
||||
}>()
|
||||
|
||||
|
||||
@@ -18,21 +18,19 @@ const emit = defineEmits<{
|
||||
dropFile: [VirtualFile],
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
accept?: string,
|
||||
}>();
|
||||
|
||||
const slot = defineSlots<{
|
||||
default(props: {isDragOver: boolean}): any
|
||||
}>()
|
||||
// const slot = defineSlots<{
|
||||
// default(props: {isDragOver: boolean}): any
|
||||
// }>()
|
||||
|
||||
function onDragEnter(event: DragEvent) {
|
||||
console.log('drag enter');
|
||||
function onDragEnter() {
|
||||
isDragOver.value = true;
|
||||
}
|
||||
|
||||
function onDragLeave(event: DragEvent) {
|
||||
console.log('drag leave');
|
||||
function onDragLeave() {
|
||||
isDragOver.value = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,10 @@ watch(()=> props.file, async (file)=>{
|
||||
currentPreviewUrl.value = null;
|
||||
try {
|
||||
console.log('downloading thumbnail');
|
||||
// @ts-ignore
|
||||
const blob = await fs.downloadThumbnail(file, {abortSignal: abort.signal});
|
||||
currentPreviewUrl.value = URL.createObjectURL(blob);
|
||||
// @ts-ignore
|
||||
currentPrevewType.value = file.mimeType;
|
||||
console.log('currentPreviewUrl', currentPreviewUrl.value);
|
||||
abort = undefined;
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
<script setup lang="ts">
|
||||
import {onUnmounted, ref} from 'vue';
|
||||
import FileManager from '../FileManager/FileManager.vue';
|
||||
import FilePreview from '../FileManager/FilePreview.vue';
|
||||
import {VirtualFile} from '../../model/FileSystem/types';
|
||||
|
||||
const popover = ref<HTMLDivElement>()
|
||||
@@ -49,7 +48,7 @@ function onDragStart(){
|
||||
dragging.value = true;
|
||||
}
|
||||
|
||||
function onDragLeave(event) {
|
||||
function onDragLeave(event: any) {
|
||||
if (!content.value?.contains(event.relatedTarget)) {
|
||||
popover.value?.hidePopover();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<Dropzone
|
||||
v-slot="{isDragOver}"
|
||||
class="map-control"
|
||||
:class="{
|
||||
'map-control--over': isDragOver
|
||||
}"
|
||||
@dropFile="onDrop"
|
||||
@mousewheel="onScroll"
|
||||
@mousedown="onMouseDown"
|
||||
@@ -44,22 +40,15 @@
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {computed, inject, onBeforeUnmount, onMounted, reactive, ref} from 'vue';
|
||||
import {inject, onBeforeUnmount, onMounted, ref} from 'vue';
|
||||
import Dropzone from '../Dropzone/Dropzone.vue';
|
||||
import MapRenderer from './MapRenderer.vue';
|
||||
import {MountedFsSymbol, RemoteDeviceManagerSymbol} from '../../model/injectionSymbols';
|
||||
import {closerToZero, MapDrawable, MapImage} from './helpers';
|
||||
import {MountedFsSymbol} from '../../model/injectionSymbols';
|
||||
import {closerToZero} from './helpers';
|
||||
import {VirtualFile} from '../../model/FileSystem/types';
|
||||
import {CanvasDisplay, MapLayerDrawable} from './object';
|
||||
import {Devices} from '../../model/Remote/RemoteDeviceManager';
|
||||
|
||||
const fs = inject(MountedFsSymbol)!;
|
||||
const remoteManager = inject(RemoteDeviceManagerSymbol)!;
|
||||
|
||||
const screens = computed(() => {
|
||||
return remoteManager.devices
|
||||
.filter(device => device.hasImageSink)
|
||||
})
|
||||
|
||||
const canvas = ref<HTMLCanvasElement>();
|
||||
const selectedScreen = ref<Devices>();
|
||||
@@ -131,7 +120,7 @@ function onMouseDown(event: MouseEvent) {
|
||||
window.addEventListener('mouseup', onMouseUp);
|
||||
}
|
||||
|
||||
function onMouseUp(event: MouseEvent) {
|
||||
function onMouseUp() {
|
||||
dragging.value = false;
|
||||
|
||||
window.removeEventListener('mouseup', onMouseUp);
|
||||
@@ -148,7 +137,7 @@ function onMouseMove(event: MouseEvent) {
|
||||
}
|
||||
|
||||
if (event.buttons === 0) {
|
||||
onMouseUp(event);
|
||||
onMouseUp();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -193,6 +182,7 @@ function onKeyup(event: KeyboardEvent) {
|
||||
onMounted(() => {
|
||||
onLoad();
|
||||
canvasDisplay?.connectTarget(canvas.value!);
|
||||
// @ts-ignore
|
||||
window.addEventListener('reveal-screen', (event: CustomEvent) => {
|
||||
selectScreen(event.detail);
|
||||
});
|
||||
@@ -219,7 +209,6 @@ async function onLoad(file?: VirtualFile | File) {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
console.log('onLoad', file, meta?.map?.width);
|
||||
// @ts-ignore
|
||||
const layer = new MapLayerDrawable(file, meta?.map?.width);
|
||||
await layer.preloadFile();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, computed } from 'vue';
|
||||
import {MapContext, MapDrawable} from './helpers';
|
||||
import {MapDrawable} from './helpers';
|
||||
|
||||
const map = ref<HTMLCanvasElement>();
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<button v-else @click.prevent="device.disconnect()">Disconnect</button>
|
||||
</td>
|
||||
<td>
|
||||
<button @click.prevent="manager.removeDevice(device)">Delete</button>
|
||||
<button @click.prevent="manager.removeDevice(device as any)">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -49,22 +49,15 @@ const canvas = ref<HTMLCanvasElement>();
|
||||
const isDragOver = ref(false);
|
||||
const scale = ref(100);
|
||||
|
||||
const {isLoading, withLoading} = useLoading();
|
||||
const {isLoading} = useLoading();
|
||||
|
||||
const {onMouseDown, x, y} = useMouseDragging()
|
||||
const {onMouseDown} = 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) {
|
||||
@@ -72,48 +65,14 @@ watch(() => imageSink.value?.display, ()=> {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
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);
|
||||
// @ts-ignore
|
||||
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) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="screens">
|
||||
<RemoteScreen :screen="screen" v-for="screen of screens" @click="revealScreen(screen)" />
|
||||
<RemoteScreen :screen="(screen as any)" v-for="screen of screens" @click="revealScreen(screen as any)" />
|
||||
<div>
|
||||
<IconButton name="Chromecast" @click="remoteManager.connectChromeCast()">
|
||||
<IconChromecast />
|
||||
@@ -12,7 +12,6 @@
|
||||
import {computed, inject} from 'vue';
|
||||
import {RemoteDeviceManagerSymbol} from '../../model/injectionSymbols';
|
||||
import RemoteScreen from './RemoteScreen.vue';
|
||||
import {ImageSink} from '../../model/Remote/types';
|
||||
import IconButton from '../Common/IconButton.vue';
|
||||
import IconChromecast from '../Icons/IconChromecast.vue';
|
||||
import {Devices} from '../../model/Remote/RemoteDeviceManager';
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{{ tab.label }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="slot, name in slots" v-show="selectedTab === name">
|
||||
<div v-for="_, name in slots" v-show="selectedTab === name">
|
||||
<slot :name="name"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ export const useMouseDragging = () => {
|
||||
const y = ref(0);
|
||||
const initialPosition = { x: 0, y: 0 };
|
||||
|
||||
function onMouseDown(event) {
|
||||
function onMouseDown(event: any) {
|
||||
dragging.value = true;
|
||||
initialPosition.x = event.clientX - x.value;
|
||||
initialPosition.y = event.clientY - y.value;
|
||||
@@ -16,7 +16,7 @@ export const useMouseDragging = () => {
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
function onMouseMove(event) {
|
||||
function onMouseMove(event: any) {
|
||||
if (dragging.value) {
|
||||
x.value = event.clientX - initialPosition.x;
|
||||
y.value = event.clientY - initialPosition.y;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { AudioContextSymbol, MountedFsSymbol, RemoteDeviceManagerSymbol } from '
|
||||
import { MountedFs } from './model/FileSystem/MountedFs'
|
||||
import { RemoteDeviceManager } from './model/Remote/RemoteDeviceManager'
|
||||
import { ChromecastChannelCaster } from './model/Remote/Channels/ChromcastCaster'
|
||||
import { WsSignalingCaster } from './model/Remote/Channels/WsSignaling'
|
||||
|
||||
|
||||
const app = createApp(ControlApp);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PlaylistGroupNode, PlaylistGroupNodeOptions } from "./PlaylistGroupNode";
|
||||
import { SfxGroupNode, SfxGroupNodeOptions } from "./SfxGroupNode";
|
||||
import { PlaylistGroupNode } from "./PlaylistGroupNode";
|
||||
import { SfxGroupNode } from "./SfxGroupNode";
|
||||
import { SoundGroupNode } from "./SoundGroupNode";
|
||||
import { TabSourceNode, TabSourceNodeOptions } from "./TabSourceNode";
|
||||
import { TabSourceNode } from "./TabSourceNode";
|
||||
import { SoundboardContext } from "./types";
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { VirtualFile } from "../FileSystem/types";
|
||||
import { RemoteDevice } from "../Remote/types";
|
||||
import { SoundGroupNode, SoundGroupNodeOptions } from "./SoundGroupNode";
|
||||
import { MediaElementSoundNode } from "./SoundNode";
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { VirtualFile } from "../FileSystem/types";
|
||||
import { SoundGroupNode, SoundGroupNodeOptions } from "./SoundGroupNode";
|
||||
import { BufferedSoundNode } from "./SoundNode";
|
||||
|
||||
export interface SfxGroupNodeOptions extends SoundGroupNodeOptions {
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export abstract class SoundGroupNode<T extends SoundGroupNodeOptions> {
|
||||
this.addSoundNode(sound);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
addSound(vFile: VirtualFile) {
|
||||
// implement in subclass
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MountedFs } from "../FileSystem/MountedFs";
|
||||
import { FileSystemSource, VirtualFile } from "../FileSystem/types";
|
||||
import { VirtualFile } from "../FileSystem/types";
|
||||
import { SoundboardContext } from "./types";
|
||||
|
||||
|
||||
@@ -145,10 +145,6 @@ export class MediaElementSoundNode extends SoundNode {
|
||||
return this._isPlaying;
|
||||
}
|
||||
|
||||
get currentTime() {
|
||||
return this.mediaElement!.currentTime;
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this.mediaElement!.duration;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export class TabSourceNode extends SoundGroupNode<TabSourceNodeOptions> {
|
||||
frameRate: {ideal: 1}
|
||||
},
|
||||
audio: {
|
||||
// @ts-ignore
|
||||
suppressLocalAudioPlayback: false,
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MountedFs } from "../FileSystem/MountedFs";
|
||||
import { FileSystemSource } from "../FileSystem/types";
|
||||
|
||||
export type SoundboardContext = {
|
||||
audioContext: AudioContext;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { VirtualFile } from "../FileSystem/types";
|
||||
|
||||
export function extensionMimeType(fileName: string) {
|
||||
if (fileName.endsWith('.jpg') || fileName.endsWith('.jpeg') || fileName.endsWith('.jpe')) {
|
||||
|
||||
@@ -12,6 +12,7 @@ function extractSize(file: VirtualFile): MapMeta {
|
||||
const match = [...file.path, file.name].join('/').match(/(\d+)x(\d+)/);
|
||||
if (match) {
|
||||
return {
|
||||
valid: true,
|
||||
width: parseInt(match[1]),
|
||||
height: parseInt(match[2]),
|
||||
}
|
||||
@@ -22,6 +23,7 @@ function extractSize(file: VirtualFile): MapMeta {
|
||||
const match = [...file.path, file.name].join('/').match(/(\d+)w/);
|
||||
if (match) {
|
||||
return {
|
||||
valid: true,
|
||||
width: parseInt(match[1]),
|
||||
height: undefined,
|
||||
}
|
||||
@@ -29,6 +31,7 @@ function extractSize(file: VirtualFile): MapMeta {
|
||||
}
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CancellationToken } from "typescript";
|
||||
import { extensionMimeType } from "../FileMeta/ExtensionMimeType";
|
||||
import { CachePolicy, FsCache } from "./FsCache";
|
||||
import { VirtualDirectory, VirtualFile, FileSystemSource, ListDirOptions } from "./types";
|
||||
@@ -65,7 +64,7 @@ export class FileStashFs implements FileSystemSource<FileStashFsConfig> {
|
||||
if (data.status !== 'ok') {
|
||||
throw new Error(`Failed to list directory ${dir.path.join('/')}`);
|
||||
}
|
||||
return data.results.map((result) => {
|
||||
const result = data.results.map((result) => {
|
||||
if (result.type === 'file') {
|
||||
return <VirtualFile>{
|
||||
type: 'file',
|
||||
@@ -75,7 +74,7 @@ export class FileStashFs implements FileSystemSource<FileStashFsConfig> {
|
||||
path: [...dir.path, result.name],
|
||||
}
|
||||
} else if (result.type === 'directory') {
|
||||
return {
|
||||
return <VirtualDirectory>{
|
||||
type: 'directory',
|
||||
source: this.options.name,
|
||||
name: result.name,
|
||||
@@ -84,7 +83,9 @@ export class FileStashFs implements FileSystemSource<FileStashFsConfig> {
|
||||
} else {
|
||||
throw new Error(`Unknown type ${result.type}`);
|
||||
}
|
||||
}).sort((a, b) => {
|
||||
})
|
||||
|
||||
result.sort((a, b) => {
|
||||
if (a.type === 'file' && b.type === 'directory') {
|
||||
return 1;
|
||||
} else if (a.type === 'directory' && b.type === 'file') {
|
||||
@@ -96,6 +97,8 @@ export class FileStashFs implements FileSystemSource<FileStashFsConfig> {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async download(file: VirtualFile, {abortSignal}: {abortSignal?: AbortSignal} = {}): Promise<File> {
|
||||
@@ -122,6 +125,8 @@ export class FileStashFs implements FileSystemSource<FileStashFsConfig> {
|
||||
} catch (err) {
|
||||
if (err !== 'User canceled') {
|
||||
return this.download(file, {abortSignal});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { CachePolicy, NoCacheEntryError } from "./FsCache";
|
||||
|
||||
type Config<T> = T extends FileSystemSource<any> ? ReturnType<T['getConfig']> : never;
|
||||
|
||||
const CACHE = Symbol('Cache');
|
||||
export interface Fs {
|
||||
get root(): VirtualDirectory[];
|
||||
download(file: VirtualFile): Promise<File>;
|
||||
@@ -104,35 +103,3 @@ export class MountedFs implements Fs {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// export class SearchableFilesystem implements Fs {
|
||||
// public allFiles: VirtualFile[] = [];
|
||||
|
||||
// constructor(private fs: MountedFs) {}
|
||||
|
||||
// async precacheDir(dir: VirtualDirectory) {
|
||||
// for (const child of await this.fs.listDir(dir, {cache: CachePolicy.useCache})) {
|
||||
// if (child.type === 'file') {
|
||||
// this.allFiles.push(child);
|
||||
// } else {
|
||||
// await this.precacheDir(child as VirtualDirectory);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// async search(query: string) {
|
||||
// await Promise.all(this.fs.root.map((root) => this.precacheDir(root)));
|
||||
// }
|
||||
|
||||
// get root() {
|
||||
// return this.fs.root;
|
||||
// }
|
||||
|
||||
// async listDir(dir: VirtualDirectory) {
|
||||
// return this.fs.listDir(dir);
|
||||
// }
|
||||
|
||||
// async download(file: VirtualFile) {
|
||||
// return this.fs.download(file);
|
||||
// }
|
||||
// }
|
||||
@@ -1,7 +1,7 @@
|
||||
import { loadScript } from "../../../helpers/loadScript";
|
||||
import { JsonChannelCaster, Message, MessageEmitter } from "./type";
|
||||
import { JsonChannelSummon, Message, MessageEmitter } from "./type";
|
||||
// @ts-ignore
|
||||
import type {CastReceiverContext, CastReceiverOptions} from '@types/chromecast-caf-receiver/cast.framework';
|
||||
import { WsSignalingSummon } from "./WsSignaling";
|
||||
|
||||
const CHANNEL = 'urn:x-cast:eu.fisoft.battlecaster.custom';
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ export class WebRtcChannelCaster extends MessageEmitter implements JsonChannelCa
|
||||
constructor(
|
||||
private channel: JsonChannelCaster,
|
||||
{onOpen}:Partial<{onOpen: Function}> = {},
|
||||
// @ts-ignore
|
||||
private ctx: RemoteDeviceManagerCtx
|
||||
) {
|
||||
super();
|
||||
@@ -144,6 +145,7 @@ export class WebRtcChannelSummon extends MessageEmitter implements JsonChannelSu
|
||||
};
|
||||
this.peer.ondatachannel = (event) => {
|
||||
if (event.channel.label === 'video') {
|
||||
// @ts-ignore
|
||||
this.videoChannel = event.channel;
|
||||
} else if (event.channel.label === 'json') {
|
||||
this.dataChannel = event.channel;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { CanvasDisplay } from "../../components/Map/object";
|
||||
import { createPersistentKeyValStore, createPersistentValue, PersistentKeyVal } from "../../helpers/persistentKeyValue";
|
||||
import { createPersistentValue, PersistentKeyVal } from "../../helpers/persistentKeyValue";
|
||||
import { VirtualFile } from "../FileSystem/types";
|
||||
import { PostMessageChannel } from "./Channels/PostMessage";
|
||||
import { WebRtcChannelCaster } from "./Channels/WebRTCChannel";
|
||||
import { WsSignalingCaster } from "./Channels/WsSignaling";
|
||||
import { RemoteDeviceManagerCtx } from "./RemoteDeviceManager";
|
||||
import { ImageSink, RemoteDevice } from "./types";
|
||||
import { ImageSink } from "./types";
|
||||
|
||||
export class ChromecastDevice {
|
||||
|
||||
@@ -106,11 +105,9 @@ class ChromecastImageSink implements ImageSink {
|
||||
console.log('show', file);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
async transform({x, y, scale}: {x: number, y: number, scale: number}) {
|
||||
await this.device.webRtcChannel?.send({
|
||||
type: 'transform',
|
||||
x, y, scale
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
get diameter() {
|
||||
|
||||
@@ -78,7 +78,7 @@ export class FullscreenWindowDevice implements RemoteDevice {
|
||||
static async getDevices(): Promise<FullscreenWindowConfig[]> {
|
||||
// @ts-ignore
|
||||
const screenDetails = await window.getScreenDetails()
|
||||
return screenDetails.screens.map((screen: any, index: number) => ({
|
||||
return screenDetails.screens.map((_: any, index: number) => ({
|
||||
name: `Screen ${index + 1}`,
|
||||
type: "fullscreen-window",
|
||||
screen: index
|
||||
@@ -149,10 +149,8 @@ class FullscreenWindowImageSink implements ImageSink {
|
||||
await this.device.webRtcChannel?.send({type: 'showFile', file});
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
async transform({x, y, scale}: {x: number, y: number, scale: number}) {
|
||||
await this.device.webRtcChannel?.send({
|
||||
type: 'transform',
|
||||
x, y, scale
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,20 +32,14 @@
|
||||
</AppLayout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import FileManager from '../../components/FileManager/FileManager.vue';
|
||||
import type { FileWithMeta } from '../../model/FileManager';
|
||||
import {ref, inject, watch} from 'vue';
|
||||
import {SenderSymbol} from '../../model/injectionSymbols';
|
||||
import Tabs from '../../components/Tabs.vue';
|
||||
import {VirtualFile} from '../../model/FileSystem/types';
|
||||
import ScreenManager from '../../components/ScreenManager/ScreenManager.vue';
|
||||
import RemoteDevices from '../../components/RemoteDevices/RemoteDevices.vue';
|
||||
import Soundboard from '../../components/Audio/Soundboard.vue';
|
||||
import FilesAside from '../../components/Files/FilesAside.vue';
|
||||
import MapControl from '../../components/Map/MapControl.vue';
|
||||
import AppLayout from '../../components/Common/AppLayout.vue';
|
||||
|
||||
const openedFile = ref<FileWithMeta>()
|
||||
const sender = inject(SenderSymbol);
|
||||
|
||||
const imageWidth = ref(20);
|
||||
@@ -78,66 +72,6 @@ watch(() => [positionX.value, positionY.value, flipped.value], () => {
|
||||
})
|
||||
});
|
||||
|
||||
function openFile(file: VirtualFile) {
|
||||
console.log('openFile', file);
|
||||
// sender?.sendMessage({
|
||||
// type: 'showFile',
|
||||
// file,
|
||||
// })
|
||||
// imageWidth.value = file.width ?? 20;
|
||||
// openedFile.value = file;
|
||||
}
|
||||
|
||||
function openMapWindow() {
|
||||
window.open('/map/', 'map');
|
||||
}
|
||||
|
||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
function onStreamImage() {
|
||||
if (!openedFile.value?.path) {
|
||||
return;
|
||||
}
|
||||
const image = new Image()
|
||||
image.onload = async () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.setAttribute('willReadFrequently', 'true');
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
const [maxWidth, maxHeight] = (image.width > image.height) ? [3840, 2160] : [2160, 3840];
|
||||
if (image.width > maxWidth || image.height > maxHeight) {
|
||||
const ratio = Math.min(maxWidth / image.width, maxHeight / image.height);
|
||||
canvas.width = image.width * ratio;
|
||||
canvas.height = image.height * ratio;
|
||||
}
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
const chunkSize = {width: 128, height: 128};
|
||||
|
||||
for (let x = 0; x < canvas.width; x += chunkSize.width) {
|
||||
for (let y = 0; y < canvas.height; y += chunkSize.height) {
|
||||
const imageData = ctx.getImageData(x, y, chunkSize.width, chunkSize.height);
|
||||
sender?.sendMessage({
|
||||
type: 'streamImage',
|
||||
imageData: {
|
||||
data: [...new Uint32Array(imageData.data.buffer)],
|
||||
width: imageData.width,
|
||||
height: imageData.height,
|
||||
},
|
||||
canvas: {
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
},
|
||||
x,
|
||||
y,
|
||||
}, {webRTC: true});
|
||||
await delay(100);
|
||||
}
|
||||
}
|
||||
};
|
||||
image.src = openedFile.value?.path;
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.aside {
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {inject, ref} from 'vue';
|
||||
import Debug from './Debug.vue';
|
||||
import {inject} from 'vue';
|
||||
import {WebRtcChannelSymbol} from '../../model/injectionSymbols';
|
||||
import {VirtualFile} from '../../model/FileSystem/types';
|
||||
|
||||
const webRtc = inject(WebRtcChannelSymbol)!;
|
||||
|
||||
@@ -27,6 +25,7 @@ function sendScreenInfo() {
|
||||
function sendAudioInfo() {
|
||||
const ac = new AudioContext();
|
||||
webRtc.send({
|
||||
// @ts-ignore
|
||||
type: 'audioInfo',
|
||||
channels: ac.destination.channelCount,
|
||||
channelInterpretation: ac.destination.channelInterpretation,
|
||||
@@ -45,28 +44,7 @@ webRtc.on((message) => {
|
||||
} else if (message.type === 'connected') {
|
||||
sendScreenInfo();
|
||||
sendAudioInfo();
|
||||
} else if (message.type === 'fileTransfered') {
|
||||
/*debugger;
|
||||
const key = fileToKey(message.meta);
|
||||
cachedFiles.set(key, message.blob);*/
|
||||
} else if (message.type === 'showFile') {
|
||||
/*debugger;
|
||||
const key = fileToKey(message.file);
|
||||
const blob = cachedFiles.get(key);
|
||||
if (blob) {
|
||||
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 (message.type === 'transform') {
|
||||
// canvas.value.style = `max-width: 100%;transform: scale(${message.scale}) translate(${message.x}%, ${message.y}%);transition: transform 100ms;`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
@@ -3,9 +3,7 @@ import './style.css';
|
||||
import { JsonChannelSymbol, WebRtcChannelSymbol } from './model/injectionSymbols';
|
||||
import { PostMessageChannel } from './model/Remote/Channels/PostMessage';
|
||||
import SummonApp from './modules/summon/SummonApp.vue';
|
||||
import { immediate } from './helpers/immediate';
|
||||
import { WebRtcChannelSummon } from './model/Remote/Channels/WebRTCChannel';
|
||||
import { loadScript } from './helpers/loadScript';
|
||||
import { ChromecastChannelSummon } from './model/Remote/Channels/ChromcastSummon';
|
||||
import { WsSignalingSummon } from './model/Remote/Channels/WsSignaling';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user