diff --git a/package.json b/package.json index eb929a66..caf04dee 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "@babel/core": "^7.5.0", "@gamestdio/websocket": "^0.3.2", "@webcomponents/custom-elements": "^1.2.4", - "@wessberg/pointer-events": "^1.0.9", "babel-loader": "^8.0.6", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "cheerio": "^1.0.0-rc.2", @@ -150,7 +149,8 @@ "CSS", "customElements", "AbortController", - "matchMedia" + "matchMedia", + "MessageChannel" ], "ignore": [ "dist", diff --git a/src/routes/_components/Draggable.html b/src/routes/_components/Draggable.html index f93dfeb2..0b360b1b 100644 --- a/src/routes/_components/Draggable.html +++ b/src/routes/_components/Draggable.html @@ -1,13 +1,13 @@ -
-
@@ -30,19 +30,60 @@ } diff --git a/src/routes/_components/dialog/components/MediaFocalPointDialog.html b/src/routes/_components/dialog/components/MediaFocalPointDialog.html index 6db356fd..83b66437 100644 --- a/src/routes/_components/dialog/components/MediaFocalPointDialog.html +++ b/src/routes/_components/dialog/components/MediaFocalPointDialog.html @@ -25,12 +25,14 @@ { let float = parseFloat(rawText) @@ -208,6 +222,7 @@ Draggable }, data: () => ({ + dragging: false, rawFocusX: '0', rawFocusY: '0', containerWidth: 0, @@ -276,8 +291,6 @@ }) }, setupSyncToStore () { - const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000) - const observeAndSync = (rawKey, key) => { this.observe(rawKey, rawFocus => { const { realm, index, media } = this.get() @@ -285,7 +298,7 @@ if (media[index][key] !== rawFocusDecimal) { media[index][key] = rawFocusDecimal this.store.setComposeData(realm, { media }) - saveStore() + scheduleIdleTask(() => this.store.save()) } }, { init: false }) } @@ -294,16 +307,16 @@ observeAndSync('rawFocusY', 'focusY') }, onDraggableChange ({ x, y }) { - const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000) - - scheduleIdleTask(() => { - const focusX = percentToCoords(x * 100) - const focusY = percentToCoords(100 - (y * 100)) + updateFocalPointsInStore(() => { + const focusX = parseAndValidateFloat(percentToCoords(x * 100)) + const focusY = parseAndValidateFloat(percentToCoords(100 - (y * 100))) const { realm, index, media } = this.get() - media[index].focusX = parseAndValidateFloat(focusX) - media[index].focusY = parseAndValidateFloat(focusY) - this.store.setComposeData(realm, { media }) - saveStore() + if (media[index].focusX !== focusX || media[index].focusY !== focusY) { + media[index].focusX = focusX + media[index].focusY = focusY + this.store.setComposeData(realm, { media }) + scheduleIdleTask(() => this.store.save()) + } }) }, measure () { diff --git a/src/routes/_utils/asyncPolyfills.js b/src/routes/_utils/asyncPolyfills.js index 7b2778b4..8fd253ae 100644 --- a/src/routes/_utils/asyncPolyfills.js +++ b/src/routes/_utils/asyncPolyfills.js @@ -13,7 +13,3 @@ export const importIndexedDBGetAllShim = () => import( export const importCustomElementsPolyfill = () => import( /* webpackChunkName: '$polyfill$-@webcomponents/custom-elements' */ '@webcomponents/custom-elements' ) - -export const importPointerEventsPolyfill = () => import( - /* webpackChunkName: '$polyfill$-@wessberg/pointer-events' */ '@wessberg/pointer-events' - ) diff --git a/src/routes/_utils/loadPolyfills.js b/src/routes/_utils/loadPolyfills.js index 5a8eb102..8c7fea51 100644 --- a/src/routes/_utils/loadPolyfills.js +++ b/src/routes/_utils/loadPolyfills.js @@ -2,8 +2,7 @@ import { importCustomElementsPolyfill, importIndexedDBGetAllShim, importIntersectionObserver, - importRequestIdleCallback, - importPointerEventsPolyfill + importRequestIdleCallback } from './asyncPolyfills' export function loadPolyfills () { @@ -11,7 +10,6 @@ export function loadPolyfills () { typeof IntersectionObserver === 'undefined' && importIntersectionObserver(), typeof requestIdleCallback === 'undefined' && importRequestIdleCallback(), !IDBObjectStore.prototype.getAll && importIndexedDBGetAllShim(), - typeof customElements === 'undefined' && importCustomElementsPolyfill(), - typeof PointerEvent === 'undefined' && importPointerEventsPolyfill() + typeof customElements === 'undefined' && importCustomElementsPolyfill() ]) } diff --git a/src/routes/_utils/pointerEvents.js b/src/routes/_utils/pointerEvents.js new file mode 100644 index 00000000..051bcfb8 --- /dev/null +++ b/src/routes/_utils/pointerEvents.js @@ -0,0 +1,53 @@ +import { get } from './lodash-lite' + +const hasPointerEvents = process.browser && typeof PointerEvent === 'function' + +// Epiphany browser reports that it's a touch device even though it's not +const isTouchDevice = process.browser && 'ontouchstart' in document && !/Epiphany/.test(navigator.userAgent) + +let pointerDown +let pointerUp +let pointerLeave +let pointerMove + +function createEventListener (event) { + return (node, callback) => { + const listener = e => { + // lightweight polyfill for clientX/clientY in pointer events, + // which is slightly different in touch events + if (typeof e.clientX !== 'number') { + e.clientX = get(e, ['touches', 0, 'clientX']) + } + if (typeof e.clientY !== 'number') { + e.clientY = get(e, ['touches', 0, 'clientY']) + } + callback(e) + } + + node.addEventListener(event, listener) + return { + destroy () { + node.removeEventListener(event, listener) + } + } + } +} + +if (hasPointerEvents) { + pointerDown = createEventListener('pointerdown') + pointerUp = createEventListener('pointerup') + pointerLeave = createEventListener('pointerleave') + pointerMove = createEventListener('pointermove') +} else if (isTouchDevice) { + pointerDown = createEventListener('touchstart') + pointerUp = createEventListener('touchend') + pointerLeave = createEventListener('touchend') + pointerMove = createEventListener('touchmove') +} else { + pointerDown = createEventListener('mousedown') + pointerUp = createEventListener('mouseup') + pointerLeave = createEventListener('mouseleave') + pointerMove = createEventListener('mousemove') +} + +export { pointerDown, pointerUp, pointerLeave, pointerMove } diff --git a/src/routes/_utils/requestPostAnimationFrame.js b/src/routes/_utils/requestPostAnimationFrame.js new file mode 100644 index 00000000..d218d27f --- /dev/null +++ b/src/routes/_utils/requestPostAnimationFrame.js @@ -0,0 +1,9 @@ +// modeled after https://github.com/andrewiggins/afterframe +// see also https://github.com/WICG/requestPostAnimationFrame +export const requestPostAnimationFrame = cb => { + requestAnimationFrame(() => { + const channel = new MessageChannel() + channel.port1.onmessage = cb + channel.port2.postMessage(undefined) + }) +} diff --git a/src/routes/_utils/throttleRaf.js b/src/routes/_utils/throttleRaf.js deleted file mode 100644 index 8755272d..00000000 --- a/src/routes/_utils/throttleRaf.js +++ /dev/null @@ -1,18 +0,0 @@ -// ensure callback is only executed once per raf -export const throttleRaf = () => { - let rafCallback - let rafQueued - - return function throttledRaf (callback) { - rafCallback = callback - if (!rafQueued) { - rafQueued = true - requestAnimationFrame(() => { - const cb = rafCallback - rafCallback = null - rafQueued = false - cb() - }) - } - } -} diff --git a/src/routes/_utils/throttleTimers.js b/src/routes/_utils/throttleTimers.js new file mode 100644 index 00000000..bf0aff43 --- /dev/null +++ b/src/routes/_utils/throttleTimers.js @@ -0,0 +1,28 @@ +// Sometimes we want to queue multiple requestAnimationFrames but only run the last one. +// It's tedious to do this using cancelAnimationFrame, so this is a utility to throttle +// a timer such that it only runs the last callback when it fires. + +import { requestPostAnimationFrame } from './requestPostAnimationFrame' +import { scheduleIdleTask } from './scheduleIdleTask' + +const throttle = (timer) => { + return () => { + let queuedCallback + + return function throttledRaf (callback) { + const alreadyQueued = !!queuedCallback + queuedCallback = callback + if (!alreadyQueued) { + timer(() => { + const cb = queuedCallback + queuedCallback = null + cb() + }) + } + } + } +} + +export const throttleRequestAnimationFrame = throttle(requestAnimationFrame) +export const throttleRequestPostAnimationFrame = throttle(requestPostAnimationFrame) +export const throttleScheduleIdleTask = throttle(scheduleIdleTask) diff --git a/src/scss/themes/_base.scss b/src/scss/themes/_base.scss index 1317d126..84b88a9a 100644 --- a/src/scss/themes/_base.scss +++ b/src/scss/themes/_base.scss @@ -124,5 +124,7 @@ --focal-img-backdrop-filter: #{rgba($main-bg-color, 0.5)}; --focal-img-bg: #{rgba($main-bg-color, 0.3)}; --focal-bg: #{rgba($toast-bg, 0.8)}; + --focal-bg-drag: #{rgba($toast-bg, 0.9)}; + --focal-bg-hover: #{lighten(rgba($toast-bg, 0.8), 5%)}; --focal-color: #{$secondary-text-color}; }