2020-11-07 05:59:01 +13:00
|
|
|
<template>
|
2021-02-12 09:17:47 +13:00
|
|
|
<canvas
|
2020-11-07 05:59:01 +13:00
|
|
|
ref="overlay"
|
2021-02-11 08:57:13 +13:00
|
|
|
class="neko-overlay"
|
2020-11-07 05:59:01 +13:00
|
|
|
tabindex="0"
|
2021-01-10 11:28:56 +13:00
|
|
|
:style="{ cursor }"
|
2020-11-07 05:59:01 +13:00
|
|
|
@click.stop.prevent
|
|
|
|
@contextmenu.stop.prevent
|
|
|
|
@wheel.stop.prevent="onWheel"
|
|
|
|
@mousemove.stop.prevent="onMouseMove"
|
|
|
|
@mousedown.stop.prevent="onMouseDown"
|
|
|
|
@mouseup.stop.prevent="onMouseUp"
|
|
|
|
@mouseenter.stop.prevent="onMouseEnter"
|
|
|
|
@mouseleave.stop.prevent="onMouseLeave"
|
2021-01-09 10:15:49 +13:00
|
|
|
@dragenter.stop.prevent="onDragEnter"
|
|
|
|
@dragleave.stop.prevent="onDragLeave"
|
|
|
|
@dragover.stop.prevent="onDragOver"
|
2021-01-08 08:21:40 +13:00
|
|
|
@drop.stop.prevent="onDrop"
|
2021-02-12 09:17:47 +13:00
|
|
|
/>
|
2020-11-07 05:59:01 +13:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2021-02-11 08:57:13 +13:00
|
|
|
.neko-overlay {
|
2020-11-07 05:59:01 +13:00
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
bottom: 0;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
2020-11-07 08:48:19 +13:00
|
|
|
outline: 0;
|
2020-11-07 05:59:01 +13:00
|
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<script lang="ts">
|
2020-12-02 22:45:23 +13:00
|
|
|
import { Vue, Component, Ref, Prop, Watch } from 'vue-property-decorator'
|
2020-11-07 08:48:19 +13:00
|
|
|
|
2020-11-29 09:47:16 +13:00
|
|
|
import GuacamoleKeyboard from './utils/guacamole-keyboard'
|
2021-01-10 03:31:01 +13:00
|
|
|
import { getFilesFromDataTansfer } from './utils/file-upload'
|
2020-11-29 09:47:16 +13:00
|
|
|
import { NekoWebRTC } from './internal/webrtc'
|
2021-02-14 05:29:35 +13:00
|
|
|
import { Scroll } from './types/state'
|
2020-11-07 05:59:01 +13:00
|
|
|
|
2021-02-11 08:49:02 +13:00
|
|
|
const inactiveCursorWin10 =
|
|
|
|
'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACEUlEQVR4nOzWz6sSURQH8O+89zJ5C32LKbAgktCSaPpBSL' +
|
|
|
|
'uSNtHqLcOV+BeIGxei0oCtFME/wI0bF4GCK6mNuAghH7xFlBAO7bQoA/Vik3riyghTaCQzTsLzbIZZDPdzzj3nzt3Df44dYDsBRNSYTqcn5XL5KoADy1VERL' +
|
|
|
|
'Is02g0+phIJG4BsFkOEEVxjhgOh59kWb5rKWIBWCAGg0EnFovdtgyhB+grkU6n7wA4ZzlgCWKzlVgGsLQnVgE2gVh7xvP5PH9ciUajFQDHyWTyHQDVKOS3+F' +
|
|
|
|
'sF/pyOcDh83Uhj/nMFBEFANpuF0+nUQ92SJD0G8AXAdwAz0wE+nw8OhwPNZhPj8RiBQOC0Vqu9EgSBcrnc11Qq9R7AeW5cd/GVsdgCr9dLiqJQtVqdv/v9fm' +
|
|
|
|
'KM9UVRfArgJoBrAC4DsJsOcLlc1Gg0qNVqVRljI0mS5oh6vU6lUukFgEta5gemLr4AFAoF6nQ6b20223G73X6ZyWTmgFAoRL1ej3f+DQ1gfqiq+qbf73/weD' +
|
|
|
|
'zPADwoFouPut3uzO12UyQSoclkotrt9ocAHKZnr8UhAP4bvg/gIs+UMfaaMTZTFOUkHo8/B/AEwAWjl5pV+j1dZ//g4xUMBo8YY/cqlcqhNvffAJxq40dmA5' +
|
|
|
|
'bFPoAjrev5EfwZQNfoKbju/u1ri/PvfgKYGMl+K2I7b8U7wA5wpgC/AgAA///Yyif1MZXzRQAAAABJRU5ErkJggg==) 4 4, crosshair'
|
|
|
|
|
2020-11-07 05:59:01 +13:00
|
|
|
@Component({
|
|
|
|
name: 'neko-overlay',
|
|
|
|
})
|
|
|
|
export default class extends Vue {
|
2021-02-12 09:17:47 +13:00
|
|
|
@Ref('overlay') readonly _overlay!: HTMLCanvasElement
|
|
|
|
private _ctx: any = null
|
2020-11-07 05:59:01 +13:00
|
|
|
|
|
|
|
private keyboard = GuacamoleKeyboard()
|
|
|
|
private focused = false
|
|
|
|
|
|
|
|
@Prop()
|
|
|
|
private readonly webrtc!: NekoWebRTC
|
|
|
|
|
|
|
|
@Prop()
|
2021-02-14 05:29:35 +13:00
|
|
|
private readonly scroll!: Scroll
|
2020-11-07 05:59:01 +13:00
|
|
|
|
|
|
|
@Prop()
|
2021-02-13 10:27:51 +13:00
|
|
|
private readonly screenSize!: { width: number; height: number }
|
2020-11-07 05:59:01 +13:00
|
|
|
|
|
|
|
@Prop()
|
2021-02-13 10:27:51 +13:00
|
|
|
private readonly canvasSize!: { width: number; height: number }
|
2020-11-07 05:59:01 +13:00
|
|
|
|
2021-02-18 08:39:12 +13:00
|
|
|
@Prop()
|
|
|
|
private readonly cursorTag!: string
|
|
|
|
|
2020-11-07 05:59:01 +13:00
|
|
|
@Prop()
|
|
|
|
private readonly isControling!: boolean
|
|
|
|
|
2020-12-02 22:45:23 +13:00
|
|
|
@Prop()
|
|
|
|
private readonly implicitControl!: boolean
|
|
|
|
|
2021-01-10 11:28:56 +13:00
|
|
|
get cursor(): string {
|
2021-02-11 08:49:02 +13:00
|
|
|
if (!this.isControling) {
|
|
|
|
return inactiveCursorWin10
|
|
|
|
}
|
|
|
|
|
2021-02-14 05:29:35 +13:00
|
|
|
if (!this.cursorImage) {
|
2021-02-11 08:49:02 +13:00
|
|
|
return 'auto'
|
|
|
|
}
|
2021-01-10 11:28:56 +13:00
|
|
|
|
2021-02-14 05:29:35 +13:00
|
|
|
const { uri, x, y } = this.cursorImage
|
2021-01-11 00:34:08 +13:00
|
|
|
return 'url(' + uri + ') ' + x + ' ' + y + ', auto'
|
2021-01-10 11:28:56 +13:00
|
|
|
}
|
|
|
|
|
2020-11-07 05:59:01 +13:00
|
|
|
mounted() {
|
2021-02-12 09:17:47 +13:00
|
|
|
this._ctx = this._overlay.getContext('2d')
|
|
|
|
|
|
|
|
// synchronize intrinsic with extrinsic dimensions
|
|
|
|
const { width, height } = this._overlay.getBoundingClientRect()
|
|
|
|
this._overlay.width = width
|
|
|
|
this._overlay.height = height
|
|
|
|
|
2020-11-07 05:59:01 +13:00
|
|
|
// Initialize Guacamole Keyboard
|
|
|
|
this.keyboard.onkeydown = (key: number) => {
|
2020-12-02 22:45:23 +13:00
|
|
|
if (!this.focused) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.isControling) {
|
2020-11-07 05:59:01 +13:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
this.webrtc.send('keydown', { key })
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
this.keyboard.onkeyup = (key: number) => {
|
2020-12-02 22:45:23 +13:00
|
|
|
if (!this.focused) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.isControling) {
|
2020-11-07 05:59:01 +13:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.webrtc.send('keyup', { key })
|
|
|
|
}
|
|
|
|
this.keyboard.listenTo(this._overlay)
|
2021-02-14 05:29:35 +13:00
|
|
|
|
|
|
|
this.webrtc.addListener('cursor-position', this.onCursorPosition)
|
|
|
|
this.webrtc.addListener('cursor-image', this.onCursorImage)
|
2021-02-19 08:53:08 +13:00
|
|
|
this.webrtc.addListener('disconnected', this.canvasClear)
|
2021-02-18 09:54:15 +13:00
|
|
|
this.cursorElement.onload = this.canvasRedraw
|
2020-11-07 05:59:01 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
beforeDestroy() {
|
|
|
|
// Guacamole Keyboard does not provide destroy functions
|
2021-02-14 05:29:35 +13:00
|
|
|
|
|
|
|
this.webrtc.removeListener('cursor-position', this.onCursorPosition)
|
|
|
|
this.webrtc.removeListener('cursor-image', this.onCursorImage)
|
2021-02-19 08:53:08 +13:00
|
|
|
this.webrtc.removeListener('disconnected', this.canvasClear)
|
2021-02-18 09:54:15 +13:00
|
|
|
this.cursorElement.onload = null
|
2020-11-07 05:59:01 +13:00
|
|
|
}
|
|
|
|
|
2021-01-08 08:21:40 +13:00
|
|
|
getMousePos(clientX: number, clientY: number) {
|
2020-11-07 05:59:01 +13:00
|
|
|
const rect = this._overlay.getBoundingClientRect()
|
|
|
|
|
2021-01-08 08:21:40 +13:00
|
|
|
return {
|
2021-02-13 05:11:28 +13:00
|
|
|
x: Math.round((this.screenSize.width / rect.width) * (clientX - rect.left)),
|
|
|
|
y: Math.round((this.screenSize.height / rect.height) * (clientY - rect.top)),
|
2021-01-08 08:21:40 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setMousePos(e: MouseEvent) {
|
2021-02-12 07:03:31 +13:00
|
|
|
const pos = this.getMousePos(e.clientX, e.clientY)
|
|
|
|
this.webrtc.send('mousemove', pos)
|
2021-02-14 05:29:35 +13:00
|
|
|
Vue.set(this, 'cursorPosition', pos)
|
2020-11-07 05:59:01 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
onWheel(e: WheelEvent) {
|
|
|
|
if (!this.isControling) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let x = e.deltaX
|
|
|
|
let y = e.deltaY
|
|
|
|
|
2021-02-14 05:29:35 +13:00
|
|
|
if (this.scroll.inverse) {
|
2020-11-07 05:59:01 +13:00
|
|
|
x *= -1
|
|
|
|
y *= -1
|
|
|
|
}
|
|
|
|
|
2021-02-14 05:29:35 +13:00
|
|
|
x = Math.min(Math.max(x, -this.scroll.sensitivity), this.scroll.sensitivity)
|
|
|
|
y = Math.min(Math.max(y, -this.scroll.sensitivity), this.scroll.sensitivity)
|
2020-11-07 05:59:01 +13:00
|
|
|
|
|
|
|
this.setMousePos(e)
|
|
|
|
this.webrtc.send('wheel', { x, y })
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseMove(e: MouseEvent) {
|
|
|
|
if (!this.isControling) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setMousePos(e)
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseDown(e: MouseEvent) {
|
|
|
|
if (!this.isControling) {
|
2021-02-11 08:49:02 +13:00
|
|
|
this.implicitControlRequest(e)
|
2020-11-07 05:59:01 +13:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setMousePos(e)
|
|
|
|
this.webrtc.send('mousedown', { key: e.button + 1 })
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseUp(e: MouseEvent) {
|
|
|
|
if (!this.isControling) {
|
2021-02-11 08:49:02 +13:00
|
|
|
this.implicitControlRequest(e)
|
2020-11-07 05:59:01 +13:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setMousePos(e)
|
|
|
|
this.webrtc.send('mouseup', { key: e.button + 1 })
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseEnter(e: MouseEvent) {
|
|
|
|
this._overlay.focus()
|
|
|
|
this.focused = true
|
|
|
|
|
2021-02-19 04:53:19 +13:00
|
|
|
if (this.isControling) {
|
|
|
|
this.updateKbdModifiers(e)
|
2020-11-07 05:59:01 +13:00
|
|
|
}
|
2020-12-02 22:45:23 +13:00
|
|
|
}
|
2020-11-07 05:59:01 +13:00
|
|
|
|
2020-12-02 22:45:23 +13:00
|
|
|
onMouseLeave(e: MouseEvent) {
|
2020-11-07 05:59:01 +13:00
|
|
|
this._overlay.blur()
|
|
|
|
this.focused = false
|
2020-12-02 22:45:23 +13:00
|
|
|
|
|
|
|
if (this.isControling) {
|
|
|
|
this.keyboard.reset()
|
|
|
|
|
2021-02-19 04:53:19 +13:00
|
|
|
// save current kbd modifiers state
|
|
|
|
Vue.set(this, 'kbdModifiers', {
|
|
|
|
capslock: e.getModifierState('CapsLock'),
|
|
|
|
numlock: e.getModifierState('NumLock'),
|
|
|
|
})
|
2020-12-02 22:45:23 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-09 10:15:49 +13:00
|
|
|
onDragEnter(e: DragEvent) {
|
|
|
|
this.onMouseEnter(e as MouseEvent)
|
2021-01-08 08:21:40 +13:00
|
|
|
}
|
|
|
|
|
2021-01-09 10:15:49 +13:00
|
|
|
onDragLeave(e: DragEvent) {
|
|
|
|
this.onMouseLeave(e as MouseEvent)
|
|
|
|
}
|
|
|
|
|
|
|
|
onDragOver(e: DragEvent) {
|
|
|
|
this.onMouseMove(e as MouseEvent)
|
|
|
|
}
|
2021-01-08 08:21:40 +13:00
|
|
|
|
2021-01-10 03:31:01 +13:00
|
|
|
async onDrop(e: DragEvent) {
|
2021-01-09 10:15:49 +13:00
|
|
|
if (this.isControling || this.implicitControl) {
|
|
|
|
let dt = e.dataTransfer
|
|
|
|
if (!dt) return
|
2021-01-08 08:21:40 +13:00
|
|
|
|
2021-01-10 03:31:01 +13:00
|
|
|
const files = await getFilesFromDataTansfer(dt)
|
|
|
|
if (files.length === 0) return
|
2021-01-08 08:21:40 +13:00
|
|
|
|
2021-01-09 10:15:49 +13:00
|
|
|
this.$emit('drop-files', { ...this.getMousePos(e.clientX, e.clientY), files })
|
|
|
|
}
|
2021-01-08 08:21:40 +13:00
|
|
|
}
|
|
|
|
|
2021-02-19 04:53:19 +13:00
|
|
|
//
|
|
|
|
// keyboard modifiers
|
|
|
|
//
|
|
|
|
|
|
|
|
private kbdModifiers: { capslock: boolean; numlock: boolean } | null = null
|
|
|
|
|
|
|
|
updateKbdModifiers(e: MouseEvent) {
|
|
|
|
const capslock = e.getModifierState('CapsLock')
|
|
|
|
const numlock = e.getModifierState('NumLock')
|
|
|
|
|
|
|
|
if (
|
|
|
|
this.kbdModifiers === null ||
|
|
|
|
this.kbdModifiers.capslock !== capslock ||
|
|
|
|
this.kbdModifiers.numlock !== numlock
|
|
|
|
) {
|
|
|
|
this.$emit('update-kbd-modifiers', { capslock, numlock })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-14 05:29:35 +13:00
|
|
|
//
|
|
|
|
// canvas
|
|
|
|
//
|
|
|
|
|
|
|
|
private cursorImage: { width: number; height: number; x: number; y: number; uri: string } | null = null
|
|
|
|
private cursorElement: HTMLImageElement = new Image()
|
|
|
|
private cursorPosition: { x: number; y: number } | null = null
|
|
|
|
|
2021-02-19 09:09:08 +13:00
|
|
|
@Watch('screenSize')
|
|
|
|
onScreenSizeChange() {
|
|
|
|
if (this.isControling) {
|
|
|
|
this.canvasClear()
|
|
|
|
} else {
|
|
|
|
this.canvasRedraw()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-13 05:11:28 +13:00
|
|
|
@Watch('canvasSize')
|
|
|
|
onCanvasSizeChange({ width, height }: { width: number; height: number }) {
|
|
|
|
this._overlay.width = width
|
|
|
|
this._overlay.height = height
|
2021-02-19 09:09:08 +13:00
|
|
|
|
|
|
|
if (this.isControling) {
|
|
|
|
this.canvasClear()
|
|
|
|
} else {
|
|
|
|
this.canvasRedraw()
|
|
|
|
}
|
2021-02-13 05:11:28 +13:00
|
|
|
}
|
|
|
|
|
2021-02-14 05:29:35 +13:00
|
|
|
onCursorPosition(data: { x: number; y: number }) {
|
|
|
|
if (!this.isControling) {
|
|
|
|
Vue.set(this, 'cursorPosition', data)
|
|
|
|
this.canvasRedraw()
|
|
|
|
}
|
2021-02-12 09:17:47 +13:00
|
|
|
}
|
|
|
|
|
2021-02-14 05:29:35 +13:00
|
|
|
onCursorImage(data: { width: number; height: number; x: number; y: number; uri: string }) {
|
|
|
|
Vue.set(this, 'cursorImage', data)
|
|
|
|
|
|
|
|
if (!this.isControling) {
|
|
|
|
this.cursorElement.src = data.uri
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
canvasRedraw() {
|
|
|
|
if (this.cursorPosition == null || this.screenSize == null || this.cursorImage == null) return
|
2021-02-12 09:17:47 +13:00
|
|
|
|
2021-02-13 05:11:28 +13:00
|
|
|
// get intrinsic dimensions
|
|
|
|
const { width, height } = this._overlay
|
2021-02-12 09:17:47 +13:00
|
|
|
|
2021-02-18 08:39:12 +13:00
|
|
|
// clear drawings
|
2021-02-12 09:17:47 +13:00
|
|
|
this._ctx.clearRect(0, 0, width, height)
|
2021-02-18 08:39:12 +13:00
|
|
|
|
|
|
|
// ignore hidden cursor
|
|
|
|
if (this.cursorImage.width <= 1 && this.cursorImage.height <= 1) return
|
|
|
|
|
|
|
|
// redraw cursor
|
|
|
|
let x = Math.round((this.cursorPosition.x / this.screenSize.width) * width - this.cursorImage.x)
|
|
|
|
let y = Math.round((this.cursorPosition.y / this.screenSize.height) * height - this.cursorImage.y)
|
|
|
|
this._ctx.drawImage(this.cursorElement, x, y, this.cursorImage.width, this.cursorImage.height)
|
|
|
|
|
|
|
|
// redraw cursor tag
|
|
|
|
if (this.cursorTag) {
|
|
|
|
x += this.cursorImage.width + this.cursorImage.x
|
|
|
|
y += this.cursorImage.height + this.cursorImage.y
|
|
|
|
|
|
|
|
this._ctx.save()
|
|
|
|
this._ctx.font = '14px Arial, sans-serif'
|
|
|
|
this._ctx.textBaseline = 'top'
|
|
|
|
this._ctx.shadowColor = 'black'
|
|
|
|
this._ctx.shadowBlur = 2
|
|
|
|
this._ctx.lineWidth = 2
|
|
|
|
this._ctx.fillStyle = 'black'
|
|
|
|
this._ctx.strokeText(this.cursorTag, x, y)
|
|
|
|
this._ctx.shadowBlur = 0
|
|
|
|
this._ctx.fillStyle = 'white'
|
|
|
|
this._ctx.fillText(this.cursorTag, x, y)
|
|
|
|
this._ctx.restore()
|
|
|
|
}
|
2021-02-12 09:17:47 +13:00
|
|
|
}
|
|
|
|
|
2021-02-14 05:29:35 +13:00
|
|
|
canvasClear() {
|
|
|
|
const { width, height } = this._overlay
|
|
|
|
this._ctx.clearRect(0, 0, width, height)
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// implicit hosting
|
|
|
|
//
|
|
|
|
|
2021-02-11 08:49:02 +13:00
|
|
|
private reqMouseDown: any | null = null
|
|
|
|
private reqMouseUp: any | null = null
|
2021-02-14 05:29:35 +13:00
|
|
|
|
2020-12-02 22:45:23 +13:00
|
|
|
@Watch('isControling')
|
|
|
|
onControlChange(isControling: boolean) {
|
2021-02-19 04:53:19 +13:00
|
|
|
Vue.set(this, 'kbdModifiers', null)
|
|
|
|
|
2021-02-11 08:49:02 +13:00
|
|
|
if (isControling && this.reqMouseDown) {
|
2021-02-19 04:53:19 +13:00
|
|
|
this.updateKbdModifiers(this.reqMouseDown)
|
2021-02-11 08:49:02 +13:00
|
|
|
this.setMousePos(this.reqMouseDown)
|
|
|
|
this.webrtc.send('mousedown', { key: this.reqMouseDown.button + 1 })
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isControling && this.reqMouseUp) {
|
|
|
|
this.webrtc.send('mouseup', { key: this.reqMouseUp.button + 1 })
|
|
|
|
}
|
|
|
|
|
2021-02-12 09:17:47 +13:00
|
|
|
if (isControling) {
|
2021-02-14 05:29:35 +13:00
|
|
|
this.canvasClear()
|
2021-02-12 09:17:47 +13:00
|
|
|
} else {
|
2021-02-14 05:29:35 +13:00
|
|
|
this.canvasRedraw()
|
2021-02-12 09:17:47 +13:00
|
|
|
}
|
|
|
|
|
2021-02-11 08:49:02 +13:00
|
|
|
this.reqMouseDown = null
|
|
|
|
this.reqMouseUp = null
|
2020-12-02 22:45:23 +13:00
|
|
|
}
|
|
|
|
|
2021-02-11 08:49:02 +13:00
|
|
|
implicitControlRequest(e: MouseEvent) {
|
|
|
|
if (this.implicitControl && e.type === 'mousedown' && this.reqMouseDown == null) {
|
|
|
|
this.reqMouseDown = e
|
2020-12-02 22:45:23 +13:00
|
|
|
this.$emit('implicit-control-request')
|
|
|
|
}
|
2021-02-11 08:49:02 +13:00
|
|
|
|
|
|
|
if (this.implicitControl && e.type === 'mouseup' && this.reqMouseUp == null) {
|
|
|
|
this.reqMouseUp = e
|
|
|
|
}
|
2020-12-02 22:45:23 +13:00
|
|
|
}
|
|
|
|
|
2021-02-11 08:49:02 +13:00
|
|
|
// unused
|
2020-12-02 22:45:23 +13:00
|
|
|
implicitControlRelease() {
|
|
|
|
if (this.implicitControl) {
|
|
|
|
this.$emit('implicit-control-release')
|
|
|
|
}
|
2020-11-07 05:59:01 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|