mirror of
https://github.com/openstf/stf
synced 2025-10-05 10:39:25 +02:00
Refactor screen code. Still not the best but a bit more manageable now.
This commit is contained in:
parent
f9e2c4f0f2
commit
4a8806debc
1 changed files with 224 additions and 191 deletions
|
@ -1,7 +1,13 @@
|
||||||
var _ = require('lodash')
|
var _ = require('lodash')
|
||||||
|
|
||||||
module.exports = function DeviceScreenDirective($document, ScalingService,
|
module.exports = function DeviceScreenDirective(
|
||||||
VendorUtil, PageVisibilityService, BrowserInfo, $timeout) {
|
$document
|
||||||
|
, ScalingService
|
||||||
|
, VendorUtil
|
||||||
|
, PageVisibilityService
|
||||||
|
, $timeout
|
||||||
|
, $window
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
restrict: 'E'
|
restrict: 'E'
|
||||||
, template: require('./screen.jade')
|
, template: require('./screen.jade')
|
||||||
|
@ -20,35 +26,47 @@ module.exports = function DeviceScreenDirective($document, ScalingService,
|
||||||
|
|
||||||
var input = element.find('input')
|
var input = element.find('input')
|
||||||
|
|
||||||
|
var screen = scope.screen = {
|
||||||
|
rotation: 0
|
||||||
|
, bounds: {
|
||||||
|
x: 0
|
||||||
|
, y: 0
|
||||||
|
, w: 0
|
||||||
|
, h: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var scaler = ScalingService.coordinator(
|
||||||
|
device.display.width
|
||||||
|
, device.display.height
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SCREEN HANDLING
|
* SCREEN HANDLING
|
||||||
*
|
*
|
||||||
* This section should deal with updating the screen ONLY.
|
* This section should deal with updating the screen ONLY.
|
||||||
*/
|
*/
|
||||||
;(function() {
|
;(function() {
|
||||||
|
|
||||||
var canvas = element.find('canvas')[0]
|
var canvas = element.find('canvas')[0]
|
||||||
, g = canvas.getContext('2d')
|
, g = canvas.getContext('2d')
|
||||||
|
|
||||||
var ws
|
var devicePixelRatio = window.devicePixelRatio || 1
|
||||||
, loading = false
|
, backingStoreRatio = g.webkitBackingStorePixelRatio ||
|
||||||
|
g.mozBackingStorePixelRatio ||
|
||||||
|
g.msBackingStorePixelRatio ||
|
||||||
|
g.oBackingStorePixelRatio ||
|
||||||
|
g.backingStorePixelRatio || 1
|
||||||
|
, frontBackRatio = devicePixelRatio / backingStoreRatio
|
||||||
|
|
||||||
var guestDisplayDensity = setDisplayDensity(1.5)
|
var options = {
|
||||||
//var guestDisplayRotation = 0
|
autoScaleForRetina: true
|
||||||
|
, density: Math.max(1, Math.min(1.5, devicePixelRatio || 1))
|
||||||
var screen = scope.screen = {
|
, minscale: 0.36
|
||||||
scaler: ScalingService.coordinator(
|
|
||||||
device.display.width, device.display.height
|
|
||||||
)
|
|
||||||
, rotation: 0
|
|
||||||
, bounds: {
|
|
||||||
x: 0
|
|
||||||
, y: 0
|
|
||||||
, w: 0
|
|
||||||
, h: 0
|
|
||||||
}
|
|
||||||
, autoScaleForRetina: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updating = false
|
||||||
|
|
||||||
var cachedScreen = {
|
var cachedScreen = {
|
||||||
rotation: 0
|
rotation: 0
|
||||||
, bounds: {
|
, bounds: {
|
||||||
|
@ -62,24 +80,11 @@ module.exports = function DeviceScreenDirective($document, ScalingService,
|
||||||
var cachedImageWidth = 0
|
var cachedImageWidth = 0
|
||||||
, cachedImageHeight = 0
|
, cachedImageHeight = 0
|
||||||
|
|
||||||
// NOTE: instead of fa-pane-resize, a fa-child-pane-resize could be better
|
|
||||||
var onPanelResizeThrottled = _.throttle(updateBounds, 16)
|
|
||||||
scope.$on('fa-pane-resize', onPanelResizeThrottled)
|
|
||||||
|
|
||||||
function setDisplayDensity(forRetina) {
|
|
||||||
// FORCE
|
|
||||||
forRetina = 1.5
|
|
||||||
|
|
||||||
guestDisplayDensity = BrowserInfo.retina ? forRetina : 1
|
|
||||||
return guestDisplayDensity
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBounds() {
|
function updateBounds() {
|
||||||
|
// TODO: element is an object HTMLUnknownElement in IE9
|
||||||
screen.bounds.w = element[0].offsetWidth
|
screen.bounds.w = element[0].offsetWidth
|
||||||
screen.bounds.h = element[0].offsetHeight
|
screen.bounds.h = element[0].offsetHeight
|
||||||
|
|
||||||
// TODO: element is an object HTMLUnknownElement in IE9
|
|
||||||
|
|
||||||
// Developer error, let's try to reduce debug time
|
// Developer error, let's try to reduce debug time
|
||||||
if (!screen.bounds.w || !screen.bounds.h) {
|
if (!screen.bounds.w || !screen.bounds.h) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -88,51 +93,41 @@ module.exports = function DeviceScreenDirective($document, ScalingService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.retryLoadingScreen = function () {
|
|
||||||
if (scope.displayError === 'secure') {
|
|
||||||
control.home()
|
|
||||||
}
|
|
||||||
$timeout(maybeLoadScreen, 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeLoadScreen() {
|
function maybeLoadScreen() {
|
||||||
var size
|
var size
|
||||||
if (ws) {
|
if (shouldUpdateScreen()) {
|
||||||
if (!loading && scope.$parent.showScreen && device) {
|
switch (screen.rotation) {
|
||||||
switch (screen.rotation) {
|
case 0:
|
||||||
case 0:
|
case 180:
|
||||||
case 180:
|
size = adjustBoundedSize(
|
||||||
size = adjustBoundedSize(
|
screen.bounds.w
|
||||||
screen.bounds.w
|
, screen.bounds.h
|
||||||
, screen.bounds.h
|
)
|
||||||
)
|
break
|
||||||
break
|
case 90:
|
||||||
case 90:
|
case 270:
|
||||||
case 270:
|
size = adjustBoundedSize(
|
||||||
size = adjustBoundedSize(
|
screen.bounds.h
|
||||||
screen.bounds.h
|
, screen.bounds.w
|
||||||
, screen.bounds.w
|
)
|
||||||
)
|
break
|
||||||
break
|
|
||||||
}
|
|
||||||
loading = true
|
|
||||||
ws.send('{"op":"jpeg","w":' + size.w + ',"h":' + size.h + '}')
|
|
||||||
}
|
}
|
||||||
|
updating = true
|
||||||
|
ws.send('{"op":"jpeg","w":' + size.w + ',"h":' + size.h + '}')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustBoundedSize(w, h) {
|
function adjustBoundedSize(w, h) {
|
||||||
var sw = w * guestDisplayDensity
|
var sw = w * options.density
|
||||||
, sh = h * guestDisplayDensity
|
, sh = h * options.density
|
||||||
, minscale = 0.36
|
|
||||||
, f
|
, f
|
||||||
|
|
||||||
if (sw < (f = device.display.width * minscale)) {
|
if (sw < (f = device.display.width * options.minscale)) {
|
||||||
sw *= f / sw
|
sw *= f / sw
|
||||||
sh *= f / sh
|
sh *= f / sh
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sh < (f = device.display.height * minscale)) {
|
if (sh < (f = device.display.height * options.minscale)) {
|
||||||
sw *= f / sw
|
sw *= f / sw
|
||||||
sh *= f / sh
|
sh *= f / sh
|
||||||
}
|
}
|
||||||
|
@ -143,152 +138,190 @@ module.exports = function DeviceScreenDirective($document, ScalingService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function on() {
|
function shouldUpdateScreen() {
|
||||||
if (!ws) {
|
return (
|
||||||
ws = new WebSocket(device.display.url)
|
// NO if we're updating already.
|
||||||
ws.binaryType = 'blob'
|
!updating &&
|
||||||
}
|
// NO if the user has disabled the screen.
|
||||||
|
scope.$parent.showScreen &&
|
||||||
ws.onerror = function() {
|
// NO if we're not even using the device anymore.
|
||||||
// @todo HANDLE
|
device.using &&
|
||||||
}
|
// NO if the page invisible to the user?
|
||||||
|
!PageVisibilityService.hidden &&
|
||||||
ws.onclose = function() {
|
// NO if we don't have a connection yet.
|
||||||
}
|
ws.readyState === WebSocket.OPEN
|
||||||
|
// YES otherwise
|
||||||
ws.onopen = function() {
|
)
|
||||||
maybeLoadScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.onmessage = function(message) {
|
|
||||||
loading = false
|
|
||||||
|
|
||||||
if (scope.$parent.showScreen) {
|
|
||||||
screen.rotation = device.display.rotation
|
|
||||||
|
|
||||||
var blob = new Blob([message.data], {
|
|
||||||
type: 'image/jpeg'
|
|
||||||
})
|
|
||||||
|
|
||||||
var img = new Image()
|
|
||||||
img.onload = function() {
|
|
||||||
// Check to set the size only if updated
|
|
||||||
if (cachedScreen.bounds.w !== screen.bounds.w ||
|
|
||||||
cachedScreen.bounds.h !== screen.bounds.h ||
|
|
||||||
cachedImageWidth !== img.width ||
|
|
||||||
cachedImageHeight !== img.height ||
|
|
||||||
cachedScreen.rotation !== screen.rotation) {
|
|
||||||
|
|
||||||
cachedScreen.bounds.w = screen.bounds.w
|
|
||||||
cachedScreen.bounds.h = screen.bounds.h
|
|
||||||
|
|
||||||
cachedImageWidth = img.width
|
|
||||||
cachedImageHeight = img.height
|
|
||||||
|
|
||||||
cachedScreen.rotation = screen.rotation
|
|
||||||
|
|
||||||
canvas.width = cachedImageWidth
|
|
||||||
canvas.height = cachedImageHeight
|
|
||||||
|
|
||||||
var projectedSize = screen.scaler.projectedSize(
|
|
||||||
screen.bounds.w
|
|
||||||
, screen.bounds.h
|
|
||||||
, screen.rotation
|
|
||||||
)
|
|
||||||
|
|
||||||
canvas.style.width = projectedSize.width + 'px'
|
|
||||||
canvas.style.height = projectedSize.height + 'px'
|
|
||||||
|
|
||||||
// @todo Make sure that each position is able to rotate smoothly
|
|
||||||
// to the next one. This current setup doesn't work if rotation
|
|
||||||
// changes from 180 to 270 (it will do a reverse rotation).
|
|
||||||
switch (screen.rotation) {
|
|
||||||
case 0:
|
|
||||||
canvas.style[cssTransform] = 'rotate(0deg)'
|
|
||||||
break
|
|
||||||
case 90:
|
|
||||||
canvas.style[cssTransform] = 'rotate(-90deg)'
|
|
||||||
break
|
|
||||||
case 180:
|
|
||||||
canvas.style[cssTransform] = 'rotate(-180deg)'
|
|
||||||
break
|
|
||||||
case 270:
|
|
||||||
canvas.style[cssTransform] = 'rotate(90deg)'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.drawImage(img, 0, 0)
|
|
||||||
|
|
||||||
// Try to forcefully clean everything to get rid of memory leaks.
|
|
||||||
img.onload = img.onerror = null
|
|
||||||
img.src = BLANK_IMG
|
|
||||||
img = null
|
|
||||||
url = null
|
|
||||||
blob = null
|
|
||||||
|
|
||||||
// Next please
|
|
||||||
maybeLoadScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = URL.createObjectURL(blob)
|
|
||||||
img.src = url
|
|
||||||
|
|
||||||
// Reset error, if any
|
|
||||||
if (scope.displayError) {
|
|
||||||
scope.$apply(function () {
|
|
||||||
scope.displayError = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBounds()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function off() {
|
function hasImageAreaChanged(img) {
|
||||||
loading = false
|
return cachedScreen.bounds.w !== screen.bounds.w ||
|
||||||
|
cachedScreen.bounds.h !== screen.bounds.h ||
|
||||||
|
cachedImageWidth !== img.width ||
|
||||||
|
cachedImageHeight !== img.height ||
|
||||||
|
cachedScreen.rotation !== screen.rotation
|
||||||
|
}
|
||||||
|
|
||||||
if (ws) {
|
function updateImageArea(img) {
|
||||||
ws.onmessage = ws.onerror = ws.onclose = null
|
if (!hasImageAreaChanged(img)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedScreen.bounds.w = screen.bounds.w
|
||||||
|
cachedScreen.bounds.h = screen.bounds.h
|
||||||
|
|
||||||
|
cachedImageWidth = img.width
|
||||||
|
cachedImageHeight = img.height
|
||||||
|
|
||||||
|
cachedScreen.rotation = screen.rotation
|
||||||
|
|
||||||
|
if (options.autoScaleForRetina) {
|
||||||
|
canvas.width = cachedImageWidth * frontBackRatio
|
||||||
|
canvas.height = cachedImageHeight * frontBackRatio
|
||||||
|
g.scale(frontBackRatio, frontBackRatio)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
canvas.width = cachedImageWidth
|
||||||
|
canvas.height = cachedImageHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
var projectedSize = scaler.projectedSize(
|
||||||
|
screen.bounds.w
|
||||||
|
, screen.bounds.h
|
||||||
|
, screen.rotation
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas.style.width = projectedSize.width + 'px'
|
||||||
|
canvas.style.height = projectedSize.height + 'px'
|
||||||
|
|
||||||
|
// @todo Make sure that each position is able to rotate smoothly
|
||||||
|
// to the next one. This current setup doesn't work if rotation
|
||||||
|
// changes from 180 to 270 (it will do a reverse rotation).
|
||||||
|
switch (screen.rotation) {
|
||||||
|
case 0:
|
||||||
|
canvas.style[cssTransform] = 'rotate(0deg)'
|
||||||
|
break
|
||||||
|
case 90:
|
||||||
|
canvas.style[cssTransform] = 'rotate(-90deg)'
|
||||||
|
break
|
||||||
|
case 180:
|
||||||
|
canvas.style[cssTransform] = 'rotate(-180deg)'
|
||||||
|
break
|
||||||
|
case 270:
|
||||||
|
canvas.style[cssTransform] = 'rotate(90deg)'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkEnabled() {
|
||||||
|
if (shouldUpdateScreen()) {
|
||||||
|
updating = false
|
||||||
|
updateBounds()
|
||||||
|
maybeLoadScreen()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
try {
|
||||||
|
ws.onerror = ws.onclose = ws.onmessage = ws.onopen = null
|
||||||
ws.close()
|
ws.close()
|
||||||
ws = null
|
ws = null
|
||||||
}
|
}
|
||||||
|
catch (err) { /* noop */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.$watch('$parent.showScreen', function (val) {
|
var ws = new WebSocket(device.display.url)
|
||||||
if (val) {
|
ws.binaryType = 'blob'
|
||||||
on()
|
|
||||||
} else {
|
|
||||||
off()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function checkEnabled() {
|
ws.onerror = function errorListener() {
|
||||||
var using = device && device.using
|
// @todo Handle
|
||||||
if (using && !PageVisibilityService.hidden) {
|
}
|
||||||
on()
|
|
||||||
|
ws.onclose = function closeListener() {
|
||||||
|
// @todo Maybe handle
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onopen = function openListener() {
|
||||||
|
checkEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onmessage = function messageListener(message) {
|
||||||
|
updating = false
|
||||||
|
|
||||||
|
if (shouldUpdateScreen()) {
|
||||||
|
screen.rotation = device.display.rotation
|
||||||
|
|
||||||
|
var blob = new Blob([message.data], {
|
||||||
|
type: 'image/jpeg'
|
||||||
|
})
|
||||||
|
|
||||||
|
var img = new Image()
|
||||||
|
|
||||||
|
img.onload = function() {
|
||||||
|
updateImageArea(this)
|
||||||
|
|
||||||
|
g.drawImage(img, 0, 0)
|
||||||
|
|
||||||
|
// Try to forcefully clean everything to get rid of memory leaks.
|
||||||
|
img.onload = img.onerror = null
|
||||||
|
img.src = BLANK_IMG
|
||||||
|
img = null
|
||||||
|
url = null
|
||||||
|
blob = null
|
||||||
|
}
|
||||||
|
|
||||||
|
img.onerror = function() {
|
||||||
|
// Happily ignore. I suppose this shouldn't happen, but
|
||||||
|
// sometimes it does, presumably when we're loading images
|
||||||
|
// too quickly.
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = URL.createObjectURL(blob)
|
||||||
|
img.src = url
|
||||||
|
|
||||||
|
// Next please
|
||||||
|
maybeLoadScreen()
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
off()
|
// Reset error, if any
|
||||||
|
if (scope.displayError) {
|
||||||
|
scope.$apply(function () {
|
||||||
|
scope.displayError = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: instead of fa-pane-resize, a fa-child-pane-resize could be better
|
||||||
|
scope.$on('fa-pane-resize', _.throttle(updateBounds, 16))
|
||||||
|
|
||||||
|
scope.retryLoadingScreen = function () {
|
||||||
|
if (scope.displayError === 'secure') {
|
||||||
|
control.home()
|
||||||
|
}
|
||||||
|
$timeout(maybeLoadScreen, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
scope.$watch('device.using', checkEnabled)
|
scope.$watch('device.using', checkEnabled)
|
||||||
scope.$on('visibilitychange', checkEnabled)
|
scope.$on('visibilitychange', checkEnabled)
|
||||||
|
scope.$watch('$parent.showScreen', checkEnabled)
|
||||||
|
|
||||||
scope.$on('guest-portrait', function () {
|
scope.$on('guest-portrait', function () {
|
||||||
control.rotate(0)
|
control.rotate(0)
|
||||||
updateBounds()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
scope.$on('guest-landscape', function () {
|
scope.$on('guest-landscape', function () {
|
||||||
control.rotate(90)
|
control.rotate(90)
|
||||||
setDisplayDensity(2)
|
|
||||||
updateBounds()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
scope.$on('$destroy', off)
|
$window.addEventListener('beforeunload', stop, false)
|
||||||
|
|
||||||
|
scope.$on('$destroy', function() {
|
||||||
|
stop()
|
||||||
|
$window.removeEventListener('beforeunload', stop, false)
|
||||||
|
})
|
||||||
})()
|
})()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -490,7 +523,7 @@ module.exports = function DeviceScreenDirective($document, ScalingService,
|
||||||
var x = e.pageX - screen.bounds.x
|
var x = e.pageX - screen.bounds.x
|
||||||
, y = e.pageY - screen.bounds.y
|
, y = e.pageY - screen.bounds.y
|
||||||
, pressure = 0.5
|
, pressure = 0.5
|
||||||
, scaled = screen.scaler.coords(
|
, scaled = scaler.coords(
|
||||||
screen.bounds.w
|
screen.bounds.w
|
||||||
, screen.bounds.h
|
, screen.bounds.h
|
||||||
, x
|
, x
|
||||||
|
@ -548,7 +581,7 @@ module.exports = function DeviceScreenDirective($document, ScalingService,
|
||||||
var x = e.pageX - screen.bounds.x
|
var x = e.pageX - screen.bounds.x
|
||||||
, y = e.pageY - screen.bounds.y
|
, y = e.pageY - screen.bounds.y
|
||||||
, pressure = 0.5
|
, pressure = 0.5
|
||||||
, scaled = screen.scaler.coords(
|
, scaled = scaler.coords(
|
||||||
screen.bounds.w
|
screen.bounds.w
|
||||||
, screen.bounds.h
|
, screen.bounds.h
|
||||||
, x
|
, x
|
||||||
|
@ -726,7 +759,7 @@ module.exports = function DeviceScreenDirective($document, ScalingService,
|
||||||
, x = touch.pageX - screen.bounds.x
|
, x = touch.pageX - screen.bounds.x
|
||||||
, y = touch.pageY - screen.bounds.y
|
, y = touch.pageY - screen.bounds.y
|
||||||
, pressure = touch.force || 0.5
|
, pressure = touch.force || 0.5
|
||||||
, scaled = screen.scaler.coords(
|
, scaled = scaler.coords(
|
||||||
screen.bounds.w
|
screen.bounds.w
|
||||||
, screen.bounds.h
|
, screen.bounds.h
|
||||||
, x
|
, x
|
||||||
|
@ -759,7 +792,7 @@ module.exports = function DeviceScreenDirective($document, ScalingService,
|
||||||
, x = touch.pageX - screen.bounds.x
|
, x = touch.pageX - screen.bounds.x
|
||||||
, y = touch.pageY - screen.bounds.y
|
, y = touch.pageY - screen.bounds.y
|
||||||
, pressure = touch.force || 0.5
|
, pressure = touch.force || 0.5
|
||||||
, scaled = screen.scaler.coords(
|
, scaled = scaler.coords(
|
||||||
screen.bounds.w
|
screen.bounds.w
|
||||||
, screen.bounds.h
|
, screen.bounds.h
|
||||||
, x
|
, x
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue