Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Device motion permission #4303

Merged
merged 1 commit into from Nov 19, 2019
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -0,0 +1,47 @@
---
title: device-orientation-permission-ui
type: components
layout: docs
parent_section: components
source_code: src/components/scene/device-orientation-permission-ui.js
---

Some browsers like Safari on iOS 13 and later require sites to request user permission to access DeviceOrientation events. This component presents a permission dialog for the user to grant or deny access.
The device-orientation-permission-ui component applies only to the [`<a-scene>` element][scene]

To configure the style of the dialog one can redefine the associated css styles. To change the colors of the allow, deny and ok buttons:

```css
.a-dialog-allow-button {
background-color: red;
}
.a-dialog-deny-button {
background-color: blue;
}
.a-dialog-ok-button {
background-color: green;
}
```

## Example

```html
<a-scene device-orientation-permission-ui="enabled: false"></a-scene>
```

## Properties

| Property | Description | Default Value |
|---------------|---------------------------------------------------------------------|---------------|
| enabled | Whether or not to display the dialog when required | true |

## Events

| Event Name | Description |
|--------------------------------------|--------------------------------------------------------------------------------------------|
| deviceorientationpermissiongranted | User has granted access to DeviceOrientation events |
| deviceorientationpermissionrejected | User or browser has denied access to DeviceOrientation events |
| deviceorientationpermissionrequested | Application has requested permission to access DeviceOrientation events |
[scene]: ../core/scene.md
@@ -33,6 +33,7 @@ require('./windows-motion-controls');

require('./scene/background');
require('./scene/debug');
require('./scene/device-orientation-permission-ui');
require('./scene/embedded');
require('./scene/inspector');
require('./scene/fog');
@@ -0,0 +1,173 @@
/* global DeviceOrientationEvent */
var registerComponent = require('../../core/component').registerComponent;
var utils = require('../../utils/');
var bind = utils.bind;

var constants = require('../../constants/');

var MODAL_CLASS = 'a-modal';
var DIALOG_CLASS = 'a-dialog';
var DIALOG_TEXT_CLASS = 'a-dialog-text';
var DIALOG_TEXT_CONTAINER_CLASS = 'a-dialog-text-container';
var DIALOG_BUTTONS_CONTAINER_CLASS = 'a-dialog-buttons-container';
var DIALOG_BUTTON_CLASS = 'a-dialog-button';
var DIALOG_ALLOW_BUTTON_CLASS = 'a-dialog-allow-button';
var DIALOG_DENY_BUTTON_CLASS = 'a-dialog-deny-button';
var DIALOG_OK_BUTTON_CLASS = 'a-dialog-ok-button';

/**
* UI for enabling device motion permission
*/
module.exports.Component = registerComponent('device-orientation-permission-ui', {
schema: {enabled: {default: true}},

init: function () {
var self = this;

if (!this.data.enabled) { return; }

// Show alert on iPad if Safari is on desktop mode.
if (utils.device.isMobileDeviceRequestingDesktopSite()) { this.showMobileDesktopModeAlert(); }

// Browser doesn't support or doesn't require permission to DeviceOrientationEvent API.
if (!DeviceOrientationEvent || !DeviceOrientationEvent.requestPermission) { return; }
this.onDeviceMotionDialogAllowClicked = bind(this.onDeviceMotionDialogAllowClicked, this);
this.onDeviceMotionDialogDenyClicked = bind(this.onDeviceMotionDialogDenyClicked, this);
// Show dialog only if permission has not yet been granted.
DeviceOrientationEvent.requestPermission().catch(function () {
self.devicePermissionDialogEl = createPermissionDialog(
'This immersive website requires access to your device motion sensors',
self.onDeviceMotionDialogAllowClicked,
self.onDeviceMotionDialogDenyClicked);
self.el.appendChild(self.devicePermissionDialogEl);
}).then(function () {
self.el.emit('deviceorientationpermissiongranted');
});
},

remove: function () {
// This removes the modal screen
if (this.devicePermissionDialogEl) { this.el.removeChild(this.devicePermissionDialogEl); }
},

onDeviceMotionDialogDenyClicked: function () {
this.remove();
},

showMobileDesktopModeAlert: function () {
var self = this;
var safariIpadAlertEl = createAlertDialog(
'Request the mobile version of this site to enjoy it in immersive mode',
function () { self.el.removeChild(safariIpadAlertEl); });
this.el.appendChild(safariIpadAlertEl);
},

/**
* Enable device motion permission when clicked.
*/
onDeviceMotionDialogAllowClicked: function () {
var self = this;
this.el.emit('deviceorientationpermissionrequested');
DeviceOrientationEvent.requestPermission().then(function (response) {
if (response === 'granted') {
self.el.emit('deviceorientationpermissiongranted');
} else {
self.el.emit('deviceorientationpermissionrejected');
}
self.remove();
}).catch(console.error);
}
});

/**
* Create a modal dialog that request users permission to access the Device Motion API.
*
* @param {function} onAllowClicked - click event handler
* @returns {Element} Wrapper <div>.
*/
function createPermissionDialog (text, onAllowClicked, onDenyClicked) {
var buttonsContainer;
var denyButton;
var acceptButton;

buttonsContainer = document.createElement('div');
buttonsContainer.classList.add(DIALOG_BUTTONS_CONTAINER_CLASS);

// Buttons
denyButton = document.createElement('button');
denyButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_DENY_BUTTON_CLASS);
denyButton.setAttribute(constants.AFRAME_INJECTED, '');
denyButton.innerHTML = 'Deny';
buttonsContainer.appendChild(denyButton);

acceptButton = document.createElement('button');
acceptButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_ALLOW_BUTTON_CLASS);
acceptButton.setAttribute(constants.AFRAME_INJECTED, '');
acceptButton.innerHTML = 'Allow';
buttonsContainer.appendChild(acceptButton);

// Ask for sensor events to be used
acceptButton.addEventListener('click', function (evt) {
evt.stopPropagation();
onAllowClicked();
});

denyButton.addEventListener('click', function (evt) {
evt.stopPropagation();
onDenyClicked();
});

return createDialog(text, buttonsContainer);
}

function createAlertDialog (text, onOkClicked) {
var buttonsContainer;
var okButton;

buttonsContainer = document.createElement('div');
buttonsContainer.classList.add(DIALOG_BUTTONS_CONTAINER_CLASS);

// Buttons
okButton = document.createElement('button');
okButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_OK_BUTTON_CLASS);
okButton.setAttribute(constants.AFRAME_INJECTED, '');
okButton.innerHTML = 'Ok';
buttonsContainer.appendChild(okButton);

// Ask for sensor events to be used
okButton.addEventListener('click', function (evt) {
evt.stopPropagation();
onOkClicked();
});

return createDialog(text, buttonsContainer);
}

function createDialog (text, buttonsContainerEl) {
var modalContainer;
var dialog;
var dialogTextContainer;
var dialogText;

modalContainer = document.createElement('div');
modalContainer.classList.add(MODAL_CLASS);
modalContainer.setAttribute(constants.AFRAME_INJECTED, '');

dialog = document.createElement('div');
dialog.className = DIALOG_CLASS;
dialog.setAttribute(constants.AFRAME_INJECTED, '');
modalContainer.appendChild(dialog);

dialogTextContainer = document.createElement('div');
dialogTextContainer.classList.add(DIALOG_TEXT_CONTAINER_CLASS);
dialog.appendChild(dialogTextContainer);

dialogText = document.createElement('div');
dialogText.classList.add(DIALOG_TEXT_CLASS);
dialogText.innerHTML = text;
dialogTextContainer.appendChild(dialogText);

dialog.appendChild(buttonsContainerEl);

return modalContainer;
}
@@ -65,6 +65,7 @@ module.exports.AScene = registerElement('a-scene', {
this.setAttribute('keyboard-shortcuts', '');
this.setAttribute('screenshot', '');
this.setAttribute('vr-mode-ui', '');
this.setAttribute('device-orientation-permission-ui', '');
}
},

@@ -289,3 +289,93 @@ a-scene audio {
top: 0px;
color: white;
}

.a-modal {
position: absolute;
background: rgba(0, 0, 0, 0.60);
background-size: 50% 50%;
bottom: 0;
font-size: 14px;
font-weight: 600;
left: 0;
line-height: 20px;
right: 0;
position: fixed;
top: 0;
z-index: 9999999;
}

.a-dialog {
position: relative;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 199995;
width: 300px;
height: 200px;
background-size: contain;
background-color: white;
font-family: sans-serif, monospace;
font-size: 20px;
border-radius: 3px;
padding: 6px;
}

.a-dialog-text-container {
width: 100%;
height: 70%;
align-self: flex-start;
display: flex;
justify-content: center;
align-content: center;
flex-direction: column;
}

.a-dialog-text {
display: inline-block;
font-weight: normal;
font-size: 14pt;
margin: 8px;
}

.a-dialog-buttons-container {
display: inline-flex;
align-self: flex-end;
width: 100%;
height: 30%;
}

.a-dialog-button {
cursor: pointer;
align-self: center;
opacity: 0.9;
height: 80%;
width: 50%;
font-size: 12pt;
margin: 4px;
border-radius: 2px;
text-align:center;
border: none;
display: inline-block;
-webkit-transition: all 0.25s ease-in-out;
transition: all 0.25s ease-in-out;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10), 0 1px 2px rgba(0, 0, 0, 0.20);
user-select: none;
}

.a-dialog-permission-button:hover {
box-shadow: 0 7px 14px rgba(0,0,0,0.20), 0 2px 2px rgba(0,0,0,0.20);
}

.a-dialog-allow-button {
background-color: #8ce3ba;
}

.a-dialog-deny-button {
background-color: #ff4b8b;
}

.a-dialog-ok-button {
background-color: #8ce3ba;
width: 100%;
}
@@ -122,6 +122,11 @@ function isIOS () {
}
module.exports.isIOS = isIOS;

function isMobileDeviceRequestingDesktopSite () {
return !isMobile() && window.orientation !== undefined;
}
module.exports.isMobileDeviceRequestingDesktopSite = isMobileDeviceRequestingDesktopSite;

/**
* Detect browsers in Stand-Alone headsets
*/
@@ -0,0 +1,49 @@
/* global assert, process, setup, suite, test, teardown */
var entityFactory = require('../../helpers').entityFactory;
var utils = require('index').utils;

var PERMISSION_DIALOG_CLASSES = ['.a-modal', '.a-dialog', '.a-dialog-allow-button', '.a-dialog-deny-button'];
var ALERT_DIALOG_CLASSES = ['.a-modal', '.a-dialog', '.a-dialog-ok-button'];

suite('device-orientation-permission-ui', function () {
suite('device permission dialog', function () {
setup(function (done) {
this.entityEl = entityFactory();
var el = this.el = this.entityEl.parentNode;
window.DeviceOrientationEvent = {
requestPermission: function () { return Promise.reject(); }
};
el.addEventListener('loaded', function () { done(); });
});

test('appends permission dialog', function (done) {
var scene = this.el;
process.nextTick(function () {
PERMISSION_DIALOG_CLASSES.forEach(function (uiClass) {
assert.equal(scene.querySelectorAll(uiClass).length, 1);
done();
});
});
});
});

suite('desktop request permission dialog', function () {
setup(function (done) {
this.entityEl = entityFactory();
var el = this.el = this.entityEl.parentNode;
this.sinon.stub(utils.device, 'isMobileDeviceRequestingDesktopSite').returns(true);
el.addEventListener('loaded', function () { done(); });
});

test('appends UI', function () {
var scene = this.el;
ALERT_DIALOG_CLASSES.forEach(function (uiClass) {
assert.equal(scene.querySelectorAll(uiClass).length, 1);
});
});
});

teardown(function () {
window.DeviceOrientationEvent = undefined;
});
});
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.